Wszystkie trzy zadania doczekały się poprawnej odpowiedzi. Zwycięzcą jest Michał Majchrowicz, części z Was zapewne znany z zainteresowania tematyką XSS.
Rozwiązania:
W pierwszym skrypcie błąd polega na użyciu operatora porównania bez kontroli typu przy porównywaniu liczby całkowitej i łańcucha znaków (warunek “1a”==1 ma wartość true). Metoda zabezpieczenia to rzutowanie parametru id na typ integer, lub porównanie z kontrolą typu. Trzeba przy tym pamiętać, że wszystkie dane w $_GET, $_POST, $_COOKIE itp. są zawsze typu string lub array.
Drugi przypadek to zastosowanie do filtrowania danych funkcji ereg(), która nie jest bezpieczna dla danych binarnych. Oznacza to, że bajt zerowy potraktuje jako koniec łańcucha. Do niezaufanych danych należy stosować funkcję preg_match().
Ostatni skrypt nie bierze pod uwagę błędu, jaki popełnili twórcy Internet Explorera 6. Ta przeglądarka w przypadku kodowania UTF-8 traktuje bajty powyżej 192 jako początek wielobajtowego znaku, niezależnie od tego, czy następny bajt jest faktycznie kontynuacją tego znaku. Dzięki temu można usunąć cudzysłów i spowodować, że tag nie zostanie zamknięty. Rozwiązaniem może być stosowanie htmlspecialchars()+ENT_QUOTES zamiast/oprócz strip_tags(), oraz usuwanie nieprawidłowych znaków UTF-8 przy pomocy $zmienna=mb_convert_encoding($zmienna,”UTF-8″,”UTF-8″). Wady tych rozwiązań:
- nawet stosując wszędzie htmlspecialchars()+ENT_QUOTES można być podatnym
- dodanie mb_convert_encoding() we wszystkich danych wyjściowych to sporo roboty; z kolei dodanie tej funkcji globalnie do wszystkich danych przychodzących to zabójstwo dla wydajności
Dodatkowo wszystkie trzy skrypty są podatne na ujawnienie ścieżki do pliku w komunikatach błędów, co słusznie zauważył jeden z uczestników.
Kilka luźnych wniosków:
Wszelkie komentarze oczywiście mile widziane.
Drugie wejście jest super ciekawe. Nie przypuszczałem, że %00 będzie traktowane jako koniec stringu dla erega. A jak zachowają się wyrażenia regularne perlowskie?
Funkcja preg_match() potraktuje bajt zerowy w przeszukiwanym ciągu jak normalny znak, więc jest pod tym względem znacznie bezpieczniejsza. Nie można przy pomocy bajtu zerowego obejść filtrów bazujących na perlowych wyrażeniach regularnych.
Gdyby bajt zerowy trafił jakoś do wzorca, zostałby jednak potraktowany jako koniec tego wzorca. Nie wiem, dlaczego funkcja preg_quote() jest bezpieczna dla danych binarnych, a pierwszy argument preg_match() nie - moim zdaniem nie ma to sensu. Na szczęście taka sytuacja właściwie nie ma miejsca w praktyce. Jeżeli dane użytkownika trafiają z jakiegoś powodu do wzorca, to bardziej należy się martwić o to, żeby nie istniała możliwość dołączenia modyfikatora “e” w preg_replace(), niż o bajt zerowy.
W ogóle to ja nie wiem, czy programiście IE popełnili błąd, czy programiści FF? W końcu 0xc0,0×22, to jest jakiś znak utf-8. Dlaczego Firefox tego nie przetwarza? Dlaczego przeglądarki nie obsługują wszystkich 6 postaci każdego znaku w UTF-8?
Zachowanie Firefoksa, Opery, IE7 itd. jest poprawne. 0xC1 to początek dwubajtowej sekwencji UTF-8, ale kolejny bajt musi się w takiej sekwencji zaczynać od bitu 1, czyli mieć wartość przynajmniej 0×80. Bajty 0×01-0xBF mogą występować tylko w jednobajtowych znakach, dzięki czemu znaki UTF-8 z tego zakresu są jednocześnie poprawnymi znakami ASCII.
Teoretycznie 0×22 można zapisać też jako 0xC0 0xA2, ale dodatkowa zasada mówi o tym, że każdy znak musi być zapisany na minimalnej ilości bajtów, na której jest to możliwe. Nie powinno się interpretować nadmiarowych sekwencji - prowadziłoby to do niezliczonej ilości luk. Znaleziono wiele bardzo grożnych błędów w oprogramowaniu nie stosującym tej zasady - najbardziej znana jest chyba poniższa:
http://www.securityfocus.com/bid/1806/exploit
W związku z tą dodatkową zasadą, żadna poprawna sekwencja UTF-8 nie zaczyna się od bajtu 0xC0.
No wszystko jest jasne, oprocz jednego :)
W taki wypadku po co w ogole w utf-8 jest mozliwosc zapisu czegokolwiek w 6 formatach skoro nikt tego nie uzyje?