Как стать автором
Обновить

Парсер на shell с обходом XOR-шифрования при аутентификации

Время на прочтение 6 мин
Количество просмотров 3.8K
Недавно возникла необходимость в парсере интернет-счета банковской карты для дальнейшего уведомления об операциях посредством смс\e-mail. Сделать это было решено по-быстрому шел-скриптом, который будет парсить страницу со счетом с определенной периодичностью при помощи задания в cron, а в случае изменения баланса счета — высылать сообщение на мобильный телефон или e-mail. Ничего сложного на первый взгляд, однако в ходе написания пришлось решить некоторые сложности, о которых вы сможете прочесть под катом.

Для парсинга было принято решение использовать стандартные unix-утилиты — curl, grep, sed. Предполагалось аутентифицироваться на https-сервере с помощью post-запроса, а далее, все как обычно — используя регулярные выражения, выдернуть нужную инфу со страницы. Но тут-то и обнаружилась основная проблема — данные при аутентификации (точнее — пароль) отправлялись на сервер закриптованными. Напомню, страница банкинга бралась по https, т.е. такой механизм вдобавок к TLS обусловлен, разве что, защитой от локальных снифферов.

POST
Перед отправкой, в лучших традициях юникода, иероглифы кодируются в хекс

Очевидно, шифрование использовалось на стороне клиента, однако трудно представить себе этот процесс без ключа, поиском которого я и занялся.
Первым делом, подумав, а почему бы и нет, были проверены куки, понятно дело, никаких зависимостей, выдающих ключ, выявлено не было. Планы написать скрипт по-быстрому и продолжить заниматься своими делами, начали понемногу рушиться. Сайт был написан на jQuery, что еще более удручало мое положение. Воспользовавшись отладчиком, в событиях был обнаружен внедренный скрипт:
function () {
var context = $(this).parents("#loginParamsWrapper");
var authMode = $("#AuthMode", context).val();
var form = $("#frm" + authMode, context);
if (!TWIB.validForm(form)) {
return false;
}
var data = form.formToArray();
data.push({name: "AuthMode", value: authMode});
if (authMode == "TBId") {
var pan = $("#name", form).val();
var prefix = "9129120000000000";
if (prefix != "" && prefix.length > pan.length) {
pan = prefix.substring(0, prefix.length - pan.length) + pan;
}
data.push({name: "PAN", value: pan});
}
data.push({name: "PIN", value: TWIB.xorString($("#password", form).val(), "3734")});
...
}
Анализировалась PDA-версия сайта в виду экономии ресурсов при парсинге, в механизме аутентификации особых различий с основной версией обнаружено не было

Как видно из кода, к цифровому логину (в моем случае — десятизначному) прибавляется префикс 912912, судя по всему, статический, что было обнаружено еще при просмотре http-заголовков. Но больший интерес представляет последняя строчка, которая наводит на мысль, что значение пароля шифруется при помощи функции xorString и динамического ключа val, в данном случае, 3734. Поищем тело самой функции в остальных скриптах.

firebug

Данный фрагмент кода производит сложение по модулю 2 каждого символа строки str (введенный пароль) со всей строкой val (ключевая комбинация), метод String.fromCharCode выводит символы по их полученным десятичным кодам, и возвращается строка res, которая и передается вместо пароля в post-запросе при аутентификации.
Общая картина механизма защиты уже есть, осталось выяснить, откуда берется ключевая комбинация.

А оказалось все довольно просто. При открытии страницы в куки записывается идентификатор сессии, затем post ajax-запросом (XNR) отправляется actionParam:Logon (который, кстати, встроенный в последний хром отладчик поначалу упорно не замечал), и уже в ответ в страницу внедряется скрипт с ключевой комбинацией, которая связанна с id сессии.

Для того, чтобы наш парсер смог добраться до нужной страницы, ему придется проделать следующие действия:

1. Запросить страницу, получить куки сессии
2. Используя полученные куки, отправить post-запрос, получить страницу с внедренным скриптом
3. Отпарсить в полученном json ключ, которым будет производиться XOR-шифрование
4. Произвести сложение по модулю 2 каждого символа пароля с ключевой комбинацией
    4.1. Перевести символы пароля из аски в десятичные коды
    4.2. Провести операцию XOR
    4.3. Провести конвертацию из десятичного числа в шестнадцатеричные
    4.4. Получить из кодовых значений символы юникода
5. Используя полученную строку, аутентифицироваться на сайте

Определенную сложность представляет только получении зашифрованного пароля для аутентификации. Для начала получим ключ, при помощи которого и будем осуществлять дальнейшие манипуляции:
curl -s -b "PDAVersion=true" -A "$ua" "$site" -c "cookies" > /dev/null

