Информативная ошибка. Манипуляция параметрами php
Взлом – это не русский язык, а скорее… химия. Почему? Да просто потому, что именно при помощи броуновского смешивания реагентов можно получить синтетическую взрывчатку =). Да-да, именно так и есть.
Как правило, при атаке на какой-то хост или сайт любой хакер (речь идет не о злоумышленниках) пытается сначала проверить общеизвестные истины, однако если администратор/пользователь хоста не является полным профаном, то простые методы не дают ожидаемого результата. Именно тогда и приходится прибегнуть к нестандартным решениям, которые и открывают нашему взору зияющее отверстие размером с люк, в которое и слона можно протиснуть, если постараться. Посему любой хакер в первую очередь должен быть превосходным аналитиком и уметь быстро соображать, но, собственно, речь не об этом.
Сегодня я хотел бы остановиться на одном весьма интересном способе получения информации о скриптах (читай, веб-приложениях). Способ является некой medium point между обычными способами сбора инфы и нестандартными. Должен заметить, что он довольно часто оказывается весьма действенным (проверенно при многочисленных тест-пингах инет-ресурсов). Последнее время он приобретает популярность из-за того, что размножающиеся как кролики горе-админы не понимают разницы между дефолтными настройками и грамотными.
Итак, на чем же основан данный метод? Все очень просто: метод основан на изучении ответов сервера при превышении лимитов времени при обработке запроса. Согласитесь, довольно несложно, учитывая, что ответы содержат абсолютный путь к скрипту, который протянул резину и не справился с выполнением задачи вовремя.
PHP
На какие же параметры стоит опираться при проведении подобной атаки? Есть несколько таких, которые присутствуют в дефолтных настройках php- интерпретатора на сервере и остаются нетронутыми после настройки сервисов и запуска железки в эксплуатацию.
Самые главные – это:
. max_execution_time
. max_input_nesting_level
. max_input_time
. memory_limit max_execution_time
. max_input_nesting_level
. max_input_time
. memory_limit
Также следует обратить внимание на эти:
. pcre.recursion_limit (PHP, версии 5.2.0 и выше)
. post_max_size (PHP, версии 4.0.3 и выше)
. upload_max_filesize
. max_file_uploads (PHP, версии 5.2.12 и выше)
В помощь вам урла http://php.net/manual/en/ini.list.php и, возможно, найдете еще что-нибудь полезное. Исходя из соображений универсальности можно сделать вывод, что лучше всего подходят опции memory_limit и max_execution_time. Если имеются настройки display_errors=On и
reporting=E_ERROR – то они выбрасываются в тело ответа сервера.
Подготовка
Для проведения подобной атаки необходимо знать максимальную длину GET-запроса. Это можно сделать при помощи скрипта. Погуглив, я нашел отличный вариантик подобной игрушки на ксакепе (автор Владимир D0znpp Воронцов):
function fuzz_max_uri_len($url){
$headers = array();
$data = array();
$left = 500;//Значение левого края искомого диапазона
$right = 64000;//Значение правого края искомого диапазона
$accur = 5;//Точность, с которой определяем значение
while (($right-$left) > $accur){
$cur = ($right+$left)/2;
$data['x'] = str_repeat("x",$cur);
list($h,$c,$t) = sendGetRequest($url, $headers, $data);
$s = intval(substr($h,9,3));
if ($s<400){
$left=$cur;
}
else{
$right=$cur;
}
echo "\n$cur\t$s";
}
return(($right+$left)/2);
}
Скрипт работает по методу дихотомии и определяет максимальную длину запроса, установленную веб-сервером.
Далее нам нужно выяснить значение параметра max_input_nesting_level, который по умолчанию равен 64. Он определяет максимальную размерность массива. Взгляните на строчку кода:
Если max_input_nesting_level=1, то в запросе передастся ?a[][], а на экране мы получим «ничего», так как в интерпретаторе возникнет ошибка Notice, говорящая о том, что переданная переменная необъективна. Если значение будет выставлено на 2, то на экране мы увидим “Array”. Как видите, это довольно простой алгоритм: увеличиваем значение, пока не исчезнет надпись “Array”. Вся соль в том, что при постепенном увеличении значения мы получаем ответ, а при превышении порогового размера ответа возникает ситуация, которая расценивается как аномальная и записывается в лог – вот вам готовый алгоритм скрипта, который поможет облегчить процесс анализа.
Прожорливость
Итак, наша задача, напомню, вызвать "Allowed memory size exhausted". Хотите посмеяться? Прошу:
Вот этот самый скрипт может поедать оперативку сервера мегабайтами. Для вывода размера используемой памяти задействуется параметр memory_get_usage(), берем и добавляем его в наш код. После вызываем скрипт не при помощи переменной, а через GET-запрос (это +1 Кб памяти). Напомню, что мы должны прибрать к рукам как можно больше памяти, при этом надо не только не превысить максимальную длину GET, но и сделать его как можно короче. Передаем запрос с параметром ?a[] (+ еще 500 байт), а после беремся за вышеописанный max_input_nesting_level – как только размерность массива превысит установленный лимит, потребление памяти будет случайной.
Чтобы промониторить объем потребляемой памяти для любого приложения, нужно воспользоваться скриптом:
И добавить его в auto_append_file в php.ini
Теперь, воспользовавшись функцией, представленной ниже, можно добиться того, что она будет искать в ответе сервера соответствующий маркер и получать значение потребляемой памяти:
function findMarker($content){
$p1 = strpos($content, "ONsec E500 mem:");
if ($p1===false){
return 0;
}
else{
$p2=strpos($content,"#",$p1);
if ($p2===false){
return 0;
}
else{
$mem=substr($content,$p1+15,$p2-$p1-15);
}
}
return intval($mem);
}
Теперь ищем на сервере те скрипты, которые любят кушать память. Навскидку это довольно тяжело определить, но если обратить внимание на циклы с обработкой массивов, рекурсии и т.п. – получится.
Медлительность как уязвимость
Стоит совершенно справедливо заметить, что веб-серверы – это не ДВК:), а посему поедание памяти и время выполнения скрипта зависит от нагрузки на сервер в данный момент времени. Следовательно, все это весьма непостоянно, однако ситуацию, в которой время выполнения скрипта увеличится, можно создать искусственно. Есть и соответствующий класс уязвимостей в OWASP - dead_code. Он как раз и включает в себя уязвимости разработки, которые позволяют злоумышленнику провести взлом, вызвав ошибку «промедления», если так можно выразиться.
Вариантов вызвать такое — великое множество, но все зависит от конкретного сайта, так как большинство подобных ошибок возникает из-за сторонних скриптов, а не из-за движка-каркаса сайта. Тем не менее, покормив ресурс разного рода запросами и их вариациями, можно понять, на что делать ставку в первую очередь.
Анализ
После того как нужное количество ответов имеется у нас в наличии, можно приступать к разбору результатов. Все что нужно – это выделить из ответов пути и расположить ответы по уровням, в зависимости от длины. На том же ксакепе я нашел отличный код, который решит проблемку (автор тот же):
function parseResults($dir){
if (is_dir($dir)) {
if ($dh = opendir($dir)) {
$i=0;
$results = array();
while (($file = readdir($dh)) !== false) {
$curFile = $dir.$file;
$fh = fopen($curFile, 'r');
$filedata = fread($fh, filesize($curFile));
$fsize = filesize($curFile);
$p1 = strpos($filedata,"Maximum execution time of ");
if ($p1 === false) {}
else{
$p2 = $p1+52;
$p3 = strpos($filedata,"",$p2);
if ($p3 === false) {}
else{
$len = $p3-$p2;
$path = substr($filedata,$p2,$len);
$unique = true;
//Проверяем на уникальность
foreach($results as $key=>$value){
if ($value['path']==$path){
$unique=false;
break;
}
}
if ($unique){
$len = $p3-$p2;
$res = array( 'path'=>substr($filedata,$p2,$len),'len'=>$fsize);
$results[$i]=$res;
$i++;
}
}
}
fclose($fh);
}
closedir($dh);
$size=count($results)-1;
//Сортируем результаты по длине
for ($i = $size; $i>=0; $i--) {
for ($j = 0; $j<=($i-1); $j++)
if ($results[$j]['len']>$results[$j+1]['len']) {
$k = $results[$j];
$results[$j] = $results[$j+1];
$results[$j+1] = $k;
}
}
return $results;
}
}
}
На выходе, как утверждает автор скрипта, мы получим упорядоченный массив с длинами ответов и адресами/именами скриптов, в которых возникла ошибка.
Вот такой вот способ получения информации путем провокации ошибки. Это далеко не единственный вариант, как всем известно. Однако он мне нравится своей оригинальностью – интересно таким образом получать нужные сведения для тестирования ресурсов.
В конце статьи я хотел бы напомнить, что использование данной информации в злонамеренных целях повлечет за собой уголовную ответственность согласно соответствующей статьи УК РБ. Если кто-либо решится совершить злодеяние, то ни автор статьи, ни редакция не несут никакой ответственность, поскольку данный материал опубликован как руководство для тестирования интернет-сайтов на наличие уязвимостей.
Евгений Кучук, SASecurity gr. q@sa-sec.org
Как правило, при атаке на какой-то хост или сайт любой хакер (речь идет не о злоумышленниках) пытается сначала проверить общеизвестные истины, однако если администратор/пользователь хоста не является полным профаном, то простые методы не дают ожидаемого результата. Именно тогда и приходится прибегнуть к нестандартным решениям, которые и открывают нашему взору зияющее отверстие размером с люк, в которое и слона можно протиснуть, если постараться. Посему любой хакер в первую очередь должен быть превосходным аналитиком и уметь быстро соображать, но, собственно, речь не об этом.
Сегодня я хотел бы остановиться на одном весьма интересном способе получения информации о скриптах (читай, веб-приложениях). Способ является некой medium point между обычными способами сбора инфы и нестандартными. Должен заметить, что он довольно часто оказывается весьма действенным (проверенно при многочисленных тест-пингах инет-ресурсов). Последнее время он приобретает популярность из-за того, что размножающиеся как кролики горе-админы не понимают разницы между дефолтными настройками и грамотными.
Итак, на чем же основан данный метод? Все очень просто: метод основан на изучении ответов сервера при превышении лимитов времени при обработке запроса. Согласитесь, довольно несложно, учитывая, что ответы содержат абсолютный путь к скрипту, который протянул резину и не справился с выполнением задачи вовремя.
PHP
На какие же параметры стоит опираться при проведении подобной атаки? Есть несколько таких, которые присутствуют в дефолтных настройках php- интерпретатора на сервере и остаются нетронутыми после настройки сервисов и запуска железки в эксплуатацию.
Самые главные – это:
. max_execution_time
. max_input_nesting_level
. max_input_time
. memory_limit max_execution_time
. max_input_nesting_level
. max_input_time
. memory_limit
Также следует обратить внимание на эти:
. pcre.recursion_limit (PHP, версии 5.2.0 и выше)
. post_max_size (PHP, версии 4.0.3 и выше)
. upload_max_filesize
. max_file_uploads (PHP, версии 5.2.12 и выше)
В помощь вам урла http://php.net/manual/en/ini.list.php и, возможно, найдете еще что-нибудь полезное. Исходя из соображений универсальности можно сделать вывод, что лучше всего подходят опции memory_limit и max_execution_time. Если имеются настройки display_errors=On и
reporting=E_ERROR – то они выбрасываются в тело ответа сервера.
Подготовка
Для проведения подобной атаки необходимо знать максимальную длину GET-запроса. Это можно сделать при помощи скрипта. Погуглив, я нашел отличный вариантик подобной игрушки на ксакепе (автор Владимир D0znpp Воронцов):
function fuzz_max_uri_len($url){
$headers = array();
$data = array();
$left = 500;//Значение левого края искомого диапазона
$right = 64000;//Значение правого края искомого диапазона
$accur = 5;//Точность, с которой определяем значение
while (($right-$left) > $accur){
$cur = ($right+$left)/2;
$data['x'] = str_repeat("x",$cur);
list($h,$c,$t) = sendGetRequest($url, $headers, $data);
$s = intval(substr($h,9,3));
if ($s<400){
$left=$cur;
}
else{
$right=$cur;
}
echo "\n$cur\t$s";
}
return(($right+$left)/2);
}
Скрипт работает по методу дихотомии и определяет максимальную длину запроса, установленную веб-сервером.
Далее нам нужно выяснить значение параметра max_input_nesting_level, который по умолчанию равен 64. Он определяет максимальную размерность массива. Взгляните на строчку кода:
Если max_input_nesting_level=1, то в запросе передастся ?a[][], а на экране мы получим «ничего», так как в интерпретаторе возникнет ошибка Notice, говорящая о том, что переданная переменная необъективна. Если значение будет выставлено на 2, то на экране мы увидим “Array”. Как видите, это довольно простой алгоритм: увеличиваем значение, пока не исчезнет надпись “Array”. Вся соль в том, что при постепенном увеличении значения мы получаем ответ, а при превышении порогового размера ответа возникает ситуация, которая расценивается как аномальная и записывается в лог – вот вам готовый алгоритм скрипта, который поможет облегчить процесс анализа.
Прожорливость
Итак, наша задача, напомню, вызвать "Allowed memory size exhausted". Хотите посмеяться? Прошу:
Вот этот самый скрипт может поедать оперативку сервера мегабайтами. Для вывода размера используемой памяти задействуется параметр memory_get_usage(), берем и добавляем его в наш код. После вызываем скрипт не при помощи переменной, а через GET-запрос (это +1 Кб памяти). Напомню, что мы должны прибрать к рукам как можно больше памяти, при этом надо не только не превысить максимальную длину GET, но и сделать его как можно короче. Передаем запрос с параметром ?a[] (+ еще 500 байт), а после беремся за вышеописанный max_input_nesting_level – как только размерность массива превысит установленный лимит, потребление памяти будет случайной.
Чтобы промониторить объем потребляемой памяти для любого приложения, нужно воспользоваться скриптом:
И добавить его в auto_append_file в php.ini
Теперь, воспользовавшись функцией, представленной ниже, можно добиться того, что она будет искать в ответе сервера соответствующий маркер и получать значение потребляемой памяти:
function findMarker($content){
$p1 = strpos($content, "ONsec E500 mem:");
if ($p1===false){
return 0;
}
else{
$p2=strpos($content,"#",$p1);
if ($p2===false){
return 0;
}
else{
$mem=substr($content,$p1+15,$p2-$p1-15);
}
}
return intval($mem);
}
Теперь ищем на сервере те скрипты, которые любят кушать память. Навскидку это довольно тяжело определить, но если обратить внимание на циклы с обработкой массивов, рекурсии и т.п. – получится.
Медлительность как уязвимость
Стоит совершенно справедливо заметить, что веб-серверы – это не ДВК:), а посему поедание памяти и время выполнения скрипта зависит от нагрузки на сервер в данный момент времени. Следовательно, все это весьма непостоянно, однако ситуацию, в которой время выполнения скрипта увеличится, можно создать искусственно. Есть и соответствующий класс уязвимостей в OWASP - dead_code. Он как раз и включает в себя уязвимости разработки, которые позволяют злоумышленнику провести взлом, вызвав ошибку «промедления», если так можно выразиться.
Вариантов вызвать такое — великое множество, но все зависит от конкретного сайта, так как большинство подобных ошибок возникает из-за сторонних скриптов, а не из-за движка-каркаса сайта. Тем не менее, покормив ресурс разного рода запросами и их вариациями, можно понять, на что делать ставку в первую очередь.
Анализ
После того как нужное количество ответов имеется у нас в наличии, можно приступать к разбору результатов. Все что нужно – это выделить из ответов пути и расположить ответы по уровням, в зависимости от длины. На том же ксакепе я нашел отличный код, который решит проблемку (автор тот же):
function parseResults($dir){
if (is_dir($dir)) {
if ($dh = opendir($dir)) {
$i=0;
$results = array();
while (($file = readdir($dh)) !== false) {
$curFile = $dir.$file;
$fh = fopen($curFile, 'r');
$filedata = fread($fh, filesize($curFile));
$fsize = filesize($curFile);
$p1 = strpos($filedata,"Maximum execution time of ");
if ($p1 === false) {}
else{
$p2 = $p1+52;
$p3 = strpos($filedata,"",$p2);
if ($p3 === false) {}
else{
$len = $p3-$p2;
$path = substr($filedata,$p2,$len);
$unique = true;
//Проверяем на уникальность
foreach($results as $key=>$value){
if ($value['path']==$path){
$unique=false;
break;
}
}
if ($unique){
$len = $p3-$p2;
$res = array( 'path'=>substr($filedata,$p2,$len),'len'=>$fsize);
$results[$i]=$res;
$i++;
}
}
}
fclose($fh);
}
closedir($dh);
$size=count($results)-1;
//Сортируем результаты по длине
for ($i = $size; $i>=0; $i--) {
for ($j = 0; $j<=($i-1); $j++)
if ($results[$j]['len']>$results[$j+1]['len']) {
$k = $results[$j];
$results[$j] = $results[$j+1];
$results[$j+1] = $k;
}
}
return $results;
}
}
}
На выходе, как утверждает автор скрипта, мы получим упорядоченный массив с длинами ответов и адресами/именами скриптов, в которых возникла ошибка.
Вот такой вот способ получения информации путем провокации ошибки. Это далеко не единственный вариант, как всем известно. Однако он мне нравится своей оригинальностью – интересно таким образом получать нужные сведения для тестирования ресурсов.
В конце статьи я хотел бы напомнить, что использование данной информации в злонамеренных целях повлечет за собой уголовную ответственность согласно соответствующей статьи УК РБ. Если кто-либо решится совершить злодеяние, то ни автор статьи, ни редакция не несут никакой ответственность, поскольку данный материал опубликован как руководство для тестирования интернет-сайтов на наличие уязвимостей.
Евгений Кучук, SASecurity gr. q@sa-sec.org
Компьютерная газета. Статья была опубликована в номере 28 за 2010 год в рубрике безопасность