Sesje - Podstawy PHP
Transkrypt
Sesje - Podstawy PHP
Artykuł pobrano ze strony eioba.pl
Sesje - Podstawy PHP
Sesje
Protokół HTTP jest protokołem bezstanowym. Oznacza to, że serwer WWW rozpatruje każde żądanie niezależnie od
innych, nie szukając żadnych powiązań w stylu wysyłania ich przez tego samego internautę. Utrudnia to
teoretycznie tworzenie wszelkich systemów autoryzacji, które wymagają śledzenia poczynań użytkownika na
naszej stronie i przenoszenia jego danych autoryzacyjnych między kolejnymi żądaniami, czyli krótko mówiąc wymagają obecności systemu sesji. Używając PHP lub innego dynamicznego języka server-side można je jednak
zasymulować. "Nasz" język jest o tyle prosty, iż posiada już zaimplementowane stosowne funkcje. My tylko musimy
zacząć ich używać.
Działanie sesji w PHP jest bardzo proste. W momencie pierwszego trafienia na stronę interpreter tworzy specjalny,
losowy oraz unikalny identyfikator przesyłany między żądaniami za pomocą ciastek lub parametru PHPSESSID
doklejanego automatycznie do adresów URL. Na jego podstawie odczytywany jest później odpowiedni plik z danymi
sesji zapisany gdzieś na serwerze. Pod koniec przetwarzania żądania wszystkie wprowadzone przez skrypt zmiany
są z powrotem zapisywane do wspomnianego pliku tak, aby były widoczne przy wejściu na kolejną podstronę. I tak
to się toczy.
Wprowadzenie do sesji
Czas na trochę praktyki. Aby zainicjować mechanizm sesji, wystarczy wywołać funkcję session_start(), najlepiej na
początku naszej aplikacji. Od tego momentu do naszej dyspozycji zostaje oddana super globalna tablica $_SESSION
- wszystkie zapisane do niej dane są przesyłane między kolejnymi żądaniami. Popatrzmy na pierwszy, bardzo
prosty przykład licznika odwiedzonych już podstron:
<?php
session_start(); // 1
if(!isset($_SESSION['licznik'])) // 2
{
$_SESSION['licznik'] = ;
}
$_SESSION['licznik']++; // 3
echo 'Odwiedziłeś już '.$_SESSION['licznik'].' podstron!'; // 4
?>
Oto analiza:
1. Inicjujemy sesje
2. Jeżeli jest to pierwsza wizyta, tablica z sesjami nie zawiera żadnych danych. Dobrym zwyczajem jest ich
inicjowanie, aby nie zostać zaatakowanym tysiącami komunikatów Notice.
3. Zmieniamy dane sesji
4. Odczytujemy dane sesji
Po odświeżeniu strony zauważymy, że licznik wskazuje już "2", po kolejnym - "3". Oznacza to, że PHP zapisuje
zmienną $licznik i przesyła ją między naszymi żądaniami. Różnica pomiędzy sesjami, a ciastkami jest taka, że dane
te w ogóle nie opuszczają serwera WWW, są przez to (w teorii) bezpieczniejsze.
Prosta autoryzacja użytkowników
Napiszemy teraz prosty skrypt do autoryzacji użytkowników bazujący na sesjach. Jak wspomnieliśmy w poprzednim
rozdziale, nie powinno się przesyłać pomiędzy żądaniami haseł oraz loginów użytkowników, nieważne czy w formie
ciastek, czy sesji. Alternatywą jest ich ID. Jak przekonasz się w następnych rozdziałach, bazy danych, gdzie
większość aplikacji WWW trzyma swoje informacje, wcale nie identyfikują rekordów jakimiś abstrakcyjnymi
rzeczami typu login lub tytuł. Operują na zwyczajnych, automatycznie nadawanych i unikalnych liczbach zwanych
identyfikatorami (w skrócie pisze się "id"). Dzięki temu można szybko je do siebie porównać, co ma szczególne
znaczenie w przypadku ogromnej ilości rekordów. Takie też ID przypisane użytkownikom przesyłane są w sesjach.
Oczywiście, zgodnie z praktyką stosowaną w bazach danych, numerację rekordów rozpoczynamy od 1. Rekord o ID
równym 0 nie istnieje.
Zatem, jak taki system logowania działa? Kiedy sesja jest już załadowana, skrypt sprawdza zapisany w niej ID
użytkownika. Jeżeli jest on większy od zera, ktoś jest już zalogowany i wystarczy tylko pobrać skądś dane jego
profilu. W przypadku ID równego 0 mamy do czynienia z kimś anonimowym. Tu, w zależności od sytuacji możemy
mu wyświetlać ogólnodostępne treści albo formularz logowania. Pisanie skryptu zaczniemy od stworzenia sobie
namiastki bazy danych. Będzie nią tablica $uzytkownicy przechowująca loginy oraz hasła użytkowników. Ponadto
napiszemy funkcję czyIstnieje() znajdującą ID użytkownika o podanym loginie i haśle lub false, kiedy takowy nie
istnieje. Hasła naturalnie haszujemy poznanym już algorytmem sha1:
<?php
$uzytkownicy = array(1 =>
array('login' => 'user1', 'haslo' => sha1('ppp')),
array('login' => 'user2', 'haslo' => sha1('ddd')),
array('login' => 'user3', 'haslo' => sha1('fff'))
);
function czyIstnieje($login, $haslo)
{
global $uzytkownicy;
$haslo = sha1($haslo);
foreach($uzytkownicy as $id => $dane)
{
if($dane['login'] == $login && $dane['haslo'] == $haslo)
{
// O, jest ktos taki - zwroc jego ID
return $id;
}
}
// Jeżeli doszedłeś a tutaj, to takiego użytkownika nie ma
return false;
} // end czyIstnieje();
Teraz zaczniemy właściwy skrypt. Zainicjujemy sesję i obsłużymy sytuację pierwszej wizyty - w takim wypadku
gościa oznaczymy jako osobę anonimową (niezalogowaną):
// Wlasciwy skrypt
session_start();
if(!isset($_SESSION['uzytkownik']))
{
// Sesja się zaczyna, wiec inicjujemy użytkownika anonimowego
$_SESSION['uzytkownik'] = ;
}
Krótka piłka - ID większy od 0? Ktoś jest zalogowany:
if($_SESSION['uzytkownik'] > )
{
// Ktos jest zalogowany
echo 'Witaj, '.$uzytkownicy[$_SESSION['uzytkownik']]['login'].' na naszej stronie!';
}
else
{
Jednak jeśli nie jest, to musimy zaprogramować zarówno pokazywanie formularza logowania, jak i autoryzację
użytkownika na podstawie danych z niego. Zaczynamy od tej drugiej opcji. Korzystając z funkcji czyIstnieje()
pobieramy ID użytkownika. Jeżeli jest on różny od false, wpisujemy go do sesji, tym samym logując go.
// Niezalogowany
if($_SERVER['REQUEST_METHOD'] == 'POST')
{
if(($id = czyIstnieje($_POST['login'], $_POST['haslo'])) !== false)
{
// Logujemy uzytkownika, wpisal poprawne dane
$_SESSION['uzytkownik'] = $id;
echo 'Dziekujemy, zostales zalogowany! <a href="sesje_2.php">Dalej</a>';
}
else
{
echo 'Podales nieprawidlowe dane, zegnaj! <a href="sesje_2.php">Dalej</a>';
}
}
else
{
Kiedy dane nie nadeszły z formularza, znaczy to, że trzeba go wyświetlić:
echo '<form method="post" action="sesje_2.php">
Zaloguj sie: <input type="text" name="login"/><input type="password" name="haslo"/>
<input type="submit" value="OK"/></form>';
}
}
?>
I gotowe. Wszystko fajnie, wszystko pięknie, nasza witryna sobie działa, użytkownicy się logują, dodając coraz to
nowe treści, lecz pewnego dnia dostajesz e-maila z ostrzeżeniem, że ktoś włamał się do serwisu i wszystko rozwalił.
Co nawaliło? Czyżby zawiódł mechanizm sesji?
Bezpieczeństwo sesji
Prawdopodobnie tak. Sytuacja z mechanizmem sesji standardowo dostępnym w PHP jest o tyle śmieszna, że jego
autorzy właśnie nam zostawili swobodę działania odnośnie tego, jak go zabezpieczyć przed kradzieżą. Normalnie
użyty jest on bowiem dziurawy, jak szwajcarskie sito. Kradzież sesji nie nastręcza wielkich trudności, lecz na
nieszczęście, wiele osób o tym nie pamięta (także autorów artykułów pokazujących, jak z niego korzystać!).
Włamania wykorzystujące dziury w mechanizmach sesji mają swoje fachowe nazwy. Pierwszą z nich jest Session
Fixation. Jej działanie jest bardzo proste. Wykorzystujemy tutaj właściwość, że kiedy podamy skryptowi jakiś
nieistniejący ID sesji, PHP automatycznie dorobi dane i nie przejmie się tym, że tak naprawdę to my go
wygenerowaliśmy, a nie jakiś algorytm losująco-mieszający. Teraz popatrz: podszywasz się pod pracownika
jakiegoś serwisu internetowego i każesz nieświadomemu użytkownikowi odwiedzić jakiśtam adres, najlepiej
wymagający zalogowania. Do adresu URL doczepiasz ciąg ?PHPSESSID=abcdef. "abcdef" jest wymyślonym przez
nas identyfikatorem. Kiedy użytkownik posłusznie się zaloguje, jest zdany na naszą łaskę. Mamy ID jego sesji i
możemy działać tak, jakbyśmy byli nim.Jeszcze więcej cwaniactwa i możemy uzyskać nawet sesję administratora,
co dla serwisu oznacza oczywiście katastrofę. Aby się przed tym zabezpieczyć, wystarczy po zainicjowaniu
mechanizmu sesji dokleić bardzo prosty kod:
<?php
session_start();
if (!isset($_SESSION['inicjuj']))
{
session_regenerate_id();
$_SESSION['inicjuj'] = true;
}
?>
Przy tworzeniu nowej sesji, dzięki funkcji session_regenerate_id() mamy pewność, że sesja dostanie losowy ID.
Teraz nawet, jeżeli podamy zmyślnie ?PHPSESSID=abcdef, nic nam to nie da, bo PHP i tak sobie wszystko
wygeneruje po swojemu i zostaniemy w tym samym miejscu, co byliśmy. Jednak nie spoczywaj jeszcze na laurach.
ID nadal może zostać wykradziony. Wystarczy, że jakiś ciamajda skopiuje znajomemu link do jakiegoś zasobu,
mając przy tym wyłączone ciastka. Oczywiście w linku znajdzie się wtedy jego LOSOWY identyfikator sesji, który w
ten sposób zostanie ujawniony przed światem. Znajomy może teraz wędrować sobie po serwisie wykorzystując
sesję ciamajdy. session_regenerate_id() tutaj nie zadziałała, bo przecież ta sesja już istnieje. Ataki tego rodzaju
określa się mianem Session Hijacking. Zabezpieczenie się przed nimi także jest proste. Wystarczy w sesji przesyłać
np. adres IP komputera wraz z nazwą przeglądarki, spod których została ona utworzona, a następnie porównywać
je przy kolejnych wizytach z danymi dostarczonymi przez serwer. Jeżeli wystąpi niezgodność, ktoś próbuje użyć
cudzej sesji.
<?php
session_start();
if (!isset($_SESSION['inicjuj']))
{
session_regenerate_id();
$_SESSION['inicjuj'] = true;
$_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
}
if($_SESSION['ip'] !== $_SERVER['REMOTE_ADDR'])
{
die('Proba przejecia sesji udaremniona!');
}
?>
Powyższy przykład zawiera łatki przeciwko obu włamaniom (co prawda w przypadku tej drugiej o wiele lepszym
rozwiązaniem byłoby utworzenie sesji na nowo w przypadku niezgodności, niż obwieszczanie całemu światu
naszego "odkrycia"). Pamiętaj jednak, że najsłabszym ogniwem jest zawsze człowiek. Jeżeli hacker zdobędzie twoje
hasło, zabezpieczenia na nic się nie zdadzą, chyba że akurat masz stały adres IP i tak sobie wszystko napisałeś, że
na twoje konto można się tylko z niego logować. Tylko kto stosuje tak rygorystyczne i nieporęczne środki
bezpieczeństwa.
Podsumowanie
Sesje PHP są bardzo dobrym rozwiązaniem, jednak paradoksalnie wiele profesjonalnych aplikacji pisze własne
systemy sesji całkowicie od zera. Powodów jest kilka:
1. Integracja ze strukturą kodu reszty aplikacji
2. Niezależność od PHP. Kiedy mechanizm sesji pojawił się w PHP po raz pierwszy, używało się go zupełnie
inaczej, niż obecnie. Przyszłość może przynieść różne rzeczy, a nasz własny mechanizm zawsze pozostanie
mimo tego taki sam.
3. Bezpieczeństwo - kiedy piszesz wszystko od zera, możesz wstawić dodatkowe zabezpieczenia tam, gdzie
normalnie nie sięgniesz.
4. Elastyczność - dlaczego dane sesji muszą być trzymane akurat w pliku? Baza danych jest przecież równie
dobra, jeśli nie lepsza.
5. Specyfika zastosowania - wiele systemów sesji służy tylko celom autoryzacji. Stąd też pisze się je od zera pod
to jedno zastosowanie, co umożliwia wprowadzenie pewnych uproszczeń oraz optymalizacji.
Jednak własny system sesji jest odpowiedzialnym kawałkiem kodu, gdyż tam to ty musisz wszystko od A do Z
zaprogramować. Na razie jedynie sygnalizujemy taką możliwość jako alternatywę dla systemu sesji wbudowanego
w PHP. Który z nich wybierzesz, to zależy tylko od Ciebie.
Treść pochodzi ze strony WikiBooks i jest udostępniana na licencji GNU FDL
Autor: WikiBooks
Artykuł pobrano ze strony eioba.pl