key=$(curl -s -b "cookies" -b "PDAVersion=true" -e "$site" -A "$ua" "$site/getData.jsp?actionParam=Logon&appendTransactions=true&format=html" | grep -om 1 "val(), '[0-9]*'" | sed "s/val(), //;s/'//g")

Обещанные манипуляции с ключом:
#ascii char -> dec char code -> xor -> hex char code -> unicode char
#получаем десятичные коды символов пароля
dec ()
{
  for i in `echo $1 | sed 's/./&\n/g'`; do printf '%d\n' "'$i"; done 
}
#производим сложение по модулю 2 с ключом
xor ()
{
  for i in $*; do echo $(($i ^$key)); done
}
#переводим результат xor из dec в hex
hex ()
{
 for i in $*; do printf '%X\n' "$i"; done
}
#получаем юникод символы по их шестнадцатеричным кодам
unicode ()
{
 for i in $*; do printf "\u$i"; done
}
dec=`dec "$pass"`
xor=`xor $dec`
hex=`hex $xor`
char=`unicode $hex`
echo $char > /dev/null

На выходе получим зашифрованный пароль, которым при помощи post-запроса аутентифицируемся на сайте:

curl -s -b "cookies" -e "$site" -A "$ua" "$site/getData.jsp" -d AuthMode=TBId -d PAN=912912$login --data-urlencode PIN=$char -d actionParam=GetPANRq -d appendParams=true -d format=json -c cookies > /dev/null


security
Довольно небезопасно выдавать информацию о неверном идентификаторе пользователя, учитывая, что теоретически максимальное число цифровых айди в базе – 1010, а цифровых паролей — всего лишь 106
Только не надо судорожно дергать мышь в желании воспользоваться брутом, ничего "вкусного", кроме информации о владельце и переводах, вы не получите, т.к. у 98,76% аккаунтов нет доступа к финансовым операциям, я гарантирую)

Теперь можно получить нужные данные со страницы, в моем случае, сумму на счету:
cash=$(curl -s -b "cookies" -b "twebank_authmode=TBId" -e "$site" -A "$ua" "$site/getData.jsp?actionParam=AcctsMenu&appendTransactions=true&format=html" | grep -o 'unt">.*<b' | sed 's/unt">//;s/\.[0-9][0-9]<b//;s/\ //g')

Ну а теперь завершим задуманное, написав небольшой скрипт для уведомления об изменениях суммы:
if [ -s "$HOME/cashlog" ]
   then
      if [ `cat $HOME/cashlog` = `cat $HOME/cashlog | sed 's/[^0-9]//g'` ]
         then
            cashlog=$(<$HOME/cashlog)
            if [ "$cash" -gt "$cashlog" ]
            #if [ $(echo "$cashlog < $cash"|bc) -eq 1 ]
               then a=`expr $cash - $cashlog`
               in=$(echo "$a rub added to your account. Now you have $cash rub")
               echo $in
               python $HOME/sms_send.py -n "+79999999999" -t "$in" -l "some@mail.ru" -p "pass"
               echo "$cash" > "$HOME/cashlog"
                  elif [ "$cash" -lt "$cashlog" ]
                     then z=`expr $cashlog - $cash`
                     out=$(echo "Exchanged $z rub, you have $cash rub")
                     echo $out
                     python $HOME/sms_send.py -n "+79999999999" -t "$out" -l "some@mail.ru" -p "pass"
                     echo "$cash" > "$HOME/cashlog"
               #else echo "No changes, you have $cash rub"
            fi   
         else echo "$cash" > "$HOME/cashlog"
         echo "Cash log was updated"      
      fi
   else echo "$cash" > "$HOME/cashlog"
   echo "Cash log was created"
fi

Для оправки смс через сервисы mail.ru agent'a используется небольшой скрипт на питоне, найденный на просторах интернета.
Реализовывать отправку сообщения на электронную почту не стал специально, т.к. она будет осуществляться средствами cron.

Используя crontab, добавляем запись
*/10 * * * * /path/to/script/ca.sh
чтобы скрипт запускался каждые десять минут.

Также стоит добавить в начало сценария запись вида MAILTO=your@mail.net, при правильно настроенном sendmail (или его аналоге), cron будет оправлять сообщения на указанное мыло с результатами вывода скрипта.

Весь скрипт можете просмотреть здесь, тестировался на bash.

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

Upd: Оказалось, что старая версия банкинга не имеет никакой дополнительной защиты, т.е. для её парсинга хватило бы такого скрипта.
Теги:
Хабы:
+21
Комментарии 10
Комментарии Комментарии 10

Публикации

Истории

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн