Получи случайную криптовалюту за регистрацию!

Рождество - время подарков и подведения итогов. Сегодня я реши | Кавычка

Рождество - время подарков и подведения итогов. Сегодня я решил сделать всем подарок - подвести итоги последних 15 лет проведения LFI в самом рождественском языке программирования - PHP.

Рассмотрим достаточно популярную (когда-то) ситуацию когда у нас есть LFI но нет возможности загрузить файл на сервер. Вот какие есть варианты получить шелл:

logs / sessions / etc
Первое что можно попробовать подгрузить - файлы логов, сессий, файловые дескрипторы или переменные окружение из procfs и т д. Идея простая и в то же время бесполезная - мы каким то либо образом влияем на подгружаемый файл (например передаём пейлоад в User-Agent и пытаемся подгрузить его из access.log) и подгружаем его через LFI. Бесполезной эта идея является потому что последние лет 10 у процесса PHP отобрали права на чтение логов и procfs, а сессии выключены, хранятся в БД (спасибо, микросервисная архитектура) или на них нет возможности повилять

phpinfo()
Ещё один старый способ, первые упоминания которого датируются 2010/2011 годом. Здесь суть заключается в том что при директиве file_uploads = on (которая имеет это значение по умолчанию) PHP будет вычитывать файлы из тела запроса и временно хранить их в директории указанной в директиве upload_tmp_dir. Звучит как отличное решение проблемы если бы не пара нюансов: временные файлы имеют рандомное имя и удаляются сразу же после завершения работы скрипта. В данном случае на помощь может прийти вывод phpinfo();, если он есть где либо на сервере. Он решит первую проблему - отправив файл на этот скрипт мы сможем увидеть имя временного файла. Вторая проблема решается благодаря директиве output_buffering которая по умолчанию имеет значение 4096. Это значит что при превышении размера тела ответа процесс PHP будет отдавать его по серверу чанками. Теперь нам нужно только сделать так чтобы тело ответа было больше размера одного чанка, чтобы имя файла было не в последнем чанке. Далее, надеемся что сервер умеет работать с chunked ответами, вычитываем чанки до имени файла и держим коннект открытым, пока подгружаем полученное имя файла и радуемся шеллу.

https://insomniasec.com/downloads/publications/LFI%20With%20PHPInfo%20Assistance.pdf

PHP_SESSION_UPLOAD_PROGRESS
В 2018 году один внимательный увидел в документации PHP директиву session.upload_progress.enabled которая включена по умолчанию и позволяет сохранять в файле сессии прогресс загрузки файла. Более того, эта директива сама создаст файл сессии даже если не было явного вызова session_start() и положит в него прогресс загрузки файла по ключу который указывается через cookie параметра PHP_SESSION_UPLOAD_PROGRESS. Таким образом, мы можем сами создать сессию и положить в неё свой ключ (который вместе с правильным использованием фильтров может стать для нас золотым ключиком). Но возникает очередная проблема - сессия будет очищена как только загрузка файла будет завершена. Здесь очевидно два решения - рейсить загрузку файла и его подключение или же подержать загрузку файла подольше.

https://blog.orange.tw/2018/10/hitcon-ctf-2018-one-line-php-challenge.html

tempfile bruteforce / php crash
Предыдущий метод хорош, но может случиться так что директива session.upload_progress.enabled выключена. В таком случае очень полезно вспомнить какой на дворе год и воспользоваться всеми благами широких каналов и быстрого сетевого стека. При возможности задержать исполнение скрипта на достаточное время (например наличие SSRF или чтения файла, что в целом в PHP почти одно и то же) мы можем попытаться набрутить имя временного файла. Удивительно, но такой подход пару раз лично у меня срабатывал и успешная эксплуатация занимала порядка пары часов. Если же мы не хотим сильно нагружать сервер, то можно поискать в багтрекере парочку сегфолтов для необходимой версии PHP. Очевидно, при падении PHP не будет удалять временный файл, тем самым сильно облегчая нам последующий брутфорс.

https://hackmd.io/@ZzDmROodQUynQsF9je3Q5Q/Hk-2nUb3Q?type=view