Ochrona identyfikatora sesji w PHP. Pułapki związane z używaniem sesji w PHP Przekazywanie wartości lub tablicy za pomocą sesji PHP

Witam szanowną społeczność.

Na początek chcę podziękować za bardzo przydatne źródło informacji. Nie raz znalazłam tu wiele ciekawych pomysłów i praktycznych porad.

Celem tego artykułu jest zwrócenie uwagi na pułapki związane z używaniem sesji w PHP. Oczywiście istnieje dokumentacja PHP i mnóstwo przykładów, a ten artykuł nie ma być kompletnym przewodnikiem. Ma na celu ujawnienie niektórych niuansów pracy z sesjami i ochronę programistów przed niepotrzebną stratą czasu.

Najczęstszym przykładem wykorzystania sesji jest oczywiście autoryzacja użytkownika. Zacznijmy od najbardziej podstawowego wdrożenia, aby stopniowo je rozwijać w miarę pojawiania się nowych zadań.

(Aby zaoszczędzić miejsce i czas, ograniczymy nasze przykłady tylko do samych funkcji sesji, zamiast budować tutaj pełnoprawną aplikację testową z piękną hierarchią klas, kompleksową obsługą błędów i innymi dobrymi rzeczami).

Funkcja startSession() ( // Jeśli sesja została już rozpoczęta, przerwij wykonywanie i zwróć TRUE // (parametr session.auto_start w pliku ustawień php.ini musi być wyłączony - wartość domyślna) if (session_id()) return true; w przeciwnym razie return session_start(); // Uwaga: przed wersją 5.3.0 funkcja session_start() zwracała TRUE nawet jeśli wystąpił błąd. // Jeśli używasz wersji wcześniejszej niż 5.3.0, przeprowadź dodatkową kontrolę for session_id() // po wywołaniu session_start() ) funkcjazniszczSession() ( if (session_id()) ( // Jeśli sesja jest aktywna, usuń ciasteczka sesji, setcookie(nazwa_sesji(), session_id(), time( )-60*60*24); // i zniszcz sesję session_unset( ); session_destroy(); ) )

Uwaga: Zakłada się, że czytelnik posiada podstawową wiedzę na temat sesji PHP, dlatego nie będziemy tutaj omawiać zasady działania funkcji session_start() i session_destroy(). Zadania związane z układem formularza logowania i uwierzytelnianiem użytkownika nie są związane z tematyką artykułu, dlatego je również pominiemy. Przypomnę, że aby zidentyfikować użytkownika w każdym kolejnym żądaniu, w momencie udanego logowania, musimy zapisać identyfikator użytkownika w zmiennej sesyjnej (np. o nazwie userid), która będzie dostępna we wszystkich kolejnych żądaniach w ciągu życie sesji. Konieczne jest także zaimplementowanie przetwarzania wyniku naszej funkcji startSession(). Jeżeli funkcja zwróci FAŁSZ, wyświetl w przeglądarce formularz logowania. Jeżeli funkcja zwróciła TRUE oraz zmienna sesyjna zawierająca identyfikator uprawnionego użytkownika (w naszym przypadku userid) istnieje - wyświetl stronę uprawnionego użytkownika (więcej informacji o obsłudze błędów znajdziesz w dodatku z dnia 2013-06- 07 w części dotyczącej zmiennych sesyjnych).

Jak dotąd wszystko jest jasne. Pytania zaczynają się, gdy trzeba wdrożyć kontrolę braku aktywności użytkowników (limit czasu sesji), umożliwić wielu użytkownikom jednoczesną pracę w jednej przeglądarce, a także zabezpieczyć sesje przed nieautoryzowanym użyciem. Zostanie to omówione poniżej.

Kontrolowanie bezczynności użytkowników za pomocą wbudowanych narzędzi PHP Pierwszym pytaniem, które często pojawia się wśród twórców różnych konsol dla użytkowników, jest automatyczne zakończenie sesji w przypadku braku aktywności ze strony użytkownika. Nie ma nic łatwiejszego niż zrobić to za pomocą wbudowanych możliwości PHP. (Ta opcja nie jest szczególnie niezawodna ani elastyczna, ale rozważymy ją pod kątem kompletności).

Funkcja startSession() ( // Limit czasu bezczynności użytkownika (w sekundach) $sessionLifetime = 300; if (session_id()) return true; // Ustaw czas życia pliku cookie ini_set("session.cookie_lifetime", $sessionLifetime); // If użytkownik ustawiono limit czasu bezczynności, ustaw czas życia sesji na serwerze // Uwaga: W przypadku serwera produkcyjnego zaleca się ustawienie tych parametrów w pliku php.ini if ​​($sessionLifetime) ini_set("session.gc_maxlifetime", $sessionLifetime) ; if (session_start( )) ( setcookie(session_name(), session_id(), time()+$sessionLifetime); return true; ) else return false; )

Kilka wyjaśnień. Jak wiadomo, PHP określa, która sesja ma zostać uruchomiona, na podstawie nazwy pliku cookie przesłanej przez przeglądarkę w nagłówku żądania. Przeglądarka z kolei otrzymuje ten plik cookie z serwera, na którym umieszcza go funkcja session_start(). Jeśli plik cookie przeglądarki utracił ważność, nie zostanie wysłany w żądaniu, co oznacza, że ​​PHP nie będzie w stanie określić, którą sesję rozpocząć i potraktuje to jako utworzenie nowej sesji. Parametr ustawień PHP session.gc_maxlifetime, który jest ustawiony jako równy limitowi czasu bezczynności użytkownika, ustawia czas życia sesji PHP i jest kontrolowany przez serwer. Kontrolowanie czasu życia sesji działa w następujący sposób (tutaj rozważymy przykład przechowywania sesji w plikach tymczasowych jako najczęstszą i domyślną opcję w PHP).

Kiedy tworzona jest nowa sesja, w katalogu ustawionym jako katalog do przechowywania sesji w parametrze ustawień PHP session.save_path tworzony jest plik o nazwie sess_, gdzie jest identyfikator sesji. Następnie w każdym żądaniu, w momencie uruchomienia już istniejącej sesji, PHP aktualizuje czas modyfikacji tego pliku. Zatem w każdym kolejnym żądaniu PHP, na podstawie różnicy pomiędzy czasem bieżącym a czasem ostatniej modyfikacji pliku sesji, może określić, czy sesja jest aktywna, czy też jej czas życia już minął. (Mechanizm usuwania starych plików sesji został omówiony bardziej szczegółowo w następnej sekcji.)

Uwaga: Należy w tym miejscu zaznaczyć, że parametr session.gc_maxlifetime dotyczy wszystkich sesji w ramach jednego serwera (a dokładniej w ramach jednego głównego procesu PHP). W praktyce oznacza to, że jeśli na serwerze działa kilka witryn i każda z nich ma swój własny limit czasu bezczynności użytkownika, to ustawienie tego parametru na jednym z serwisów spowoduje jego ustawienie dla pozostałych serwisów. To samo dotyczy hostingu współdzielonego. Aby uniknąć tej sytuacji, dla każdej witryny na tym samym serwerze używane są oddzielne katalogi sesji. Ustawienie ścieżki do katalogu sesji odbywa się za pomocą parametru session.save_path w pliku ustawień php.ini lub poprzez wywołanie funkcji ini_set(). Następnie sesje każdej witryny będą przechowywane w oddzielnych katalogach, a parametr session.gc_maxlifetime ustawiony na jednej z witryn będzie ważny tylko dla jej sesji. Nie będziemy szczegółowo omawiać tego przypadku, zwłaszcza że mamy bardziej elastyczną opcję monitorowania braku aktywności użytkowników.

Sterowanie bezczynnością użytkowników za pomocą zmiennych sesyjnych Wydawać by się mogło, że poprzednia opcja, mimo całej swojej prostoty (wystarczy kilka dodatkowych linijek kodu), daje wszystko, czego potrzebujemy. Ale co, jeśli nie każde żądanie można uznać za wynik aktywności użytkownika? Na przykład strona ma licznik czasu, który okresowo wysyła żądanie AJAX w celu otrzymania aktualizacji z serwera. Takiego żądania nie można uznać za aktywność użytkownika, co oznacza, że ​​automatyczne przedłużanie czasu trwania sesji nie jest w tym przypadku prawidłowe. Wiemy jednak, że PHP aktualizuje czas modyfikacji pliku sesji automatycznie za każdym razem, gdy wywoływana jest funkcja session_start(), co oznacza, że ​​każde żądanie doprowadzi do wydłużenia czasu życia sesji i nigdy nie nastąpi przekroczenie limitu czasu braku aktywności użytkownika. Ponadto ostatnia uwaga z poprzedniej sekcji dotycząca zawiłości parametru session.gc_maxlifetime może wydawać się dla niektórych zbyt zagmatwana i trudna do wdrożenia.

Aby rozwiązać ten problem, zrezygnujemy ze stosowania wbudowanych mechanizmów PHP i wprowadzimy kilka nowych zmiennych sesyjnych, które pozwolą nam samodzielnie kontrolować czas bezczynności użytkownika.

Funkcja startSession($isUserActivity=true) ( ​​$sessionLifetime = 300; if (session_id()) return true; // Ustaw czas życia pliku cookie przed zamknięciem przeglądarki (wszystko będziemy kontrolować po stronie serwera) ini_set("session. cookie_lifetime", 0) ; if (! session_start()) return false; $t = time(); if ($sessionLifetime) ( // Jeśli ustawiony jest limit czasu braku aktywności użytkownika, // sprawdź czas, jaki upłynął od ostatniej aktywności użytkownika // (czas ostatniego żądania, kiedy zaktualizowano zmienną sesji lastactivity) if (isset($_SESSION["lastactivity"]) && $t-$_SESSION["lastactivity"] >= $sessionLifetime) ( // Jeśli czas, który upłynął od ostatnia aktywność użytkownika, // jest większa niż limit czasu bezczynności, co oznacza, że ​​sesja wygasła i należy zakończyć sesjęzniszczSession(); return false; ) else ( // Jeśli limit czasu jeszcze nie upłynął, // i jeśli żądanie przyszło w wyniku aktywności użytkownika, // zaktualizuj zmienną lastactivity o wartość bieżącą, // wydłużając w ten sposób czas sesji o kolejne sessionLifetime sekundy if ($isUserActivity) $_SESSION["lastactivity"] = $t; ) ) zwróć wartość true; )

Podsumujmy. W każdym żądaniu sprawdzamy, czy upłynął limit czasu od ostatniej aktywności użytkownika do chwili obecnej, a jeśli został osiągnięty, niszczymy sesję i przerywamy wykonywanie funkcji, zwracając FALSE. Jeżeli limit czasu nie został osiągnięty, a do funkcji przekazany zostanie parametr $isUserActivity o wartości TRUE, aktualizujemy czas ostatniej aktywności użytkownika. Wystarczy, że w skrypcie wywołującym ustalimy, czy żądanie jest efektem aktywności użytkownika, a jeśli nie, wywołamy funkcję startSession z parametrem $isUserActivity ustawionym na FALSE.

Dodatek z 2013-06-07 Przetwarzanie wyniku funkcji sessionStart().

W komentarzach wskazano, że zwrócenie FALSE nie zapewnia pełnego zrozumienia przyczyny błędu i jest to całkowicie uczciwe. Nie publikowałem tutaj szczegółowej obsługi błędów (długość artykułu jest już dość duża), ponieważ nie jest to bezpośrednio związane z tematem artykułu. Ale biorąc pod uwagę komentarze, wyjaśnię.

Jak widać, funkcja sessionStart może zwrócić FAŁSZ w dwóch przypadkach. Albo sesja nie mogła zostać rozpoczęta z powodu wewnętrznych błędów serwera (na przykład nieprawidłowych ustawień sesji w php.ini), albo upłynął okres ważności sesji. W pierwszym przypadku musimy przekierować użytkownika na stronę z błędem informującym o problemach na serwerze oraz formularzem umożliwiającym kontakt z supportem. W drugim przypadku musimy przenieść użytkownika do formularza logowania i wyświetlić w nim odpowiedni komunikat informujący o wygaśnięciu sesji. Aby to zrobić musimy wpisać kody błędów i zwrócić odpowiedni kod zamiast FALSE, a w metodzie wywołującej sprawdzić to i podjąć odpowiednie działania.

Teraz, nawet jeśli sesja na serwerze nadal istnieje, zostanie zniszczona przy pierwszym dostępie, jeśli upłynie limit czasu bezczynności użytkownika. Stanie się to niezależnie od tego, jaki czas życia sesji jest ustawiony w globalnych ustawieniach PHP.

Uwaga: co się stanie, jeśli przeglądarka zostanie zamknięta, a plik cookie z nazwą sesji zostanie automatycznie zniszczony? Żądanie skierowane do serwera przy następnym otwarciu przeglądarki nie będzie zawierało plików cookie sesji, a serwer nie będzie mógł otworzyć sesji i sprawdzić limitu czasu bezczynności użytkownika. Dla nas jest to równoznaczne z utworzeniem nowej sesji i nie wpływa w żaden sposób na funkcjonalność ani bezpieczeństwo. Powstaje jednak zasadne pytanie - kto w takim razie zniszczy starą sesję, jeśli do tej pory zniszczyliśmy ją po upływie limitu czasu? A może będzie teraz zawieszony w katalogu sesji na zawsze? Aby wyczyścić stare sesje w PHP, istnieje mechanizm zwany zbieraniem śmieci. Działa w momencie następnego żądania skierowanego do serwera i czyści wszystkie stare sesje na podstawie daty ostatniej modyfikacji plików sesji. Jednak mechanizm zbierania śmieci nie uruchamia się przy każdym żądaniu skierowanym do serwera. Częstotliwość (a raczej prawdopodobieństwo) uruchamiania określają dwa parametry ustawień session.gc_probability i session.gc_divisor. Wynikiem podzielenia pierwszego parametru przez drugi jest prawdopodobieństwo uruchomienia mechanizmu zbierania śmieci. Zatem, aby przy każdym żądaniu do serwera uruchamiał się mechanizm zliczania sesji, parametry te muszą być ustawione na równe wartości, np. „1”. Takie podejście gwarantuje czysty katalog sesji, ale jest oczywiście zbyt kosztowne dla serwera. Dlatego na systemach produkcyjnych domyślna wartość session.gc_divisor jest ustawiona na 1000, co oznacza, że ​​mechanizm zbierania śmieci będzie działał z prawdopodobieństwem 1/1000. Jeśli poeksperymentujesz z tymi ustawieniami w pliku php.ini, możesz zauważyć, że w przypadku opisanym powyżej, gdy przeglądarka zamknie się i wyczyści wszystkie pliki cookie, w katalogu sesji nadal pozostają przez jakiś czas stare sesje. Ale nie powinno Cię to niepokoić, ponieważ... jak już wspomniano, nie wpływa to w żaden sposób na bezpieczeństwo naszego mechanizmu.

Aktualizacja z 2013-06-07 Zapobieganie zawieszaniu się skryptów z powodu blokowania plików sesji

W komentarzach poruszono kwestię zawieszania się jednocześnie uruchomionych skryptów na skutek zablokowania pliku sesji (najbardziej rzucającą się w oczy opcją jest długa ankieta).

Na początek zauważam, że problem ten nie zależy bezpośrednio od obciążenia serwera ani liczby użytkowników. Oczywiście im więcej żądań, tym wolniej wykonywane są skrypty. Ale jest to zależność pośrednia. Problem pojawia się tylko w ramach jednej sesji, gdy serwer otrzymuje kilka żądań w imieniu jednego użytkownika (przykładowo jedno z nich to długie odpytywanie, a pozostałe to zwykłe żądania). Każde żądanie próbuje uzyskać dostęp do tego samego pliku sesji, a jeśli poprzednie żądanie nie odblokowało pliku, kolejne będzie czekać.

Aby ograniczyć do minimum blokowanie plików sesji, zdecydowanie zaleca się zamknięcie sesji poprzez wywołanie funkcji session_write_close() natychmiast po zakończeniu wszystkich działań ze zmiennymi sesji. W praktyce oznacza to, że nie należy przechowywać wszystkiego w zmiennych sesyjnych i uzyskiwać do nich dostęp w trakcie wykonywania skryptu. A jeśli potrzebujesz przechowywać jakieś dane robocze w zmiennych sesji, to przeczytaj je natychmiast po rozpoczęciu sesji, zapisz je w zmiennych lokalnych do późniejszego wykorzystania i zamknij sesję (co oznacza zamknięcie sesji za pomocą funkcji session_write_close, a nie niszczenie jej za pomocą session_destroy ).

W naszym przykładzie oznacza to, że zaraz po otwarciu sesji, sprawdzeniu jej czasu życia i obecności uprawnionego użytkownika, musimy odczytać i zapisać wszystkie dodatkowe zmienne sesyjne wymagane przez aplikację (jeśli takie istnieją), a następnie zamknąć sesję wywołując metodę session_write_close() i kontynuuj wykonywanie skryptu, niezależnie od tego, czy jest to długa ankieta, czy zwykłe żądanie.

Ochrona sesji przed nieuprawnionym użyciem Wyobraźmy sobie sytuację. Jeden z Twoich użytkowników dostaje trojana, który kradnie pliki cookie przeglądarki (w których przechowywana jest nasza sesja) i wysyła je na podany adres e-mail. Osoba atakująca uzyskuje plik cookie i wykorzystuje go do sfałszowania żądania w imieniu naszego autoryzowanego użytkownika. Serwer pomyślnie akceptuje i przetwarza to żądanie tak, jakby pochodziło od autoryzowanego użytkownika. Jeżeli nie zostanie wdrożona dodatkowa weryfikacja adresu IP, taki atak doprowadzi do skutecznego włamania się na konto użytkownika ze wszystkimi tego konsekwencjami.

Dlaczego było to możliwe? Oczywiście dlatego, że nazwa i identyfikator sesji są zawsze takie same przez cały czas trwania sesji, a jeśli otrzymasz te dane, możesz łatwo wysłać żądania w imieniu innego użytkownika (oczywiście w trakcie trwania tej sesji). Może nie jest to najczęstszy rodzaj ataku, ale teoretycznie wydaje się całkiem wykonalny, zwłaszcza biorąc pod uwagę, że taki trojan nie potrzebuje nawet uprawnień administratora, aby okraść pliki cookie przeglądarki użytkownika.

Jak można się chronić przed tego typu atakami? Znowu oczywiście poprzez ograniczenie czasu życia identyfikatora sesji i okresową zmianę identyfikatora w ramach tej samej sesji. Nazwę sesji możemy także zmienić całkowicie usuwając starą i tworząc nową, kopiując do niej wszystkie zmienne sesji ze starej. Nie wpływa to jednak na istotę podejścia, dlatego dla uproszczenia ograniczymy się jedynie do identyfikatora sesji.

Oczywiste jest, że im krótszy czas życia identyfikatora sesji, tym mniej czasu osoba atakująca będzie musiała uzyskać i wykorzystać pliki cookie w celu sfałszowania żądania użytkownika. W idealnym przypadku dla każdego żądania należy zastosować nowy identyfikator, co zminimalizuje możliwość wykorzystania sesji innej osoby. Rozważymy jednak ogólny przypadek, gdy czas regeneracji identyfikatora sesji jest ustawiony dowolnie.

(Pominiemy część kodu, która została już omówiona).

Funkcja startSession($isUserActivity=true) ( ​​\/ Czas życia identyfikatora sesji $idLifetime = 60; ... if ($idLifetime) ( // Jeśli ustawiony jest czas życia identyfikatora sesji, // sprawdź czas, jaki upłynął od sesji utworzone lub ostatnia regeneracja // (czas ostatniego żądania, kiedy zaktualizowano czas rozpoczęcia zmiennej sesji) if (isset($_SESSION["starttime"])) ( if ($t-$_SESSION["starttime"] >= $ idLifetime) ( // Czas wygaśnięcia identyfikatora sesji // Wygeneruj nowy identyfikator session_regenerate_id(true); $_SESSION["starttime"] = $t; ) ) else ( // Dotrzemy tutaj, jeśli sesja właśnie się zakończyła został utworzony // Ustaw czas generowania identyfikatora sesji na aktualny czas $_SESSION["starttime"] = $t; ) ) return true; )

Zatem tworząc nową sesję (co następuje, gdy użytkownik pomyślnie się zaloguje) ustawiamy zmienną sesyjną starttime, która przechowuje dla nas czas ostatniej generacji identyfikatora sesji, na wartość równą aktualnemu czasowi serwera. Następnie w każdym żądaniu sprawdzamy, czy od ostatniej generacji identyfikatora upłynęła wystarczająca ilość czasu (idLifetime) i jeśli tak, generujemy nowy. Tym samym, jeśli w ustawionym okresie życia identyfikatora atakujący, który otrzymał plik cookie uprawnionego użytkownika, nie będzie miał czasu go wykorzystać, fałszywe żądanie zostanie uznane przez serwer za nieautoryzowane, a atakujący zostanie przeniesiony na stronę logowania .

Uwaga: nowy identyfikator sesji trafia do pliku cookie przeglądarki po wywołaniu funkcji session_regenerate_id(), która wysyła nowy plik cookie, podobnie jak funkcja session_start(), więc nie musimy samodzielnie aktualizować pliku cookie.

Jeśli chcemy, aby nasze sesje były jak najbardziej bezpieczne, wystarczy ustawić czas życia identyfikatora na jeden lub nawet usunąć z nawiasów funkcję session_regenerate_id() i usunąć wszelkie kontrole, co doprowadzi do regeneracji identyfikatora w każdym wniosek. (Nie testowałem wpływu tego podejścia na wydajność i mogę tylko powiedzieć, że funkcja session_regenerate_id(true) w zasadzie wykonuje tylko 4 akcje: wygenerowanie nowego identyfikatora, utworzenie nagłówka z plikiem cookie sesji, usunięcie starego i utworzenie nowy plik sesji).

Dygresja liryczna: Jeśli trojan okaże się na tyle sprytny, że nie wysyła atakującemu plików cookie, ale organizuje wysłanie wcześniej przygotowanego fałszywego żądania natychmiast po otrzymaniu pliku cookie, opisana powyżej metoda najprawdopodobniej nie będzie w stanie tego zrobić chronić przed takim atakiem, ponieważ pomiędzy momentem otrzymania przez trojana pliku cookie a wysłaniem fałszywego żądania praktycznie nie będzie różnicy i istnieje duże prawdopodobieństwo, że w tym momencie identyfikator sesji nie zostanie ponownie wygenerowany.

Możliwość jednoczesnej pracy w jednej przeglądarce na rzecz kilku użytkowników Ostatnim zadaniem, które chciałbym rozważyć, jest możliwość jednoczesnej pracy w jednej przeglądarce przez kilku użytkowników. Funkcja ta jest szczególnie przydatna na etapie testów, gdy trzeba emulować jednoczesną pracę użytkowników i warto to robić w ulubionej przeglądarce, zamiast korzystać z całego dostępnego arsenału lub otwierać kilka instancji przeglądarki w trybie incognito .

W naszych poprzednich przykładach nie określiliśmy jawnie nazwy sesji, więc użyliśmy domyślnej nazwy PHP (PHPSESSID). Oznacza to, że wszystkie sesje, które do tej pory utworzyliśmy, wysłały do ​​przeglądarki plik cookie o nazwie PHPSESSID. Oczywiście, jeśli nazwa pliku cookie jest zawsze taka sama, nie ma możliwości zorganizowania dwóch sesji o tej samej nazwie w tej samej przeglądarce. Ale gdybyśmy użyli własnej nazwy sesji dla każdego użytkownika, problem zostałby rozwiązany. Zróbmy to.

Funkcja startSession($isUserActivity=true, $prefix=null) ( ... if (session_id()) return true; // Jeśli w parametrach zostanie przekazany prefiks użytkownika, // ustaw unikalną nazwę sesji zawierającą tę prefiks, // w przeciwnym razie ustaw wspólną nazwę dla wszystkich użytkowników (na przykład MÓJPROJEKT) nazwa_sesji("MÓJPROJECT".($prefiks ? "_".$prefiks: "")); ini_set("session.cookie_lifetime", 0); if (! session_start()) zwróć fałsz; ... )

Teraz pozostaje tylko upewnić się, że skrypt wywołujący przekazuje unikalny przedrostek dla każdego użytkownika do funkcji startSession(). Można to zrobić na przykład poprzez przekazanie prefiksu w parametrach GET/POST każdego żądania lub poprzez dodatkowy plik cookie.

Podsumowanie Na zakończenie przedstawię kompletny, końcowy kod naszych funkcji do pracy z sesjami PHP, obejmujący wszystkie omówione powyżej zadania.

Funkcja startSession($isUserActivity=true, $prefix=null) ( $sessionLifetime = 300; $idLifetime = 60; if (session_id()) return true; nazwa_sesji("MÓJPROJEKT".($prefiks ? "_".$prefiks: "")); ini_set("session.cookie_lifetime", 0); if (! session_start()) return false; $t = time(); if ($sessionLifetime) ( if (isset($_SESSION["lastactivity"] ) && $t-$_SESSION["lastactivity"] >= $sessionLifetime) (zniszczSession(); return false; ) else ( if ($isUserActivity) $_SESSION["lastactivity"] = $t; ) ) if ($idLifetime ) ( if (isset($_SESSION["starttime"])) ( if ($t-$_SESSION["starttime"] >= $idLifetime) ( session_regenerate_id(true); $_SESSION["starttime"] = $t; ) ) else ( $_SESSION["starttime"] = $t; ) ) return true; ) funkcjazniszczSession() ( if (session_id()) ( session_unset(); setcookie(nazwa_sesji(), identyfikator_sesji(), time() -60*60*24); session_destroy(); ) )

Mam nadzieję, że ten artykuł oszczędzi trochę czasu tym, którzy nigdy nie zagłębiali się zbytnio w mechanizm sesji, a pozwoli na wystarczający wgląd w ten mechanizm tym, którzy dopiero zaczynają zapoznawać się z PHP.

Czy potrzebujesz nazwy użytkownika i hasła?

Aby móc przesyłać artykuły online i sprawdzać status przesłanych artykułów, należy zarejestrować się i zalogować na swoje konto.

Lista kontrolna przygotowania artykułu do przesłania

W ramach procesu zgłaszania artykułu autorzy muszą sprawdzić, czy ich artykuł spełnia wszystkie poniższe punkty; artykuły mogą zostać zwrócone autorom, jeśli nie spełniają tych wymagań.

Artykuł został przygotowany zgodnie z wymogami

Warunki przeniesienia praw autorskich

Autorzy zachowują prawa autorskie do utworu i przyznają wraz z utworem prawa do pierwszej publikacji czasopisma, jednocześnie udzielając mu licencji na warunkach licencji Creative Commons Uznanie autorstwa, która umożliwia innym rozpowszechnianie tego dzieła z obowiązkowym podaniem autora dzieła i linkiem do oryginalnej publikacji w tym czasopiśmie.

Oświadczenie o ochronie prywatności

Imiona i nazwiska oraz adresy e-mail wprowadzone na stronie internetowej niniejszego magazynu będą wykorzystywane wyłącznie do celów wyznaczonych przez niniejsze czasopismo i nie będą wykorzystywane w żadnym innym celu ani udostępniane jakiejkolwiek innej osobie lub podmiotowi.

Użytkownik przed rejestracją w systemie wyraża zgodę na politykę przetwarzania i przechowywania danych osobowych.

Płatności autorskie

1500 znaków ze spacjami: 300,00 (RUB)

Publikacja 1 strony rękopisu (1500 znaków) – 300 rubli. Materiały graficzne / stoły płatne osobno - 50 rubli / 1 sztuka. Kopia autorska, łącznie z wysyłką na terenie Rosji, płatna na życzenie autora - 400 rubli. Wysyłka za granicę - 800 rubli. Koszt przesłania zaświadczenia o przyjęciu materiału do publikacji wynosi 150 rubli.

Tłumaczenie informacji towarzyszących (imię i nazwisko, miejsce pracy autorów; tytuł; streszczenie; słowa kluczowe) na język angielski 0,5 rubla za każdy znak ze spacjami.

Uwaga! Autorzy (kandydaci i doktorzy nauk), którzy według elibrary.ru mają 300 lub więcej cytowań (udział samocytowań nie powinien przekraczać 30%), są publikowani bezpłatnie. Jeśli kwalifikujesz się do bezpłatnej publikacji, przy nadsyłaniu materiału w polu komentarza podaj link do swojego profilu bibliotecznego z liczbą cytowań. Koszty wysyłki kolekcji płatne są osobno.

Bezpieczeństwo serwisu opiera się na zarządzaniu sesjami. Gdy użytkownik łączy się z bezpieczną witryną, podaje dane uwierzytelniające, zwykle w postaci nazwy użytkownika i hasła. Serwer WWW nie ma pojęcia, który użytkownik jest już zalogowany i w jaki sposób porusza się ze strony na stronę. Mechanizm sesji zapobiega konieczności wpisywania hasła za każdym razem, gdy użytkownik chce wykonać nową akcję lub przejść do nowej strony.

Zasadniczo zarządzanie sesją zapewnia, że ​​aktualnie podłączony użytkownik jest tym, który został uwierzytelniony. Niestety, sesje stały się oczywistym celem hakerów, ponieważ umożliwiają dostęp do serwera internetowego bez konieczności uwierzytelniania.

Po uwierzytelnieniu użytkownika serwer WWW udostępnia mu identyfikator sesji. Identyfikator ten jest przechowywany w przeglądarce i jest zastępowany, gdy wymagane jest uwierzytelnienie. Pozwala to uniknąć powtarzających się procesów wprowadzania loginu/hasła. Wszystko to dzieje się w tle i nie powoduje dyskomfortu dla użytkownika. Wyobraź sobie, że za każdym razem, gdy przeglądasz nową stronę, wprowadzasz swoją nazwę użytkownika i hasło!

W tym artykule postaram się opisać wszystkie znane mi sposoby ochrony identyfikatora sesji w PHP.

Korzystanie z plików cookie Domyślnie wszystkie informacje o sesji, w tym identyfikator, są wysyłane do pliku cookie. Ale nie zawsze tak się dzieje. Niektórzy użytkownicy wyłączają pliki cookie w swoich przeglądarkach. W takim przypadku przeglądarka przekaże identyfikator sesji w adresie URL.

W tym przypadku identyfikator jest przesyłany w postaci zwykłego tekstu, w przeciwieństwie do sesji za pośrednictwem pliku cookie, gdy informacja jest ukryta w nagłówku HTTP. Najprostszym sposobem zabezpieczenia się przed tym byłoby zakazanie przesyłania identyfikatora sesji przez pasek adresu. Można tego dokonać wpisując następujący zapis w pliku konfiguracyjnym .htaccess serwera Apache:

Php_flag session.use_only_cookies włączone

Korzystanie z szyfrowania Jeśli Twoja witryna musi przetwarzać poufne informacje, takie jak numery kart kredytowych (witaj, Sony), powinieneś używać szyfrowania SSL3.0 lub TSL1.0. Aby to zrobić, ustawiając plik cookie, należy określić wartość true dla parametru secure.

Jeśli przechowujesz hasło sesji w zmiennej $_SESSION (nadal lepiej jest używać sql), to nie powinieneś przechowywać go w postaci zwykłego tekstu.

If ($_SESSION["hasło"] == $hasło użytkownika) ( // kod )

Powyższy kod nie jest bezpieczny, ponieważ hasło jest przechowywane w postaci zwykłego tekstu w zmiennej sesyjnej. Zamiast tego użyj szyfrowania md5, coś takiego:

If ($_SESSION["md5hasło"] == md5($userpass)) ( // kod )

Sprawdzanie przeglądarki Aby uniemożliwić korzystanie z sesji z innej przeglądarki (komputera), należy wprowadzić sprawdzenie pola nagłówka HTTP user-agent:

Początek_sesji(); if (isset($_SESSION["HTTP_USER_AGENT"])) ( if ($_SESSION["HTTP_USER_AGENT"] != md5($_SERVER["HTTP_USER_AGENT"])) ( // kod ) ) else ( $_SESSION["HTTP_USER_AGENT" ] = md5($_SERVER["HTTP_USER_AGENT"]); )

Wygaśnięcie sesji Ogranicz czas życia sesji, a także czas wygaśnięcia plików cookie. Domyślny czas trwania sesji wynosi 1440 sekund. Możesz zmienić tę wartość poprzez php.ini i .htaccess. Przykład dla .htaccess:

# Czas życia sesji w sekundach
php_value session.gc_maxlifetime 3600
# Czas życia pliku cookie w sekundach
php_value session.cookie_lifetime 3600

Powiązanie po adresie IP W niektórych sytuacjach (nie zawsze) należy powiązać po adresie IP. Głównie gdy liczba użytkowników jest ograniczona i mają statyczne adresy IP. Kontrola może odbywać się na podstawie listy dozwolonych adresów IP,

Dołącz("ip_list.php"); //$ip_white_list = tablica („admin1” => „111.222.333.444”, „admin2” => „555.666.777.888”); if(!empty(array_search($_SERVER["REMOTE_ADDR"],$ip_white_list))) ( header("Lokalizacja: admin.php"); ) else ( echo "ODMOWA DOSTĘPU!"; )

Lub według adresu IP dla każdego żądania (tylko w przypadku statycznego adresu IP):

If(isset($_SESSION["ip"]) i $_SESSION["ip"] == $_SERVER["REMOTE_ADDR"]) ( header("Lokalizacja: admin.php"); ) else ( session_unset(); $ _SESSION["ip"] = $_SERVER["REMOTE_ADDR"]; )

Należy mieć świadomość, że hackowania nie da się całkowicie uniknąć. Możesz jedynie utrudnić ten hack w dowolny znany sposób. Nie należy jednak zapominać również o swoich legalnych użytkownikach, aby nie komplikować im życia taką ochroną.

Ten artykuł powstał w 2009 roku i pozostaje jednym z naszych najpopularniejszych postów. Jeśli chcesz dowiedzieć się więcej o PHP i MySQL, może to Cię bardzo zainteresować.

UWAGA: ten artykuł został niedawno zaktualizowany, aby działał w PHP 4.2 lub nowszym!

Ostatnio miałem okazję pracować z grupą ludzi nad małym projektem. Na początku ustaliliśmy, że sam e-mail nie wystarczy, aby informować wszystkich na bieżąco, więc powierzono mi zadanie zbudowania małej witryny internetowej dla projektu. Zawierałaby prostą tablicę ogłoszeń, miejsce, w którym moglibyśmy przesyłać dokumenty i inne pliki do wykorzystania przez resztę zespołu, a także dane kontaktowe poszczególnych członków zespołu.

Wiedziałem, że aby wiele z tych funkcji działało, użytkownicy będą musieli się zalogować, zanim uzyskają dostęp do odpowiednich części witryny. Potrzebowałem systemu, który umożliwiłby użytkownikom zarejestrowanie się w celu uzyskania identyfikatora użytkownika w celu uzyskania dostępu do witryny, a następnie natychmiastowego wykorzystania tego identyfikatora bez żadnej interwencji z mojej strony.

W tym artykule przedstawię przegląd systemu, który opracowałem, zaczynając od pierwszej połowy procesu rejestracji użytkownika. W drugiej połowie skupię się na samej witrynie, na tym, jak wymaga od użytkowników zalogowania się, a następnie utrzymuje ten status zalogowania przez cały czas ich wizyty. Szczególną uwagę zwrócę na wykorzystanie funkcji zarządzania sesjami w PHP. Na koniec powinieneś mieć wszystkie informacje potrzebne do wdrożenia własnego, podobnego systemu.

W całym artykule zakładam, że masz podstawową wiedzę na temat języka PHP, korzystania z formularzy do przesyłania informacji do skryptu PHP oraz tego, jak PHP może być używane do interakcji z bazą danych MySQL. Jeżeli którekolwiek z tych pojęć jest dla Ciebie obce, powinieneś zacząć od przeczytania mojego poprzedniego artykułu pt.

Część pierwsza: Proces rejestracji Formularz rejestracji

Naturalnym miejscem do rozpoczęcia tworzenia witryny, która będzie wymagała od użytkowników rejestracji w celu uzyskania dostępu, jest sam proces rejestracji. Jak można się spodziewać, prosty formularz internetowy załatwi sprawę. Oto jak to będzie wyglądać:

A oto kod tego formularza:




Rejestracja nowego użytkownika



Formularz rejestracji nowego użytkownika

* wskazuje wymagane pole


Gdy cel jest już jasny, przeprowadzę Cię przez kod accesscontrol.php. Zacznij od dołączenia dwóch przydatnych plików dołączanych: