Konferencja Mega Sekurak Hacking Party w Krakowie – 26-27 października!
Mikołajki z sekurakiem! od 2 do 8 grudnia!
Konferencja Mega Sekurak Hacking Party w Krakowie – 26-27 października!
Mikołajki z sekurakiem! od 2 do 8 grudnia!
Upload plików zalicza się do najczęściej występujących funkcjonalności w webaplikacjach. Zazwyczaj wiąże się z wgrywaniem na serwer obrazków bądź dokumentów. Jest zarazem miejscem, na które bardzo chętnie patrzą pentesterzy, ze względu na liczne błędy bezpieczeństwa w implementacjach. W tym artykule przedstawimy najczęściej występujące błędy oraz pokażemy, w jaki sposób mogą zostać wykorzystane. Omówimy także sposoby obrony.
Na wszystkie omawiane w tym artykule implementacje będziemy spoglądać z dwóch stron:
Najbardziej podstawowa implementacja, która ciągle pojawia się w webaplikacjach. Zakładamy, że aplikacja zapisuje na serwerze plik o takim samym rozszerzeniu, jakie miał oryginalny plik, a ponadto nie jest sprawdzana zawartość pliku.
Rozważmy kilka możliwych rozwiązań zapisywania pliku po stronie serwera:
Najprostszy możliwy wariant: użytkownik wgrywa plik o nazwie hack.php, który następnie jest dostępny pod adresem np. http://www.example.com/uploads/hack.php. Bezpośrednie odwołanie się do tego URL-a prowadzi prosto do RCE w aplikacji.
Istnieje możliwość zabezpieczenia się przed ww. atakiem, uniemożliwiając wykonywanie plików .php w pojedynczym katalogu. Można to zrobić dyrektywą php_flag engine off w pliku .htaccess lub jednym z plików konfiguracyjnych Apache. Dzięki temu po odwołaniu się bezpośrednio do pliku http://www.example.com/uploads/hack.php zostanie wyświetlone jego źródło, nie zostanie jednak wykonany kod. Skoro jednak atakujący może wysyłać pliki o dowolnych nazwach, może wysłać plik .htaccess o następującej zawartości:
AddHandler application/x-httpd-php .jpeg
Spowoduje to, że pliki o rozszerzeniu .jpeg będą interpretowane przez parser PHP, tym samym obchodząc zabezpieczenie. Dopuszczanie plików o dowolnych rozszerzeniach może również być przydatne w przypadku wystąpienia błędu LFI (Local File Inclusion), jeśli w innym miejscu aplikacji pojawi się kod podobny do poniższego:
<?php include 'includes/'.$_GET['module'].'.php'; ?>
Rozwiązanie, w którym uploadowane pliki znajdują się poza głównym drzewem katalogów dostępnym bezpośrednio przez URLe. Na przykład webroot aplikacji może znajdować się w /var/www, zaś uploady w /var/uploads. W tym przypadku nie zadziała już sztuczka z .htaccess, jednak nadal plik może być przydatny w przypadku LFI.
Czasem programiści decydują się zapisywać plik o takim samym rozszerzeniu jak zostało wysłane, jednak zmieniana jest nazwa pliku. Czyli np. zamiast hack.php na serwerze mógłby zostać zapisany plik 4093b2546a7437d1a2ca3f9b7ac662e3.php. Jeżeli nazwa nowego pliku jest później gdzieś bezpośrednio widoczna, to taki wariant nie różni się od omówionych wcześniej. Jednak nawet jeśli nazwa nie jest bezpośrednio znana, w pewnych przypadkach ataki mogą być możliwe:
eżeli uploadowany plik jest dostępny bezpośrednio przez URL, istnieje szereg rozszerzeń, których wysłanie spowoduje wykonanie XSSa. Najprościej wysłać po prostu plik .html o treści:
<script>alert(1)</script>
XSS-y są możliwe jeszcze przez wiele rozszerzeń plików, które przeglądarka interpretuje jako XML, np. .xml, .xsl, .rss, .svg, .xslt i inne. Payload, który powinien zadziałać dla dowolnego pliku opierającego się o XML, to:
<?xml version="1.0" encoding="UTF-8"?> <html xmlns="http://www.w3.org/1999/xhtml"> <script>alert(1)</script> </html>
Kolejnym sposobem na wykonanie XSS-ów są pliki Flasha (.swf). Flash, poprzez klasę ExternalInterface, może odwoływać się do silnika JS w przeglądarce. Na GitHubie można znaleźć przykładową implementację wykonywania XSSów przez .swf.
Standardową odpowiedzią na powyższe problemy jest wprowadzenie czarnej listy rozszerzeń, innymi słowy: jawne podane rozszerzeń, które nie są dopuszczalne. W przypadku wysłania do serwera pliku o rozszerzeniu z tej listy, odpowiada on komunikatem informującym o niepoprawnym typie. Jakie problemy mogą wiązać się z takim rozwiązaniem?
Autorzy aplikacji nie zawsze są świadomi, które konkretnie rozszerzenia skutkują wykonaniem kodu. Jeśli na czarnej liście znajduje się tylko rozszerzenie .php, być może zabezpieczenie będzie dało się obejść, używając .php5. W przypadku serwera Microsoft IIS – czarną listę składającą się z rozszerzenia .aspx, omijamy przez .asp itp.
Problem opisywany w tym punkcie jest specyficzny dla serwera Apache. Zasadniczo istnieją w nim dwa sposoby na zdefiniowanie obsługi plików .php przez parser tego języka: dyrektywy SetHandler oraz AddHandler. Przykładowy wpis w konfiguracji z użyciem tej drugiej:
AddHandler application/x-httpd-php .php
AddHandler ma jednak swoje specyficzne zachowanie, o którym wspomina dokumentacja:
Filenames may have multiple extensions and the extension argument will be compared against each of them.
Oznacza to, że w przypadku wysłania do serwera pliku test.php.jpg, gdy na serwerze nie zdefiniowano żadnego handlera dla rozszerzeń .jpg (co jest bardzo prawdopodobne), taki plik zostanie wykonany przez parser PHP.
Co ciekawe, dyrektywa AddHandler była używana w domyślnej konfiguracji w dystrybucjach Red Hat Enterprise Linux 6/CentOS 6 oraz we wcześniejszych wersjach . Dopiero w wersji siódmej, wydanej w połowie zeszłego roku, zmieniono ją na SetHandler. Na wszelki wypadek sprawdźcie swoje konfiguracje :)
Problem dotyczy w szczególności aplikacji działających na Windowsie, gdzie wielkość liter w nazwach pliku nie jest brana pod uwagę. Możemy zatem natrafić na implementację, w której na czarnej liście znajdzie się tylko rozszerzenie .aspx, zaś plik o rozszerzeniu .ASPX zostanie już przepuszczony, co, w efekcie, doprowadzi do RCE.
Zazwyczaj w przypadku stosowania czarnej listy rozszerzeń, twórcy aplikacji nie rozważają problemu ataków client-side. Z dużym prawdopodobieństwem będzie można wgrać plik o jednym z rozszerzeń wspomnianych wcześniej, tj. .html, .htm, .xml, .swf itp.
Podejście, w którym w aplikacji jawnie podano białą listę rozszerzeń, a więc listę rozszerzeń, których upload jest dopuszczalny. Dla obrazków, biała lista mogłaby przykładowo składać się z rozszerzeń: [“jpg”, “png”, “jpeg”, “gif”, „tiff”].
W innym miejscu aplikacji może wystąpić błąd LFI.
<?php include 'includes/'.$_GET['template'];
Jako że aplikacja nie sprawdza zawartości plików, a tylko ich rozszerzenie, zuploadowany plik “obrazka” będzie mógł zostać wykorzystany do wykonania kodu PHP.
Skoro aplikacja oczekuje obrazka, można dodać w niej kod weryfikujący, czy wysłany plik rzeczywiście nim jest, wykorzystując do tego jedną z bibliotek. Zasadniczo możemy tutaj wyróżnić dwa sposoby weryfikacji:
Biała lista dopuszczalnych rozszerzeń obrazków może zawierać .svg, który de facto jest zwykłym plikiem XML parsowanym przez przeglądarkę. Wówczas można użyć kodu wcześniej wspominanego w tym artykule i wykonać dzięki temu XSS-a.
Pliki .swf umożliwiają pewien ciekawy, specyficzny atak w przypadku, gdy aplikacja weryfikuje rozszerzenia, jednak nie sprawdza zawartości pliku. We Flashu obowiązują trochę inne zasady same origin policy niż w przypadku samego JavaScriptu. Jeśli mamy przykładowo plik .swf zahostowany na domenie www.google.com, wówczas niezależnie od tego, z jakiej domeny jest on wywoływany, może pobierać dowolne zasoby (metodami POST i GET) z domeny www.google.com. Wcześniej wspominanym skryptem xss.swf można potwierdzić, że taki atak rzeczywiście się powiedzie. Co więcej, nawet jeśli plik ma rozszerzenie .jpg, można przekonać przeglądarkę, aby interpretowała go jako plik Flasha. Załóżmy, że na domenie victim.com udało się zahostować plik xss.png, którym w rzeczywistości jest xss.swf. Wówczas na domenie kontrolowanej przez atakującego należy dodać kod:
<embed allowscriptaccess="always" src="http://www.victim.com/uploads/xss.png?a=get&c=http://www.victim.com/inny_zasob" type="application/x-shockwave-flash">
Jeśli wszystko się powiedzie, w odpowiedzi powinniśmy otrzymać odpowiedź “corss domain request ok.” (literówka zamierzona, taka sama jest w kodzie skryptu).
Dotychczas skupiliśmy się wyłącznie na różnych metodach realizacji uploadu w webaplikacjach, jak jednak powinniśmy się bronić? Wszystkie ataki (czy to server-side, czy client-side) związane były z tym, że pobrane na serwer pliki znajdują się na tym samym hoście oraz tej samej domenie, co webaplikacja. Jeśli tylko jest to możliwe, należy wydzielić osobną domenę (np. jeśli aplikacja działa w domenie www.example.com, to pliki mogą być hostowane w www.example-files.com), jak również osobną instancję serwera wyłącznie do uploadów. Dzięki temu zniwelowane zostanie ryzyko związane z wykonywaniem kodu, jak również wszelkie ataki typu XSS będą odbywały się na osobnej domenie.
Nie zawsze jednak mamy środki i możliwości, by wprowadzić takie rozwiązanie. Wówczas należy pamiętać o kilku podstawowych regułach:
– mb
Zakolejkowane na kindleku do poczytania:) Dzięki!:D
Bardzo dobry artykuł, dzięki.
Przy okazji chciałem wskazać jedną rzecz, która zapewne nie tylko mi przeszkadza. Dlaczego na sekuraku gdy klikam środkowym przyciskiem myszy w linki w tekście to nie otwierają się one w nowej zakładce tylko tej samej zamykajac sekuraka w polowie artykulu? Oczywiscie na wszystkich innych stronach srodkowy przycisk myszy otwiera URL w nowej zakladce. Czemu ma służyć taki override tego zachowania na sekuraku? Łapie sie na to za każdym razem :/
Paul – tak to jest popsute po naszej stronie. W wolnej chwili (uff) pewnie zrobimy mały research / fixa. Chyba że ktoś na szybko podrzuci fixa?
Tak na szybko udało mi się znaleźć coś takiego:
https://wordpress.org/support/topic/middle-click-open-link-in-new-tab-is-ignored
Nie wiem na ile pomoże to wyeliminować problem z klikaniem środkowym przyciskiem myszy. Pewnie lepsze rozwiązanie dałoby się zrobić grzebiąc w kodzie plugina Slimstat.
Poleganie na rozszerzeniach w np. takim PHP to w ogóle słaby pomysł. Dodajcie coś o finfo (http://php.net/manual/en/class.finfo.php) ;)
Przy zapisywaniu lepiej rozszerzenia olać, dać plikowi totalnie losową nazwę i wrzucić go do katalogu z lepkim nobody:nobody oraz -x
Świetny artykuł.
A ja bym się bał uploadować plik bez sprawdzenia formatu. W php od dawna jest getimagesize() podające typ pliku.
Co do plików typu doc czy innych nie do otwacia na serwerze, to polecam spakowanie i zmianę nazwy :)
Warto jeszcze jedną rzecz uwypuklić mniej doświadczonym programistom: wszelka walidacja client-side powinna być traktowana tylko jako część frontendu i mieć równolegle walidację server-side.
Weryfikacja np. rozszerzenia pliku tylko po stronie klienta w javascripcie może doprowadzić do RCE w aplikacji.
Prawda. Lepiej nie robic po stronie klienta validacji wcale. Ostatnio musialem podmieniac javascripta na stronie rwe zebym mogl zresetowac haslo.
Klient posiadał stronę w starej niezałatanej joomli z niezałatanym modułem do generowania statystyk. statystyki nie były nawet używane. Ten moduł umożliwiał ładowanie obrazków, ale posiadał również błąd który umożliwiał ładowanie plików PHP (CVE-2009-4140).
Po załadowaniu pliku PHP następowało przejęcie serwisu, infekcje komputerów odwiedzających i wysyłke SPAMu, co skutowało dodatkowo blokadami na RBL i utrate wizerunku.