Атака на API веб-приложения Django

Ата­ка API Django

В этом рай­тапе я раз­беру ата­ку на веб‑при­ложе­ние, которое мы условно назовем TopApp.

Еще по теме: Взлом сайта на Django

Статья в образовательных целях для обучения этичных хакеров (багхантеров). Баг Баунти — это программа, которую владелец приложения проводит для привлечения сторонних специалистов к поиску уязвимостей. При участии в программе Bug Bounty нужно действовать этично и придерживаться установленных правил. Ни редакция spy-soft.net, ни автор не несут ответственности за ваши действия.

Атака на API веб-приложения Django

Одна из осо­бен­ностей его API — это исполь­зование текуще­го зна­чения вре­мени в фор­мате Unix в качес­тве уни­каль­ного иден­тифика­тора.

API веб‑при­ложе­ния Django

Оно и вер­но, вре­мя назад не вер­нуть, оно чис­ло инкре­мен­тное, а количес­тво опе­раций вряд ли будет таким боль­шим, что­бы выз­вать исклю­читель­ную ситу­ацию.

Ве­роят­ность генера­ции одно­го и того же уни­каль­ного иден­тифика­тора дос­таточ­но мала, око­ло 0,01% при усло­вии, что две опе­рации прой­дут в одну и ту же секун­ду. А еще в иден­тифика­торе сра­зу же фик­сиру­ется вре­мя опе­рации.

API веб при­ложе­ния Django

Од­нако, зная вре­мя, ког­да про­изош­ла опе­рация, мы име­ем 100%-ю веро­ятность уга­дать этот иден­тифика­тор, так как для перебо­ра иден­тифика­тора нам необ­ходимо око­ло 10 000 вари­антов.

Имен­но из‑за это­го опас­ны такие уяз­вимос­ти, как небезо­пас­ная ссыл­ка на объ­ект.

Нап­ример, дос­туп к иден­тифика­тору через ресурс top-secret для поль­зовате­ля без аутен­тифика­ции.

API Django

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

Django API

Од­нако есть и сек­реты — такие как иден­тифика­тор сес­сии (на скрин­шоте выше это skey — 28 сим­волов) и ключ вос­ста­нов­ления пароля, клю­чи к API.

Нач­нем с кода вос­ста­нов­ления пароля. Пос­ле зап­роса кода нам при­ходит сле­дующее пись­мо.

Код восстановления пароля на сайте

Очень стран­ный токен, учи­тывая, что сам сайт написан на Django.

Сна­чала я изу­чил модуль django-rest-passwordreset, в нем есть уяз­вимость, при которой токен под­ходит к любому поч­товому адре­су, а пару лет назад в изу­чаемом движ­ке была такая же уяз­вимость. Но токен вос­ста­нов­ления совер­шенно дру­гой.

В Django есть раз­ные фор­маты токенов и путей к вос­ста­нов­лению паролей.

В вер­сии 1.11:

В вер­сии 1.8:

3.0 исполь­зует такую ссыл­ку:

В нашем слу­чае это [0-9].

14 байт — это мно­го ком­бинаций, пусть даже из цифр, которую нере­аль­но переб­рать онлайн.

Час­то для генера­ции токенов исполь­зуют ГПСЧ, нап­ример тот же mt_rand. Ломать ран­домы обыч­но очень весело, потому что ник­то не понима­ет, как работа­ет крип­тогра­фия, а потом все очень удив­ляют­ся, как мож­но было пред­ска­зать слу­чай­ное зна­чение.

Для начала изу­чения нам нуж­но было срав­нить воз­можные токены, про­верить на инкре­мен­тность и дру­гую пред­ска­зуемость.

Для это­го мы под­няли собс­твен­ный SMTP-сер­вер, что облегча­ет работу с тек­стом пись­ма. Зап­росили 10 000 кодов вос­ста­нов­ления паролей, толь­ко вот сами пись­ма шли мно­го часов, а дош­ло око­ло 1500. Но это­го было дос­таточ­но.

SMTP сервер для атаки на API Django

На вид нет ничего необыч­ного, но, если отсорти­ровать токены, будет понят­но, что что‑то не так.

SMTP сервер для атаки на API Django

SMTP сервер для атаки на API Django

Пер­вая мысль — это собс­твен­ная реали­зация ли­ней­ного кон­гру­энтно­го метода (LCG).

Мы поп­робова­ли нес­коль­ко зап­росов для экс­плу­ата­ции сос­тояния гон­ки (race condition) и отпра­вили нес­коль­ко токенов.

Идея была в том, что, если отпра­вить два зап­роса на вос­ста­нов­ление пароля (на собс­твен­ную поч­ту и жер­тве), при­дут оди­нако­вые или минималь­но раз­личимые коды.

Пос­ле мно­жес­тва попыток мы получи­ли мак­сималь­ное приб­лижение:

поч­та 1: 567*94*300990116;
поч­та 2: 567*56*301990116.

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

И все же эти токены не давали покоя. Как буд­то есть какое‑то пра­вило, которое при­вяза­но к чему‑то цик­лично­му. Фор­мат токена не пов­торя­ется пос­ледова­тель­но, но пов­торя­ется вре­мя от вре­мени.

Итак, что может быть цик­личным и пов­торять­ся с изме­нени­ем вре­мени? Ну да, вре­мена года. А еще? На самом деле ответ был в самом воп­росе — это вре­мя. Часы, минуты, секун­ды.

Все ста­нет понят­но, как толь­ко пос­мотришь на вре­мя сер­вера. Имен­но тут начина­ется магия.

В отве­те всег­да есть заголо­вок Date.

Взлом API Django

Что дела­ем?

  1. Зап­рашива­ем пароль.
  2. Смот­рим, что в отве­те веб‑при­ложе­ния есть заголо­вок Date: Date: Mon, 08 Jun 2020 08:14:10 GMT.
  3. Перево­дим эту дату в timestamp (есть спе­циаль­ный сер­вис). В дан­ном слу­чае это будет 1591604050.
  4. Срав­нива­ем с получен­ным кодом — 50501015409368.

А теперь пос­мотри на циф­ры вни­матель­но.

Что­бы было понят­нее, посим­воль­но отсорти­руем оба зна­чения.

Код — 00001134555689. Заголо­вок Date — 0001145569.

Код вос­ста­нов­ления — 50501015409368, это вре­мя 1591604050 + 5038, где четыре циф­ры — либо что‑то слу­чай­ное, либо мил­лисекун­ды.

Циф­ры — забав­ная шту­ка. Вот мы зна­ем, из чего сос­тоит код, но уга­дать его не можем. Непонят­но, каким обра­зом фор­миру­ется код, потому что, даже если мы зна­ем все 14 сим­волов, у нас 87 178 291 200 вари­антов ком­бинации их перес­танов­ки.

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

В резуль­тате уда­лось соб­рать око­ло 150 токенов вос­ста­нов­ления и зна­чений вре­мени:

А теперь обра­тим­ся к ком­бинато­рике. Мы зна­ем, что у нас есть вре­мя (10 байт) и четыре циф­ры. Сор­тиров­ка стол­бцов про­исхо­дит по каким‑то пра­вилам. Так как это не фун­кция свер­тки, веб‑при­ложе­ние дол­жно видеть, по какому пра­вилу нуж­но раз­ложить ключ, что­бы получить иско­мое зна­чение (дату).

Поп­робу­ем поис­кать ано­малии токенов. Если пос­ледний сим­вол клю­ча нулевой, то мы видим некото­рую ано­малию сре­ди всех подоб­ных токенов, это чис­ла 51 и 23.

Нам нуж­но смот­реть имен­но на начало тай­мстем­па, потому что стро­ка 15932 ста­тич­на.

Взломать API Django

А вот что будет, ког­да пос­ледняя циф­ра — трой­ка.

Взломать Django API

Та­кая же ано­малия с 123 и 95.

Это зна­чит, что на самом деле генери­рует­ся 13 сим­волов timestamp, это вре­мя + мил­лисекун­ды, пос­леднее слу­чай­ное чис­ло отве­чает за алго­ритм перес­танов­ки.

Со­бира­ем дан­ные для каж­дого токена, получа­ем такой экс­пло­ит:

Для демонс­тра­ции мож­но взять любой email, который зарегис­три­рован на сай­те с этим движ­ком, и зап­росить вос­ста­нов­ление пароля.

Взломать API веб приложения

Ко­пиру­ем зна­чение заголов­ка Date и встав­ляем его в код экс­пло­ита.

Взломать API сайта

Вы­пол­нив экс­пло­ит, сох­раня­ем получен­ные токены и смот­рим в пись­мо. Убеж­даем­ся, что один из наших токенов сов­пада­ет с тем, что при­шел в пись­ме.

Ах да, что там у нас было еще сек­ретно­го?

Вер­немся к это­му скрин­шоту.

Взлом API сайта

Здесь id — это вре­мя.

Со­бира­ем skey:

Вре­мя + ID поль­зовате­ля?

Akey, судя по виду, — это SHA-256 от… вре­мени?

Все иден­тифика­торы, вклю­чая сек­реты, зна­чения которых дол­жны быть крип­тогра­фичес­ки стой­кими и уни­каль­ными, — это вре­мя. Счи­тать ли это бэк­дором? Не знаю.

Как защититься от такой ата­ки на API Django

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

Как это может выг­лядеть? Для каж­дого домена генери­рует­ся спе­циаль­ный ключ, который шиф­рует зна­чение timestamp и рас­шифро­выва­ет стро­ки обратно.

Та­ким обра­зом на бэкен­де текущая архи­тек­тура будет про­дол­жать работать в том же виде, что и сей­час, но все стро­ки с датой замене­ны крип­тостой­кими хешами вида 71ccca0995eebbc9315547f2ca76ee67 (или cczKCZXuu8kxVUfyynbuZw==, кому как боль­ше нра­вит­ся), которые не рас­шифро­вать без клю­ча. Ну или мож­но прос­то генерить крип­тостой­кие стро­ки.

Спасибо, i_bo0om, за интересный райтап.

ПОЛЕЗНЫЕ ССЫЛКИ:

Дима (Kozhuh)

Эксперт в кибербезопасности. Работал в ведущих компаниях занимающихся аналитикой компьютерных угроз. Анонсы новых статей в Телеграме.

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