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

12 września 2016, 07:44 | Teksty | komentarzy 17

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:
Rys. 1 Zapytanie wgrywające plik

Rys. 1 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.

Spodobał Ci się wpis? Podziel się nim ze znajomymi:



Komentarze

  1. Knock Knock
    Race condition
    Who’s There?

    Odpowiedz
  2. Pawel

    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...

    Odpowiedz
    • Robert

      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.

      Odpowiedz
  3. eRIZ

    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.

    Odpowiedz
    • Łukasz

      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 ;)

      Odpowiedz
      • eRIZ

        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.

        Odpowiedz
    • dzek

      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?

      Odpowiedz
  4. cola czy pepsi

    +1

    Odpowiedz
  5. fakit

    Wcale że nie jest bezpieczny na pierwszy rzut oka bo nie weryfikuje tylko rozszerzenie po ostatniej kropce

    Odpowiedz
  6. dzek

    `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.

    Odpowiedz
    • Tomek

      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

      Odpowiedz
    • Pawel

      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»

      Odpowiedz
  7. Perkun

    Bardzo naciagany przypadek, ktorego spotkanie w prawdziwym zyciu graniczy z cudem

    Odpowiedz
    • Gwarantuję że nie :)

      Odpowiedz
    • Deus

      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ń – %:)

      Odpowiedz
  8. Seba

    W poprawnym kodzie powinno być:
    $ext = strtolower(pathinfo($_FILES[„fileToUpload”][„name”],PATHINFO_EXTENSION));

    Odpowiedz
  9. DaBomb

    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

    Odpowiedz

Odpowiedz