SQL-инъекция в DDOS-боте Illusion

Обнаружением данной уязвимости я обязан Ragnar’у, собственно по его просьбе сейчас и пишу этот пост с целью объяснения сути самой баги. Однажды он постучал мне в асю и попросил поковыряться в web-админке DDOS-бота Illusion – видимо хотел угнать чей-то ботнет =) Предложение я принял и через некоторое время нашел уязвимость, а именно SQL-инъекцию. Так как основной функционал админки был недоступен для неавторизованных пользователей (а пароль хранился прямо в исходнике =\), то свои надежды я возложил на интерфейс для ботов, что и принесло мне успех. Итак, рассмотрим, какие значения параметров скрипт ожидает от ботов и в чем заключается уязвимость.

<?php
/* this function will be used by bots */
if ($act == "online")
{
	if (isset( $_GET["nickname"] ))
		$nickname = base64_decode( $_GET["nickname"] );
	else 		exit();

	if (isset( $_GET["s4"] )) $s4 = $_GET["s4"]; else $s4 = 0;
	if (isset( $_GET["s5"] )) $s5 = $_GET["s5"]; else $s5 = 0;
	if (isset( $_POST["msg_out"] )) $msg_out = base64_decode( $_POST["msg_out"] ); 
else $msg_out = "";
#	if (isset( $_GET["msg_out"] )) $msg_out = $_GET["msg_out"]; 
else $msg_out = "";
	die( db_bot_online( $nickname, $msg_out, $s4, $s5 ) );
}
?>

Переменная act передается GET-запросом и если ее содержимое равно online, это означает, что к нам пожаловал бот. Далее формируются все необходимые значения, которые передаются в функцию db_bot_online, ее листинг смотрим ниже.

<?php
/* add/update DB record about bot */
function db_bot_online( $nickname, $mo, $socks4_port, $socks5_port )
{
	GLOBAL $mysql_host, $mysql_user, $mysql_password, $mysql_dbname, $mysql_bots_table,
 $HTTP_ENV_VARS;
	if (!@mysql_connect( $mysql_host, $mysql_user, $mysql_password ))
		return "400";

	mysql_select_db( $mysql_dbname );

	$time = time();
	$ip = $_SERVER["REMOTE_ADDR"];
	$msg_out = str_replace( "\", "\\", $mo );
	$msg_out = htmlspecialchars( $msg_out );

	$r = mysql_query( "SELECT time, status, ip, msg_in FROM $mysql_bots_table 
WHERE ip=\"$ip\"" );
	$msg_in = get_new_cmd();
	if ($arr = mysql_fetch_array( $r ))
	{
		$msg_in = $arr["msg_in"];
		$status = $arr["status"];
		$t      = $arr["time"];
		$fetched = 1;
	}
	else
		$fetched = 0;

	if ($msg_out == "")
		if ($fetched)
			{mysql_query( "UPDATE $mysql_bots_table SET time=$time,
nickname=\"$nickname\",socks4=$socks4_port,socks5=$socks5_port WHERE ip=\"$ip\"" );
			}
		else
		{
			if ($msg_in) $st = 0; else $st = 1;
			mysql_query( "INSERT INTO $mysql_bots_table VALUES($time, \"$ip\", 
\"$nickname\", \"$msg_in\", \"\", $st, $socks4_port, $socks5_port)" );
			$status = $st;
		}
	else {
/**/
}
/**/
}
?>

Из всех входящих переменных фильтруется лишь msg_out, более того в запросах UPDATE и INSERT переменные st, socks4_port, socks5_port не обрамлены в кавычки. Это значит, что не придется париться с magic_quotes_gpc. С другой стороны, UNION провести мы не можем, так как в единственном SELECT –запросе подставляется значение из REMOTE_ADDR, т.е. наш ip, который изменить на произвольное значение нельзя. Остается лишь UPDATE и INSERT, поэтому придется получать инфу из БД на основании временных задержек, которые реализуются с помощью MySQL-функции BENCHMARK. Вкратце суть данного приема заключается в следующем: если условие истинно, то n-ое количество раз выполняется наше выражение, что приводит к задержке ответа, если же условие ложно, то ответ будет получен без задержки. Однако есть и ограничение: данная техника актуальна только для версии MySQL выше 4.1, так как именно с этой версии в MySQL появилась поддержка подзапросов.

index.php?act=online&nickname=emxpbmE%3D &s4=1&s5= (IF(1=1,BENCHMARK(3000000,MD5(31337)),1)) – есть задержка
index.php?act=online&nickname=emxpbmE%3D&s4=1&s5= (IF(2=1,BENCHMARK(3000000,MD5(31337)),1)) – нет задержки

Подставляя вместо MD5(31337) полезные для нас запросы, можно получить интересные сведения из БД.
Подробнее об этом методе можно прочитать здесь:
Посимвольный перебор в базах данных на примере MySQL
Итак, написав небольшой скрипт, автоматизирующий действия по сбору данных, мы с Ragnar’ом попытались узнать версию мускула. Удача была на нашей стороне, так как на сервере крутился пятый MySQL и мы могли узнать названия таблиц. Как я уже упоминал, пароль хранился прямо в исходнике поэтому достать его было невозможно ввиду отсутствия file_priv у текущего пользователя MySQL. Среди обнаруженных таблиц ничего интересного также не оказалось, поэтому получить доступ к админке так и не удалось =\ В конце концов, пришлось лишь довольствоваться наличием баги.

Скачать скрипт


No comments yet.

Write a comment: