Dziś jest czwartek, 24 lipca 2008 roku (z kalendarza...)

SERVER_NAME i rozpoznawanie domen

Icon

14.02.2008, 12:54

PHP

Komentarze (10)

Powrót

Pod koniec zeszłego roku montowałem sobie silnik w PHP, lecz prace nad nim wstrzymałem z powodu OPT. Postanowiłem go wczoraj uruchomić na nowopostawionym FastCGI ot tak, żeby sprawdzić, jak się trzyma. Okazało się, że kod się niesamowicie sypał. Nie działało wykrywanie środowiska pracy, translacja adresów w routerze oraz kilka innych rzeczy. Szybki rzut oka wystarczył, by zorientować się, że "awaria" dotknęła wszystkich komponentów, które opierały swoje działanie na dostarczonych z serwera danych o żądaniu.

Dane dotyczące stanu środowiska znajdują się w superglobalnej tablicy $_SERVER. Wiele skryptów, w tym także i mój, korzysta z elementu $_SERVER['SERVER_NAME'] do określenia nazwy domeny, w obrębie której został on uruchomiony. W moim przypadku od niej zależało wybrane środowisko, a później także postać adresów URL. Okazało się, że skrypt dostaje w tym polu zupełnie inne informacje, niż przewidywał. Wywoływałem demoapp1.scriptist.lh, lecz tam miałem ciągle localhost. Wyświetlenie pełnej zawartości tablicy ujawniło, że oczekiwane dane także są dostarczane, lecz w polu $_SERVER['HTTP_HOST']. Sprawa mnie zaintrygowała, dlatego znalazłem w sieci więcej informacji i tak trafiłem tutaj.

Pole HTTP_HOST odpowiada dokładnie zawartości nagłówka "Host:" w żądaniu HTTP. Jeżeli takiego nagłówka nie ma, pole przyjmuje pustą wartość. Inaczej sprawa ma się z SERVER_NAME. Może ono bazować na zawartości wspomnianego nagłówka, ale równie dobrze może tam znaleźć się domyślny host serwera lub też zawartość nagłówka zostanie poddana modyfikacjom. Autor załączonego wyżej linka przeprowadził programem telnet kilka testów, aby sprawdzić zachowanie się obu tych pól. Polegało to na umieszczeniu na serwerze w głównym katalogu skryptu o treści:

<?php
	echo 'HTTP_HOST: ['.$_SERVER['HTTP_HOST']."]\n";
	echo 'SERVER_NAME: ['.$_SERVER['SERVER_NAME']."]\n";
?>

Następnie telnetem wysyłamy żądanie HTTP podobne do tego poniżej:

# telnet 127.0.0.1 80
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
GET /http.php HTTP/1.0
Host: localhost

Serwer odpowie wynikiem działania skryptu. Autor przetestował kilka konfiguracji - bez nagłówka host oraz z różnymi jego wartościami. Powtórzyłem go u siebie na dwóch konfiguracjach: Lighttpd+FastCGI oraz Apache+mod_php.

Lighttpd+FastCGI
Żądanie Odpowiedź
GET /http.php HTTP/1.0
403 Forbidden :)
GET /http.php HTTP/1.0
Host: localhost
HTTP_HOST: [localhost]
SERVER_NAME: [localhost]
GET /http.php HTTP/1.0
Host: test.sites.lh
HTTP_HOST: [test.sites.lh]
SERVER_NAME: [localhost]
GET /http.php HTTP/1.0
Host: <xss>--atak
400 Bad Request

Lighttpd okazał się dość idiotoodporny. Żądanie bez nagłówka zawsze powoduje wygenerowanie błędu 403 (choć możliwe, że po prostu wyszła mi taka konfiguracja - nie znam jeszcze dobrze tego programu), zaś próba ataku XSS została udaremniona przez komunikat o błędnym żądaniu HTTP. Co ciekawe, przy łączeniu się z istniejącym hostem, w SERVER_NAME pozostawała nazwa "localhost" i właśnie to było przyczyną niedziałania mojego silnika. Testy powtórzyłem dla Apache+mod_php:

Żądanie Odpowiedź
GET /http.php HTTP/1.0
HTTP_HOST: []
SERVER_NAME: [localhost]
GET /http.php HTTP/1.0
Host: localhost
HTTP_HOST: [localhost]
SERVER_NAME: [localhost]
GET /http.php HTTP/1.0
Host: test.sites.lh
HTTP_HOST: [test.sites.lh]
SERVER_NAME: [test.sites.lh]
GET /http.php HTTP/1.0
Host: <xss>--atak
HTTP_HOST: [<xss>--atak]
SERVER_NAME: [&lt;xss&gt;--atak]

Wyniki są zgoła odmienne. Przede wszystkim nazwa użytego hosta jest zawsze przepisywana do SERVER_NAME, o ile została ustawiona, a co więcej - poddawana jest escapingowi. Zauważmy, że w przypadku próby ataku XSS w HTTP_HOST cała nazwa pozostała w oryginalnej postaci, zaś w drugim została przefiltrowana, dzięki czemu nie stanowi zagrożenia. Co prawda wykonany tutaj atak XSS jest trochę bez sensu, bo właściwie atakuję sam siebie, ale nie wolno lekceważyć pomysłowości włamywaczy. Być może ktoś, komu by zależało, zrobiłby z niej właściwy użytek.

Można stąd wysnuć kilka wniosków. Korzystanie z surowych danych dostarczonych przez tablicę $_SERVER rodzi bardzo dużo problemów, ponieważ uzależniamy nasz skrypt od jednej, konkretnej platformy. Porządna aplikacja powinna być zaopatrzona w odpowiedni filtr, który by analizował zawartość wspomnianej tablicy i na jej podstawie dopiero budował użyteczne dla skryptu dane. Co więcej, danym tym nie powinno się bezkrytycznie ufać. Część z nich pochodzi bezpośrednio z nagłówków żądania HTTP, zatem może być dowolnie zmanipulowana przez sprytnych włamywaczy. Prawdziwie bezpiecznych pól jest zaledwie parę (np. REMOTE_ADDR) i choć zawarte w nich informacje są ograniczone, można na nich spokojnie polegać, zwłaszcza tam, gdzie w grę wchodzą sprawy związane z dostępem i bezpieczeństwem.

Powrót

Komentarze

Napisał Bob w czwartek, 14 lutego 2008 o 15:33

Przecież REMOTE_ADDR też może być dowolny np. 127.0.0.1 przy dobrym proxy.

Napisał Jacek w piątek, 15 lutego 2008 o 01:51

Bardzo fajny artykul; daje do myslenia ;)

Pozdrawiam

Napisał Zyx w piątek, 15 lutego 2008 o 07:43

REMOTE_ADDR może mieć wartość "127.0.0.1" tylko, gdy został wysłany z samego serwera przy użyciu interfejsu pętli zwrotnej. Jest to adres, z którego serwer HTTP bezpośrednio otrzymał żądanie i na który wyśle odpowiedź. Nie zależy on od żadnego nagłówka HTTP i dlatego nie może być zafałszowany w taki sposób, jak Ty piszesz - że wchodzisz z np. 12.34.56.78, a REMOTE_ADDR jest 87.65.43.21. Fakt, jeśli wejdziesz przez proxy, to zamaskujesz swój IP, ale w REMOTE_ADDR będzie wtedy adres serwera proxy i skrypt może skorzystać z tego pola, aby się o tym dowiedzieć.

Niebezpieczny jest za to adres HTTP_X_FORWARDED_FOR, ponieważ jego wartość zależy od nagłówków HTTP, czyli można tam dosłownie wpisać cokolwiek, nawet "miecio jest gupi".

Napisał Bob w piątek, 15 lutego 2008 o 08:20

Niektóre proxy elitarne zwracają 127.0.0.1 w REMOTE_ADDR i puste HTTP_X_FORWARDED_FOR. Kilka miesięcy temu dość dokładnie to sprawdzałem. Jeżeli chciałbyś na tym w jakikolwiek sposób polegać to możesz się przejechać.

Napisał GDR! w piątek, 15 lutego 2008 o 10:56

A nie łatwiej było po prostu zajrzeć do dokumentacji? ;)

Napisał Zyx w piątek, 15 lutego 2008 o 11:48

