Се­год­ня рас­ска­жем, как не погиб­нуть в руч­ном поис­ке IDOR-уяз­вимос­тей в коде тысяч хен­длов API. В ход пой­дут пра­вила Semgrep в режиме join. Затем поп­робу­ем вос­ста­новить логику биз­нес‑сце­нари­ев по арте­фак­там от QA-авто­тес­тов и обер­нуть ее в шаб­лоны nuclei.

А в кон­це подума­ем, как не задушить раз­работ­ку и про­вер­ки авто­риза­ции в сер­висах лег­ким выбором по умол­чанию с auth-дирек­тивами в GraphQL.

За помощь в под­готов­ке матери­ала бла­года­рю Гри­гория Про­хоро­ва и Андрея Кана.

Это во мно­гом про­дол­жение темы прош­логод­ней статьи, где было уде­лено мно­го вни­мания руч­ному поис­ку проб­лем с IDOR через ана­лиз логов и быс­трые про­вер­ки в Burp, поиск проб­лемных фун­кций в коде с помощью базовых пра­вил Semgrep.

Так­же статья ста­ла при­зером лет­него Pentest Award 2025 и лег­ла в осно­ву одно­имен­ного док­лада на про­шед­шем недав­но ZeroNights 2025.

 

Проблема

Раз­работ­чик в реали­зации API-хен­дла забыл про­верить при­над­лежность объ­екта текуще­му авто­ризо­ван­ному поль­зовате­лю при обра­щении к это­му объ­екту по ID.

С иден­тифика­тора­ми типа int все сов­сем пло­хо. Прос­то добав­ляешь по еди­нице к парамет­ру зап­роса, и получишь чужой объ­ект, а может, и смо­жешь его поменять. Но не мно­гим луч­ше дела с иден­тифика­тора­ми UUID. Вро­де бы в лоб их не под­берешь, но есть кей­сы, ког­да мы можем подоб­рать и их:

  • биз­нес‑сце­нарии (сок­ращате­ли URL, слож­ный ID в обмен на ИНН или телефон);
  • внут­ренние логи сер­висов и админки, отда­ющие ID объ­ектов;
  • стра­ницы, попав­шие в Internet Archive.

По­это­му одной слу­чай­ной про­вер­ки недос­таточ­но — нам нуж­на мас­шта­биру­емая защита.

Но есть и вто­рая боль­шая проб­лема, в которой живут, кажет­ся, все аппсе­ки мира.

У нас тысячи хен­длов, и десят­ки новых добав­ляют­ся каж­дый месяц. Мы не можем прог­нать все новые биз­нес‑сце­нарии и вруч­ную про­верять в Burp Repeater каж­дый подоз­ритель­ный зап­рос от дру­гого аутен­тифици­рован­ного поль­зовате­ля.

Или все‑таки можем?

 

Ищем в коде с Semgrep в join mode

Что­бы луч­ше заш­ла логика опи­сан­ных далее пра­вил, нач­нем с лан­дшаф­та, в котором мы реша­ем нашу задачу.

Зап­росы с кли­ент­ско­го бра­узе­ра летят в сер­верный API (ReactJS, Svelte, что‑то на фрон­тендер­ском), там кли­ент­ский инпут перек­ладыва­ется в зап­росы GraphQL и летит вмес­те с авто­риза­цион­ными куками в сер­вис GraphQL на Go, где выпол­няет­ся про­вер­ка авто­риза­ции поль­зовате­ля, пос­ле чего сер­вис зап­рашива­ет дан­ные у внут­ренних сер­висов по gRPC и воз­вра­щает кли­енту типизи­рован­ный ответ.

Как это выг­лядит в коде? Где‑то в папоч­ке API лежат схе­мы GraphQL, име­нован­ные зап­росы и мутации («руч­ки» на язы­ке GraphQL).

А вот вари­ант с типизи­рован­ным вво­дом и выводом.

А вот резол­вер на Go, реали­зующий биз­нес‑логику каж­дой фун­кции, про­вер­ку авто­риза­ции кли­ента (на дос­туп к объ­екту), поход во внут­ренние сер­висы за дан­ными или изме­нени­ями и воз­врат отве­та.

И хорошо бы нам тут взять кли­ента из сес­сии и про­верить на соот­ветс­твие объ­екту сра­зу.

Или передать даль­ше вглубь, что­бы про­вери­ли там.

Мы хотим най­ти все мес­та в нашем коде, где про такие конс­трук­ции забыва­ют. Для это­го насоби­раем semgrep.yml, похожий на наш изна­чаль­ный резол­вер, толь­ко без вызова авто­риза­ции (через pattern-not на вызов сес­сии).

Нор­маль­ную конс­трук­цию пра­вило не тро­гает.

Но конс­трук­цию без вызова сес­сии поль­зовате­ля пра­вило цеп­ляет для нас.

Об­рати вни­мание, что здесь имя резол­вера и имя струк­туры вво­да ложат­ся в так называ­емые метапе­ремен­ные $FUNCNAME и $INPUT, дос­тупные нам даль­ше.

За­тем нам нуж­но в новом пра­виле выб­рать толь­ко те струк­туры вво­да, которые содер­жат что‑то похожее на иден­тифика­торы.

Ме­тапе­ремен­ные вто­рого пра­вила тоже зацепи­ли для нас кон­крет­ные зна­чения — и инпу­та, и най­ден­ных полей.

Да­лее исполь­зует­ся режим join в Semgrep. Он поз­воля­ет объ­еди­нять сра­баты­вания раз­ных пра­вил по общим метапе­ремен­ным и ана­лизи­ровать их как еди­ный набор дан­ных, а не как незави­симые резуль­таты отдель­ных пра­вил.

Мы объ­еди­няем эти два набора дан­ных по име­ни вход­ной струк­туры, в одном будут все резол­веры без авто­риза­ции, а во вто­ром — одно­имен­ные инпу­ты с иден­тифика­тора­ми в струк­туре дан­ных.

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

На­мерен­но тут опус­тим под­робнос­ти о работе с рекур­сией в типах инпу­тов (Semgrep впол­не может в рекур­сию), наг­рузкой (в каких‑то боль­ших про­ектах эта самая рекур­сия ока­залась не луч­шей иде­ей), об осо­бен­ностях пар­сера Semgrep.

И луч­ше перей­дем ко вто­рой час­ти про поиск проб­лем в динами­ке.

 

Ищем в динамике с nuclei и артефактами от QA

Вер­немся к общей схе­ме сис­темы. В нее добав­ляет­ся слой кон­тро­ля качес­тва (QA): в GitLab запус­кают­ся end-to-end-авто­тес­ты, которые от име­ни авто­мати­чес­ки соз­данных тес­товых поль­зовате­лей обра­щают­ся к кли­ент­ским API на тес­товом окру­жении. Все отправ­ленные зап­росы и получен­ные отве­ты сох­раня­ются в хра­нили­ще арте­фак­тов Allure.

Продолжение доступно только участникам

Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».

Присоединяйся к сообществу «Xakep.ru»!

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии