Translate: 
EnglishFrenchGermanItalianPolishPortugueseRussianSpanish

Code injection czyli prosty wirus PHP zapisany w obrazku JPEG

Pracując jako programista i administrator usług w firmie hostingowej spotkałem się wielokrotnie z różnymi skryptami PHP obsługującymi galerie zdjęć lub download plików. O ile zazwyczaj mechanizm był podobny (polecenia typu file_get_contents(), fopen(), readfile()), to czasami zdarzały się mniej tradycyjne implementacje.

Do napisania tego artykułu natchnął mnie jeden użytkownik serwera, który zgłosił się do mnie z problemem wysyłania plików poprzez skrypt PHP. Niektóre pliki na jego stronie udawało się wysłać w całości, niektóre nie. Nie potrafił jednak dotrzeć do sedna sprawy.

Po krótkich oględzinach okazało się, że napisany przez niego skrypt oprócz pożądanej funkcjonalności posiadał także dodatkową: dzięki użyciu polecenia include() do odczytu obrazków, był podatny na code injection w języku PHP.

Jak to działa?

Wyobraźmy sobie źle napisany kawałek kodu PHP odpowiedzialny za pobranie obrazka z dysku i przesłanie do przeglądarki użytkownika:

// [...]
if(preg_match('~^[a-z_\.0-9]+\.(jp[e]?g|png|gif)$~i', $filename)) {
     require($filename);
}
// [...]

Skrypt po zwalidowaniu nazwy pliku próbuje wyświetlić go w przeglądarce użytkownika poleceniem require(). W większości przypadków operacja odczytu zakończy się sukcesem, jednak przy niektórych plikach okaże się, że pobrane dane są niekompletne, przez co niemożliwe do podejrzenia w programie graficznym.

Zagadkę wyjaśni dopiero zbadanie zawartości pobranego pliku przy pomocy edytora tekstowego. Jeśli w pliku php.ini zostało włączone raportowanie błędów PHP (display_errors = on), na końcu binarnej treści pliku znajdziemy błąd krytyczny języka PHP informujący o problemie z parsowaniem pliku PHP.

Tutaj z pomocą przychodzi nam dokumentacja języka PHP.

Pliki odczytane funkcjami require() oraz include() traktowane są jako zwykły tekst/binaria do czasu, aż interpreter nie odnajdzie w nich ciągu o treści <?php. Od tego miejsca zaczyna się interpretowanie pliku jako skryptu PHP oraz wykonywanie poleceń w nim zawartych.

Łatwo zgadnąć, że w plikach binarnych istnieje (niewielka) szansa przypadkowego napotkania ciągu takich liter. Stwarza to ryzyko powstania krytycznego błędu, a nawet wstrzyknięcia kodu przez osoby trzecie.

Jak to wykorzystać?

Lista dostępnych metod ataku uzależniona jest oczywiście od poziomu zabezpieczeń skryptu. Najprostszym sposobem jest przesłanie zwykłego pliku z kodem PHP zapisanym z docelowym rozszerzeniem obrazka/filmu/pliku do ściągnięcia (np. wirus.jpg).

Wiele aplikacji podczas uploadu plików na serwer sprawdza jednak nie tylko rozszerzenia plików ale także jego wewnętrzny format (np. pobierając wymiary obrazka funkcją getimagesize())).

W takim przypadku nie można ograniczyć się do zmiany rozszerzenia pliku w celu jego uruchomienia (np. z wirus.php na wirus.jpg), gdyż skrypt wychwyci zły format pliku podczas jego obróbki. Na (nie-)szczęście większość plików binarnych może w swojej treści zawierać pełen kod PHP bez ryzyka utraty zgodności ze standardem w jakim zostały utworzone.

Prosty wirus PHP krok po kroku

O ile opisany przeze mnie mechanizm można wykorzystać w wielu różnych formatach plików, to w poniższym przykładzie ograniczę się do przedstawienia ataku na źle zabezpieczoną galerię.

Do przeprowadzenia code injection na taki skrypt wystarczy dowolny obrazek JPEG, prosty edytor tagów EXIF oraz niewielka znajomość języka PHP.

Za obrazek wybrałem sobie standardowe logo PHP, a do edycji danych EXIF posłużyłem się tym programem.

Najpierw otwieram plik z grafiką:

Następnie dodaję nowy tag (wciskając przycisk z plusem w zielonym kółku), pojawia się wtedy nowe okno edycji:

Z drop-down listy wybieram tag typu DocumentName i jako wartość (value) podaję poniższy kod PHP:

<style>body{font-size: 0;} h1{font-size: 12px !important;}</style><h1><?php echo "<hr />THIS IMAGE COULD ERASE YOUR WWW ACCOUNT, it shows you the PHP info instead...<hr />"; phpinfo(); __halt_compiler(); ?></h1>

Na końcu zapisuję zmiany przyciskiem „Commit change(s)„:

Oto i wynik pracy, plik JPG z ukrytym kodem PHP (można go pobrać i wypróbować):

Od tej pory logo PHP zawiera w sobie kod niewidoczny z poziomu zwykłego edytora grafiki. Po wrzuceniu go do źle zabezpieczonej galerii i próbie jego podejrzenia poleceniem include()/require() naszym oczom ukazuje się taki komunikat:

Podsumowanie

Niniejszy artykuł napisałem jako ciekawostkę, gdyż większość gotowych skryptów w internecie pobiera dane binarne przy pomocy właściwych funkcji. Powyższy przykład pokazuje jednak, jak ważne jest walidowanie danych wejściowych, oraz to, że nawet istniejące mechanizmy zabezpieczające (np. wbudowane w PHP getimagesize()) mogą zostać w prosty sposób oszukane przez odpowiednio spreparowane pliki.

Stworzony w tym artykule plik graficzny nie jest prawdziwym wirusem (nie potrafi się rozprzestrzeniać ani samoistnie zarażać innych plików), lecz może służyć jako zaczątek takiego programu. Opisany mechanizm może zaś być użyty jako nietypowy obfuskator kodu, ukrywający go w plikach binarnych.

Dodaj odpowiedź