Preorder drugiego tomu książki sekuraka: Wprowadzenie do bezpieczeństwa IT. -15% z kodem: sekurak-book
Atak Race Condition – przykład zastosowania w aplikacji webowej
Wykorzystanie Race Condition, to metoda ataku polegająca na wykonaniu zapytania w czasie krótszym niż trwa weryfikacja warunków danej akcji aplikacji, np. podczas wgrywania plików na serwer, czas między zapisaniem pliku na dysku a weryfikacją jego typu czy rozszerzenia, pozwala na wykonanie zapytania do zapisanego zasobu. Taki proces, opisany w pseudo języku, wyglądałby następująco:
WgrajPlik ( Zasob ); JEZELI ( SprawdzRozszerzenie ( Zasob ) == ‘JPG’ ) TO PokazKomunikat ( ‘Wgrano ZASOB’ ); W PRZECIWNYM WYPADKU UsunPlik ( Zasob ); PokazKomunikat ( ‘Zasob posiada niedozwolone rozszerzenie’ ); KONIEC JEŻELI;
Jak łatwo zauważyć w powyższym przykładzie, wgrywany jest najpierw plik, a dopiero później następuje weryfikacja jego rozszerzenia. Czas jakiego skrypt potrzebuje do zweryfikowania rozszerzenia typu pliku i w przypadku jego niezgodności skasowania go, pozwala na odwołanie się do pliku, do momentu kiedy jeszcze istnieje.
Jak przeprowadzić tego typu atak?
Należy symultanicznie wgrywać plik na serwer oraz odwoływać się do wgranego zasobu.
W celach demonstracyjnych wykonaliśmy prosty kod upload’u w języku PHP.
<!DOCTYPE html> <html> <body> <form method="post" enctype="multipart/form-data"> Select image to upload: <input type="file" name="fileToUpload" id="fileToUpload"> <input type="submit" value="Upload Image" name="submit"> </form> <?php $target_dir = "uploads/"; $target_file = $target_dir . basename(@$_FILES["fileToUpload"]["name"]); if(isset($_POST["submit"])) { $finfo = finfo_open(FILEINFO_MIME); $mime = explode(";", finfo_file($finfo, $_FILES["fileToUpload"]["tmp_name"]))[0]; finfo_close($finfo); if($mime == "image/png" || $mime == "image/gif" || $mime == "image/jpg") { move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file); $ext = strtolower(pathinfo($target_file,PATHINFO_EXTENSION)); if($ext == "png" || $ext == "gif" || $ext == "jpg") { echo "File uploaded - $target_file"; } else { echo "File extension error - $target_file"; unlink($target_file); } } else { echo "File content type error - $target_file"; } } ?> </body> </html>
Załóżmy, że wyżej przedstawiony kod zapiszemy na stacji lokalnej jako „upload.php”.
Kod może wydawać się bezpieczny, jednak tylko na pierwszy rzut oka, ponieważ pomimo weryfikacji rozszerzenia pliku, a nawet jego typu – jest to po prostu weryfikowane w złej kolejności.
Aby wykonać atak na w/w aplikację należy równocześnie wysyłać setki zapytań (upload, wywołanie skryptu).
Zapytanie wgrywające plik:
Kod PHP został celowo wkomponowany w fragment obrazka PNG, w celu ominięcia filtrów „mime”.
Zapytanie wywołujące akcję z naszym shell’em (wersja dla OS Linux):
http://localhost/uploads/shellimg.php?command=cp%20shellimg.php%20shell.php
Co dalej?
Teraz wystarczy uruchomić odpowiedni skrypt lub iteracje Intruderem w BURP/ZAP, który wykona za nas setki prób wgrania pliku oraz wywołania akcji w naszym shell’u, mającej na celu skopiowanie samego siebie do bezpiecznego miejsca, w celu dalszej exploitacji.
W momencie, gdy skrypt sprawdzający wartość rozszerzenia nie zdąży skasować złośliwego pliku, a nasz program wywoła akcję kopiowania, uzyskamy dostęp do stabilnej już wersji shell’a pod adresem:
http://localhost/uploads/shell.php
Jak się zabezpieczyć?
Istotne jest aby wszelkie testy dotyczące bezpieczeństwa zawartości danego pliku odbywały się, zanim zostanie on wgrany do publicznego katalogu.
Bezpieczna wersja kodu (nie uwzględniając ataku XSS ;) ):
<!DOCTYPE html> <html> <body> <form method="post" enctype="multipart/form-data"> Select image to upload: <input type="file" name="fileToUpload" id="fileToUpload"> <input type="submit" value="Upload Image" name="submit"> </form> <?php $target_dir = "uploads/"; $target_file = $target_dir . basename(@$_FILES["fileToUpload"]["name"]); if(isset($_POST["submit"])) { $finfo = finfo_open(FILEINFO_MIME); $mime = explode(";", finfo_file($finfo, $_FILES["fileToUpload"]["tmp_name"]))[0]; finfo_close($finfo); if($mime == "image/png" || $mime == "image/gif" || $mime == "image/jpg") { $ext = strtolower(pathinfo($_FILES["fileToUpload"]["tmp_name"],PATHINFO_EXTENSION)); if($ext == "png" || $ext == "gif" || $ext == "jpg") { move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file); echo "File uploaded - $target_file"; } else { echo "File extension error - $target_file"; } } else { echo "File content type error - $target_file"; } } ?> </body> </html>
Robert Kruczek, pentester w Securitum.
Knock Knock
Race condition
Who’s There?
Z softu PHPwego, z którym miałem styczność – większość miała w swoim odpowiedniku katalogu „upload” plik .htaccess blokujący jakikolwiek dostęp albo katalog „upload” był poza drzewem widzianym przez serwer http://WWW...
Oczywiście, podany przykład nie zakłada dodatkowych zabezpieczeń w postaci blokady dostępu do katalogu, do którego zostanie przekazany plik.
Jednakże, nawet w takiej sytuacji – gdy istnieje podatność LFI – możemy skorzystać z Race Condition.
Ale jak się to może wykonać w ten sposób, skoro w większości konfiguracji katalog tymczasowy na wysyłane pliki jest poza document_root (zwykle gdzieś pod /tmp)? Jedyną słabość, jaką tutaj dostrzegam, to brak filtrowania wysyłanych plików pod kątem kodu, ale to się wykona tylko przy beznadziejnej konfiguracji httpd (gdy interpreter odpala również wszystkie pliki statyczne).
Artykuł mocno naciągany, powiedziałbym że nawet bardzo.
O ile dobrze rozumiem, to chodzi o to że własnie plik został uploadowany do właściwego folderu.
Na 1 listingu z kodem, sprawdzany jest tylko mime type (dlatego plik .php ma również kod określający go jako .png), nastomiast nie sprawdzane jest rozszerzenie. Po tym już plik wędruje do publicznego folderu, a dopiero tam sprawdzane jest rozszerzenie i ewentualne usuwanie go. Wtedy następuje próba uruchomienia takiego pliku.
Na 2 listingu jest już to zrobione inaczej ;)
W PHP przecież kod też jest wykonywany z góry do dołu; move_uploaded_file jest w najbardziej zagnieżdżonym warunku, więc jakim cudem sprawdzenie ma nastąpić „po”?
http://php.net/manual/en/ini.core.php#ini.upload-tmp-dir – jeśli nie jest sprecyzowane, to katalog domyślny. Nie pamiętam, żebym kiedykolwiek widział, aby ta wartość była zmieniona na coś innego, niż katalog tymczasowy znajdujący się poza zasięgiem httpd.
tu wlasnie chodzi o przypadek, kiedy plik już jest poza tmp, bo autor skryptu nie myśli
w pseudokodzie oznaczone jako WgrajZasob (pierwsza linijka)
poniżej w kodzie php masz to nawet dokładnie pokazane.
może pierwsze czytaj artykuł, potem komentuj?
+1
Wcale że nie jest bezpieczny na pierwszy rzut oka bo nie weryfikuje tylko rozszerzenie po ostatniej kropce
`symultanicznie` — co nie jest jest ze słowem `jednocześnie`? Ani to krótsze, ani bardziej zrozumiałe, ani wygodniej się nie pisze. Nawet w korpomowie się z tego nie korzysta.
Kiedy na co dzień siedzisz po kilka godzin nad angielskojęzycznymi dokumentami to normalne, że zaczynasz wyłapywać takie słówka i stosować je pisząc w języku polskim
Ze SJP, patrz pierwsze znaczenie:
symultaniczny
1. «odbywający się jednocześnie»
2. «mający związek z tłumaczeniem z języka obcego dokonywanym na żywo»
3. «oparty na zasadach symultanizmu w teatrze, malarstwie lub literaturze»
Bardzo naciagany przypadek, ktorego spotkanie w prawdziwym zyciu graniczy z cudem
Gwarantuję że nie :)
Przypadek może i lekko naciągany, ale problem realny.
Często okazuje się, że strona nie musi być wcale celowo atakowana. Wystarczy zwiększony ruch lub większe obciążenie serwera/ów..
„Lekko naciągany” – każdemu zdaża się mieć gorszy dzień – %:)
W poprawnym kodzie powinno być:
$ext = strtolower(pathinfo($_FILES[„fileToUpload”][„name”],PATHINFO_EXTENSION));
To ciekawe, że zawsze jak się trafi artykuł na trochę łatwiejszy temat to nagle w komentarzach jest wysyp specjalistów :D Każdy mistrz PHPa :D