Bob, a mógłbyś podać mi link do takiego proxy? Przejrzałem parę różnych stron o proxy i na każdym w podpunkcie "High anonymity/Elite proxy" było "REMOTE_ADDR = proxy IP". Nigdzie nie natrafiłem nawet na ślad czegoś, co działając zdalnie, potrafi przestawić to na 127.0.0.1. Jesteś pewien, że dobrze zinterpretowałeś parę miesięcy temu dane? Może ktoś wykorzystał oprogramowanie proxy zainstalowane na tym samym serwerze, co serwer HTTP i dlatego pokazywało Ci się 127.0.0.1? W dokumentacji Apache'a jest wyraźnie napisane, że REMOTE_ADDR nie jest kontrolowany żadnym nagłówkiem HTTP i jest adresem, na który serwer odsyła odpowiedź. Gdyby dostał 127.0.0.1, to wysłałby odpowiedź na pętlę zwrotną i taki proxy nie dostałby z powrotem ani bajta danych.

GDR -> gdyby to było opisane w dokumentacji, już dawno bym o tym wiedział. A z informacji znalezionych w dołączonym wpisie wynika, że nawet Rasmus Lerdorf, twórca PHP, mylił się co do działania SERVER_NAME.

Napisał Bob w piątek, 15 lutego 2008 o 13:13

A co tu jest do interpretowania? print_r($_SERVER) i sprawdzamy. Sprawdzałem zarówno na domowym serwerze jak i dla pewności na dostępnym w sieci. Moje zapytanie do google "elite proxy list" już w pierwszym wyniku zwróciło 350 proxy więc raczej ci nie podam - aż tak mi się nie chce udowadniać :) Ale jak nie masz co w nocy robić albo na jakimś nudnym wykładzie z dostępem do netu możesz poszukać.

Napisał Zyx w sobotę, 16 lutego 2008 o 12:03

Mam pomysł na napisanie skryptu, który automatycznie mógłby sprawdzać tego typu adresy i niedługo go załączę, jako że już zgromadziłem około 40 adresów IP rozmaitych serwerów proxy. Dopóki nie zobaczę jakiegoś proxy nieznajdującego się na moim serwerze, który potrafi przełączyć REMOTE_ADDR na 127.0.0.1 i nie dowiem się, jak on to robi, to nie uwierzę. Nie zastanawiało Cię przypadkiem, dlaczego nikt inny w sieci nie wspomina o takim zjawisku? Jeżeli to jest faktycznie możliwe, to zdziwiłbym się, gdybyś był dotąd jedyną osobą na całej Ziemi, która to zauważyła tak poważną lukę bezpieczeństwa.

Napisał Bob w poniedziałek, 18 lutego 2008 o 06:50

A mam pytanie. Zdajesz sobie sprawę jak wadliwa jest tak prosta wydawałoby się czynność jak ustawianie cookie? Aby to zrobić w 100% poprawnie należałoby wykryć wcześniej przeglądarkę i w zależności od tego wysłać poprawny nagłówek. Ile osób ma o tym pojęcie i ile osób się tym przejmuje? Czy Ty o tym wiesz? (hint: FF przyjmuje tylko i wyłącznie max-age, zresztą zgodnie z rfc)

Napisał Zyx w wtorek, 19 lutego 2008 o 15:21

Na tyle dużo, że w sieci można znaleźć bez większego problemu materiały na ten temat i nie kłóci się to z logiką. Natomiast w Twoim przypadku nie ma absolutnie nic. Powiedz, jak zweryfikowałeś poprawność swych przypuszczeń.

Strona 1 z 1 :: 1

Skomentuj

NickInformacja
E-mailTylko do użytku wewnętrznego.
WWWNie zapomnij o http://
LayoutNapisz tu, czy widzisz dzienny czy nocny layout.
WpisFormatowanie wiki
Internauto, pamiętaj! Wolność to nie samowola - dbaj o kulturę wypowiedzi oraz dyskusji w sieci.

Na Zyxist.com panuje swoboda wyrażania opinii oraz krytyki pod dowolnym adresem. Jedyny warunek: musi być ona kulturalna i rzeczowa. Na chamstwo, prostactwo lub jawne obrażanie kogokolwiek nie ma tu miejsca i takie komentarze są bardzo szybko usuwane. Jeśli zamierzasz polemizować z treścią wpisu, wpierw uważnie ją przeczytaj.

© Tomasz "Zyx" Jędrzejewski 2005 - 2008 | Wykonanych zapytań: 2 | Serwer wirtualny zapewnia