<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.3">Jekyll</generator><link href="http://blog.secsem.ru/feed/ru.xml" rel="self" type="application/atom+xml" /><link href="http://blog.secsem.ru/" rel="alternate" type="text/html" /><updated>2025-07-03T20:08:08+03:00</updated><id>http://blog.secsem.ru/feed/ru.xml</id><title type="html">The SecLab Blog | Ru</title><subtitle>The blog of the Security Lab @ Lomonosov Moscow State University</subtitle><entry xml:lang="ru"><title type="html">Relwarc: третья часть рассказа про алгоритм анализа клиентского JS для майнинга HTTP-запросов</title><link href="http://blog.secsem.ru/ru/mining-requests-from-js-with-static-analysis-part3/" rel="alternate" type="text/html" title="Relwarc: третья часть рассказа про алгоритм анализа клиентского JS для майнинга HTTP-запросов" /><published>2024-08-31T00:00:00+03:00</published><updated>2024-08-31T00:00:00+03:00</updated><id>http://blog.secsem.ru/ru/mining-requests-from-js-with-static-analysis-part3</id><content type="html" xml:base="http://blog.secsem.ru/ru/mining-requests-from-js-with-static-analysis-part3/"><![CDATA[<p>В этом посте (продолжающем эти: <a href="../mining-requests-from-js-with-static-analysis">1</a>, <a href="../mining-requests-from-js-with-static-analysis-part2">2</a>) расскажу про поддержку библиотек, трансформацию вызовов в HTTP-запросы, о том, что в итоге получилось, какие выводы мы сделали, и кое-что ещё.</p>

<p>Но обо всём по порядку.</p>

<h2 id="как-он-работает--ещё-про-алгоритм">Как он работает — ещё про алгоритм</h2>

<p>Алгоритм ищет в коде вызовы функций, отправляющие запросы на сервер. Но как, глядя на вызов, понять,
что он отправляет на сервер запрос? И как понять, какой именно запрос отправляется?
По идее, если вызываемая функция определена где-то в коде, можно было бы проанализировать
её код, и всё понять. Но в реальности это может быть не так-то просто — многие современные
JS-библиотеки устроены довольно сложно, а JavaScript-код тяжело анализировать статически.
Как <a href="../mining-requests-from-js-with-static-analysis#существующие-анализаторы">упоминалось в первом посте</a>, даже с библиотекой jQuery, самой популярной в интернете, у
существующих статических анализаторов есть проблемы.
Поэтому мы используем сигнатуры библиотечных вызовов и модели библиотек, встроенные в анализатор.</p>

<h3 id="сигнатуры-библиотечных-вызовов">Сигнатуры библиотечных вызовов</h3>

<p>В анализатор встроен набор сигнатур, по которым он понимает, что вызов в коде — это вызов
отправляющей запрос на сервер функции. Самые простые сигнатуры — те, которые мы добавили
изначально, и которые активно используем до сих пор — работают просто по названиям функций.
То есть, в анализатор просто встроено знание о том, что, если вызывается метод <code class="language-plaintext highlighter-rouge">$.ajax</code>, с именем объекта <code class="language-plaintext highlighter-rouge">$</code> и именем метода <code class="language-plaintext highlighter-rouge">ajax</code>,
то он отправляет запрос на сервер. Точно так же,
как анализатор знает, что отправкой запросов занимается функция <code class="language-plaintext highlighter-rouge">fetch</code> и методы экземпляров
класса <code class="language-plaintext highlighter-rouge">XMLHttpRequest</code>. Мы добавили такие сигнатуры для ряда популярных библиотек: jQuery, Angular,
Axios. Благодаря ним анализатор, увидев <code class="language-plaintext highlighter-rouge">$http.post(...)</code>, <code class="language-plaintext highlighter-rouge">axios(...)</code> или <code class="language-plaintext highlighter-rouge">jQuery.post(...)</code>, поймёт,
что это места отправки запросов на сервер. В него заложен список имён. Могут ли эти сигнатуры не сработать?
Конечно. Достаточно было бы переименовать библиотечный объект или функцию, и вызов не найдётся.
И всё же, нередко их достаточно. Разработчики навряд ли сами будут переименовывать библиотечные объекты:
для читаемости кода лучше, чтобы вызов имел привычный вид. Тем не менее, функции и объекты библиотек
могут быть переименованы при упаковке бандлерами, особенно в сочетании с минификацией.
А некоторые библиотеки вообще не рассчитаны на то, чтобы ими пользовались через какой-то глобальный
объект или функцию со стандартным названием. Поэтому в анализатор встроены и
более сложные виды сигнатур — например, такие, которые распознают функцию по виду
её кода (AST-сигнатуре на тело функции), и дальше, в местах использования, матчат её по значению, а не по имени.</p>

<h3 id="модели-библиотек">Модели библиотек</h3>

<p>Хорошо, вот мы нашли вызовы, постарались вычислить их аргументы. Но имя функции
вместе с аргументами это, хоть и уже полезно, ещё не достаточно для сканера, чтобы искать уязвимости.
Всё-таки ему нужны готовые HTTP-запросы. Сейчас мы решаем эту задачу довольно-таки «в лоб»:
для всех функций, для которых у нас есть сигнатуры, мы написали руками код,
который примерно моделирует их работу, то есть выданный анализатором набор
аргументов преобразует в HTTP-запрос, который был бы отправлен таким вызовом с такими аргументами.
Тут есть такая особенность, что эти аргументы не всегда полностью
конкретные — какие-то значения могут быть неизвестными.
При таких неполных данных мы хотели бы не падать с исключением (что вполне имеет право делать реальная библиотека),
а всё таки выдать какой то результат, сохранив настолько много информации, насколько это возможно.
В самом плохом случае URL-адрес может быть полностью неизвестным (или всё тело запроса целиком) — в
этом случае анализатор такой запрос не выдаёт. Подробнее эту часть описывать тут не хочется, она довольно скучная:
каждая «модель» библиотеки пытается учитывать разные нюансы работы библиотеки,
а также разные случаи когда данные получились неконкретными. Лучше гляньте код.</p>

<p>Потому что мы его выложили.</p>

<h2 id="relwarc"><span class="relw">Relwarc</span></h2>

<p>Мы выложили код нашего анализатора в open source. А также сделали публично-доступный
сервис <a href="https://relwarc.solidpoint.net/">relwarc.solidpoint.net</a>, позволяющий запустить анализатор на JS-коде.
Мы решили назвать
анализатор <span class="relw">Relwarc</span> — то есть «сrawler» наоборот. Потому
что он решает ту же задачу, что и краулер, но наоборот: пытается определить,
какие запросы клиентский код посылает на сервер, но при этом не имитирует действия пользователя на странице, а
смотрит на сам код. Работает не динамически, а статически.</p>

<p>Версию кода, которая примерно соответствует тому, что описано в этом и предыдущих постах,
можно найти на GitHub, вот по такой ссылке:</p>

<p><a href="https://github.com/seclab-msu/relwarc/tree/vanilla-algorithm">https://github.com/seclab-msu/relwarc/tree/vanilla-algorithm</a></p>

<p>Она же соответствует описанию алгоритма из нашей <a href="https://journals.tsu.ru/pdm/&amp;journal_page=archive&amp;id=2153&amp;article_id=48226">научной статьи 2021 года</a>.</p>

<p>Сервис <a href="https://relwarc.solidpoint.net/">relwarc.solidpoint.net</a> умеет анализировать как отдельные JS-файлы, так
и веб-страницы. Он предоставляет API и веб-UI, позволяющий вручную позапускать анализатор на чём-нибудь, поиграться с ним.
Попробуйте им воспользоваться! Может быть он найдёт для вас новые серверные ручки в каком-то коде, который нет времени или сил
разбирать вручную. Если найдёте баг или будет идея что улучшить — смело создавайте issue в репозитории на GitHub (или сразу
pull-реквест)!</p>

<h2 id="что-в-итоге">Что в итоге</h2>

<p>Итак, в этом и двух предыдущих постах описан наш алгоритм анализа JS. Это и есть полный алгоритм, который мы разработали?
Ну, на самом деле не совсем. Можно заметить, что ссылка на GitHub выше ведёт на тег, который соответствует не самому
новому коммиту в репозитории. С тех пор, как мы разработали эту, базовую версию, мы доработали в алгоритме немало всего — определение
возвращаемых значений функций, поддержку классов, бандлеров, ещё ряд улучшений. Чтобы описать их все и трёх постов маловато и,
к тому же, алгоритм, описанный в статье, уже вполне себе работал, и мы применяли его на практике.</p>

<h3 id="метрика-качества">Метрика качества</h3>

<p>Ну хорошо, вот мы делали этот алгоритм, реализовали там анализ цепочек вызвовов, добавляли
такие улучшения и эдакие — и что же в итоге?
Что мы получили? Этот анализатор работает вообще? Он нормально работает? Находит что-то?
Чтобы ответить на этот вопрос, нам хорошо было бы иметь какой-то бенчмарк, позволяющий
мерить качество работы алгоритма. Скажем, набор приложений или хотя бы веб-страниц, реальных
или похожих на современные реальные, где из клиентского JS отправлялись бы запросы, и
для которых был бы известен правильный ответ — какие именно запросы отправляются.
Тогда мы могли бы сравнивать набор правильных запросов с тем, что выдал наш анализатор,
и считать метрику качества. Мы не нашли такого бенчмарка (из похожего
нашёлся <a href="https://github.com/bedirhan/wivet">WIVET</a>, но он всё-таки немного про другое).
Поэтому мы решили сделать свой! Мы создали датасет из страниц реальных приложений, для которых
мы вручную разметили, к каким серверным эндпоинтам отправляет запросы их JS-код (точнее, там
есть страница тестового приложения, но только одна — это главная страница <a href="https://github.com/juice-shop/juice-shop">Juice Shop</a>).
Почти все страницы датасета это страницы случайных сайтов из интернета — с сайтов из списка
Alexa Top 1 Million или из публичных Bug Bounty программ. Этот датасет мы тоже решили опубликовать, вот он:</p>

<p><a href="https://github.com/seclab-msu/ajax-page-dataset">https://github.com/seclab-msu/ajax-page-dataset</a></p>

<p>Кроме самих страниц и разметки (наборов «правильных» запросов, которые должны найтись) в этом репо есть
скрипты для прогона по нему анализатора и подсчёта метрик. Главная метрика качества, которую мы используем —
это среднее покрытие. То есть считаем для каждой страницы процент найденных запросов и берём среднее значение этих процентов между
всеми страницами. Сейчас для последней версии анализатора среднее покрытие около 40%.</p>

<h3 id="в-заключение">В заключение</h3>

<p>Задача статического анализа в общем случае неразрешима, а анализировать
JavaScript-код особенно тяжело. Так что сделать анализатор, который работал бы
идеально, полно и точно, в 100% случаев, невозможно. Но мы можем постараться
сделать алгоритм, который будет работать в большинстве реальных случаев. Стараться
дотянуть процент реальных приложений, на которых он работает, до 80% или 90%. Всегда
можно будет сделать хитрый пример кода, на котором алгоритм сломается — ну и что с того,
если такой код не будет встречаться почти никогда в реальной жизни? Чтобы понять, как сделать
такой анализатор, который будет работать в реальности, нам пришлось не только программировать,
но и заниматься исследованием реального кода. Куча времени ушла на изучение того,
как устроена отправка запросов и потоки данных, от которых отправляемые запросы зависят.</p>

<p>Сейчас среднее покрытие на нашем датасете составляет 40% — это много или мало? Конечно, было бы классно иметь
скор в те самые 80% или 90%. Но всё-таки JavaScript анализировать статически непросто, а мы
старались сделать наш датасет более-менее представительным — то есть, чтобы, глядя на скор
на нём, можно было составлять какое-то представление о качестве на случайно взятом случайном сайте из интернета. Ни один из существующих академических статических
анализаторов JS не справляется с реальными современными веб-страницами. Следуя принципу, упомянутому
в первом посте, мы решили начать с чего-то простого, чтобы не сделать сложную махину, которая так никогда и не сработает
на чём-то реальном (или которую мы даже не допишем до конца). Начать с чего-то простого, и затем
итеративно это улучшать, повышая скор и повышая сложность. Поддерживая новые фичи, новые особенности кода.
В результате такой, более простой и учитывающий особенности реального кода анализатор (заточенный под решаемую задачу)
способен справляться с реальными страницами, и часто у него получается обработать их довольно быстро — меньше чем
за минуту, иногда за несколько минут.</p>

<p>Значит ли это, что этот анализатор всегда теперь надо развивать только итеративно, понемногу добавляя
или меняя что-то? На самом деле нет — иногда надо всё-таки переписывать софт заново, передылывая
его существенно. Быть может, построив алгоритм на немного других принципах. Но ценными останутся
показатели на датасете, то, какие запросы находил прошлый алгоритм, и за какое время. То, какие тесты он проходил.
То есть полученные знания. Если при разработке нового алгоритма он в чём-то будет уступать старому, всегда
можно будет глянуть — а почему старому это удавалось? За счёт каких принципов работы он справлялся с какой-то конструкцией
кода.</p>

<p>И, наконец, этот статический анализатор не заменяет, а дополняет динамические краулеры. Когда
мы начали его разрабатывать, у нас была идея о том, что есть запросы, которые трудно стриггерить действиями в интерфейсе.
Но всегда остаются и такие запросы, которые динамически найти легче. Потребовать 99% покрытия значило бы
потребовать, чтобы статический анализатор везде полностью справлялся сам. Может такой алгоритм и можно сделать, но
сейчас явно видно, что где-то разумнее применить его, а где-то проще будет воспользоваться динамическим анализом.
Ни один из этих методов не является панацеей, и наибольшее покрытие получится, если применить и то и другое.</p>]]></content><author><name>Даниил Сигалов (@asterite3)</name></author><category term="ru" /><summary type="html"><![CDATA[В этом посте про поддержку библиотек, трансформацию вызовов в HTTP-запросы, о том, что в итоге получилось, какие выводы мы сделали, и кое-что ещё.]]></summary></entry><entry xml:lang="ru"><title type="html">Майнинг HTTP-запросов из клиентского JS с помощью статического анализа — часть 2</title><link href="http://blog.secsem.ru/ru/mining-requests-from-js-with-static-analysis-part2/" rel="alternate" type="text/html" title="Майнинг HTTP-запросов из клиентского JS с помощью статического анализа — часть 2" /><published>2024-05-08T00:00:00+03:00</published><updated>2024-05-08T00:00:00+03:00</updated><id>http://blog.secsem.ru/ru/mining-requests-from-js-with-static-analysis-part2</id><content type="html" xml:base="http://blog.secsem.ru/ru/mining-requests-from-js-with-static-analysis-part2/"><![CDATA[<p>В <a href="/ru/mining-requests-from-js-with-static-analysis">предыдущем посте</a> (гляньте его, если ещё не)
мы посмотрели на базовую идею работы алгоритма и даже построили небольшой анализатор, основанный на ней. В этом посте
расскажу ещё часть принципов устройства нашего алгоритма, включая поиск значений аргументов функций и хендлинг объектов типа инстансов <code class="language-plaintext highlighter-rouge">XMLHttpRequest</code>, а ещё кое-что про поддерживаемые операции.</p>

<h2 id="и-всё-таки-как">И всё таки, как?</h2>

<p>Как уже говорил, идейно алгоритм работает так же, как то, что было описано в прошлом посте. Но есть ряд отличий.</p>

<h3 id="поддерживаемые-типы-данных-и-операции">Поддерживаемые типы данных и операции</h3>

<p>Анализатор поддерживает ряд типов данных, помимо строк и <code class="language-plaintext highlighter-rouge">UNKNOWN</code>. Среди них объекты, массивы, числа, <code class="language-plaintext highlighter-rouge">boolean</code>, ну и
<code class="language-plaintext highlighter-rouge">undefined</code> тоже (да-да, напомню, что для <code class="language-plaintext highlighter-rouge">undefined</code> в JavaScript есть отдельный тип). То есть для этих типов анализатор
превращает в значения их <em>литералы</em> (в том числе всякие <code class="language-plaintext highlighter-rouge">[1, 2, 3]</code> и <code class="language-plaintext highlighter-rouge">{foo: "bar", abc: [true]}</code>) и вычисляет результаты
некоторых операций с ними. В том числе чтение поля объекта/массива (<code class="language-plaintext highlighter-rouge">a[x]</code>, <code class="language-plaintext highlighter-rouge">ob.prop</code>) и запись поля (<code class="language-plaintext highlighter-rouge">a[x] = 123</code>, <code class="language-plaintext highlighter-rouge">ob.prop = v</code>).</p>

<p>Также поддерживаются <em>функциональные</em> значения — для объявлений функций и функциональных выражений (литералов, если
угодно) анализатор тоже вырабатывает специальные значения, которые могут храниться в его <code class="language-plaintext highlighter-rouge">memory</code>.</p>

<p>То есть для вот такого кода</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">f</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">return</span> <span class="mi">123</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">var</span> <span class="nx">g</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">x</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">x</span> <span class="o">*</span> <span class="mi">25</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">var</span> <span class="nx">k</span> <span class="o">=</span> <span class="nx">f</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">l</span> <span class="o">=</span> <span class="nx">g</span><span class="p">;</span>
</code></pre></div></div>

<p>Анализатор поймёт, что переменные <code class="language-plaintext highlighter-rouge">f</code> и <code class="language-plaintext highlighter-rouge">k</code> указывают на первую функцию (которая возвращает <code class="language-plaintext highlighter-rouge">123</code>), а <code class="language-plaintext highlighter-rouge">g</code> и <code class="language-plaintext highlighter-rouge">l</code> — на
вторую (которая возвращает свой аргумент, умноженный на 25).</p>

<p>Эти функциональные значения содержат в себе ссылку на код соответствующей функции (точнее, на её AST). Это пригодится
для межпроцедурного анализа, про него расскажу дальше.</p>

<p>Кстати, можно заметить, что значения, про которые знает анализатор, могут не только непосредственно в <code class="language-plaintext highlighter-rouge">memory</code> быть
записаны — поскольку поддерживаются объекты и массивы, значения могут «сидеть» в полях объектов/массивов. Возможно, на
какой-то глубине (с несколькими уровнями вложенности).</p>

<p>Ещё анализатор умеет вычислять результаты вызовов некоторых встроенных в язык функций, включая методы работы со
строками (<code class="language-plaintext highlighter-rouge">.concat(...)</code>, <code class="language-plaintext highlighter-rouge">.substr(...)</code> и т. д.). Кроме того, поддерживаются <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals">template strings</a>. Это вот такие штуки:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s2">`the value is </span><span class="p">${</span><span class="nx">x</span><span class="p">}</span><span class="s2">`</span>
</code></pre></div></div>

<p>И, как и раньше, если результат какой-то операции вычислить не удаётся, то результатом будет <code class="language-plaintext highlighter-rouge">UNKNOWN</code>. Такое может быть, если операция не поддерживается анализатором, или если неизвестно участвующее в операции значение. К примеру, если
надо вычислить <code class="language-plaintext highlighter-rouge">x = a[3]</code>, а у <code class="language-plaintext highlighter-rouge">a</code> значение <code class="language-plaintext highlighter-rouge">UNKNOWN</code>, то и <code class="language-plaintext highlighter-rouge">x</code> станет <code class="language-plaintext highlighter-rouge">UNKNOWN</code>.</p>

<h3 id="многопроходный-анализ">Многопроходный анализ</h3>

<p>Алгоритм анализа, который был описан в предыдущем посте, делает один обход AST-дерева в глубину — это более-менее
соответствует проходу по коду в прямом порядке «от начала до конца». Но в реальности инструкции программы не всегда
выполняются в том же порядке, в котором они написаны в коде. Функции, например, не обязаны вызываться в том порядке, в
котором они в коде написаны. Что если нам попадётся какой-то такой код:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">f</span><span class="p">()</span> <span class="p">{</span>
    <span class="nf">fetch</span><span class="p">(</span><span class="nx">baseURL</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">/users/list</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>

<span class="nx">baseURL</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">/blog/api</span><span class="dl">'</span><span class="p">;</span>
<span class="nf">f</span><span class="p">();</span>
</code></pre></div></div>
<p>Здесь без объявления используется глобальная переменная <code class="language-plaintext highlighter-rouge">baseURL</code>.
Или такой:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">createRequestService</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nx">apiVer</span><span class="p">,</span> <span class="nx">section</span><span class="p">;</span>
    <span class="kd">function</span> <span class="nf">getSettings</span><span class="p">()</span> <span class="p">{</span>
        <span class="nf">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">/api/</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">apiVer</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">/</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">section</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">/settings</span><span class="dl">'</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="kd">function</span> <span class="nf">notifyOnline</span><span class="p">()</span> <span class="p">{</span>
        <span class="nf">fetch</span><span class="p">(</span>
            <span class="dl">'</span><span class="s1">/api/</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">apiVer</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">/</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">section</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">/status?online=true</span><span class="dl">'</span><span class="p">,</span>
            <span class="p">{</span> <span class="na">method</span><span class="p">:</span> <span class="dl">'</span><span class="s1">POST</span><span class="dl">'</span> <span class="p">}</span>
        <span class="p">);</span>
    <span class="p">}</span>
    <span class="kd">function</span> <span class="nf">initialize</span><span class="p">()</span> <span class="p">{</span>
        <span class="nx">apiVer</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">2.1</span><span class="dl">'</span><span class="p">;</span>
        <span class="nx">section</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">shop</span><span class="dl">'</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="p">{</span> <span class="nx">getSettings</span><span class="p">,</span> <span class="nx">notifyOnline</span><span class="p">,</span> <span class="nx">initialize</span> <span class="p">};</span>
<span class="p">}</span>
</code></pre></div></div>

<p>В обоих случаях переменные задаются по коду дальше, чем используются. Можно сказать, что потоки данных «текут» не
всегда от начала программы к концу, иногда они «текут обратно». Для того чтобы с таким справляться, анализаторы делают
несколько проходов анализа по коду. Чтобы алгоритм был легковесным, мы решили начать с двух
проходов — предварительного, на котором анализатор постарается пособирать заданные значения везде где найдутся, и,
следующего, основного. Причём обрабатывает AJAX-вызовы анализатор только на основном проходе, а предварительный используется только
для сбора значений в программе. Для случаев в двух сниппетах кода выше этого уже хватит. Для каких-то более сложных
случаев уже нет.</p>

<p>Вообще, классические статические анализаторы, построенные на стандартных принципах, делают так — они
итерируются по коду, собирая на каждом проходе всё больше информации о его работе, и делают это до тех пор, пока процесс
не сойдётся — точнее, пока очередной проход по коду не добавит никакой новой информации. Чтобы это всегда срабатывало,
надо чтобы алгоритм анализа был построен особым образом — чтобы процесс гарантированно сходился. Даже когда он сходится,
бывает такое, что сходится слишком долго.
Кстати, напомню, что узнать больше про классические алгоритмы анализа можно по
<a href="../mining-requests-from-js-with-static-analysis#ссылки">ссылкам из прошлого поста</a>.</p>

<p>По тому, как сделан наш алгоритм, можно увидеть, что идейно он заточен под нахождение значений, которые на самом деле
«константные» — которые задаются один раз, причём задаётся какое-то конкретное значение (зафиксированное в коде, а не пришедшее извне). Делаем расчёт на то, что
«фиксированные» части запроса, которые и определяют, какая серверная ручка задействуется, будут заданы таким образом.
Переменные же части, параметры запросов (юзернеймы, поисковые запросы, количества товаров и т. д.) в коде не найдутся,
ну может они нам и не нужны.</p>

<h3 id="межпроцедурный-анализ">Межпроцедурный анализ</h3>

<p>Процесс отправки запроса часто вовлекает в себя работу нескольких функций. Надо понимать, как данные передаются между
ними. При разборе разных сэмплов реального кода, у нас это чаще всего выливалось в вопрос «какие данные приходят из аргументов
функции?». Мы решили не делать полноценный межпроцедурный анализ для всех функций программы, так как решили, что это
слишком тяжеловесно, а вместо этого решили сделать таргетированный анализ только для интересующих точек программы (позже
мы, кстати, несколько пересмотрели это решение, но об этом в другой раз). Возвращаемые значения обычных, объявленных
пользователем, функций мы решили для начала не искать — будем считать что они возвращают <code class="language-plaintext highlighter-rouge">UNKNOWN</code>. Сосредоточимся
на аргументах.</p>

<p>Чтобы анализ получился таргетированным и искал аргументы только там, где нужно, надо как-то понять: а где же нужно.
Для этого добавлено ещё одно специальное значение <code class="language-plaintext highlighter-rouge">FROM_ARG</code>. Оно в целом аналогично <code class="language-plaintext highlighter-rouge">UNKNOWN</code>, то есть это такое уникальное
значение, которое обозначает что-то, для нас неизвестное. Но оно дополнительно несёт информацию о том, что значение пришло
именно из формальных аргументов функции. При анализе тел функций анализ будет считать, что значения <em>формальных аргументов</em>
это <code class="language-plaintext highlighter-rouge">FROM_ARG</code>.</p>

<p><img src="/images/fromarg1.png" alt="Формальные аргументы функции изначально получают значение FROM_ARG" /></p>

<p>Если далее это значение дойдёт до места вызова AJAX-функции (<code class="language-plaintext highlighter-rouge">$.ajax</code> например),
то есть среди её аргументов будет <code class="language-plaintext highlighter-rouge">FROM_ARG</code>,
будет сделан вывод, что отправляемый запрос зависит от формальных аргументов функции, сделавшей AJAX-вызов. А значит,
надо поискать вызовы этой функции и то, какие там в неё передаются аргументы.</p>

<p><img src="/images/fromarg2.png" alt="Значение FROM_ARG дошло до AJAX-вызова" /></p>

<p>Как же будем искать? У нас есть наша мапа <code class="language-plaintext highlighter-rouge">memory</code>, в которой записаны значения переменных. При этом, как говорилось выше,
анализатор поддерживает функциональные значения. Возьмём функцию, чьи вызовы мы хотим найти (функцию <code class="language-plaintext highlighter-rouge">f</code> в данном случае),
пройдёмся по <code class="language-plaintext highlighter-rouge">memory</code>, взяв оттуда все переменные, указывающие на функциональное значение этой функции <code class="language-plaintext highlighter-rouge">f</code>. Ну то есть
в простейшем случае это будет просто переменная <code class="language-plaintext highlighter-rouge">f</code>, но в теории могут быть и другие переменные, если, например, было
присваивание типа <code class="language-plaintext highlighter-rouge">other_func = f</code>. Для каждой такой переменной пройдёмся снова по коду, ища места вызова, где на месте
вызываемой функции стоит эта переменная — их будем считать местами вызова этой искомой функции <code class="language-plaintext highlighter-rouge">f</code>. Вот для них нам хорошо
бы понять, какие аргументы в вызов передаются. Предположим, что найденное место вызова находится в какой-то другой функции,
фукнции <code class="language-plaintext highlighter-rouge">g</code>.</p>

<p><img src="/images/fromarg-fg.png" alt="Место вызова `f` нашлось в функции `g`" /></p>

<p>Проделаем анализ тела этой функции <code class="language-plaintext highlighter-rouge">g</code> — то есть обход её тела в глубину таким же образом, как и раньше, но, когда дойдём
до искомого места вызова <code class="language-plaintext highlighter-rouge">f</code>, то вычислим аргументы вызова и перейдём на начало кода <code class="language-plaintext highlighter-rouge">f</code>. В качестве значений формальных
аргументов <code class="language-plaintext highlighter-rouge">f</code> на этот раз возьмём не <code class="language-plaintext highlighter-rouge">FROM_ARG</code>, а фактические значения, которые только что вычислили — и дальше будем
выполнять анализ, обходя AST-дерево <code class="language-plaintext highlighter-rouge">f</code> от его корня. Таким образом мы «проэмулируем» вызов <code class="language-plaintext highlighter-rouge">f</code> с вычисленными аргументами.
Теперь, когда, обходя код <code class="language-plaintext highlighter-rouge">f</code>, мы дойдём до AJAX-вызова, то имеем шанс найти уже более точные аргументы этого вызова.</p>

<p><img src="/images/fromarg-fg-reached.png" alt="Анализ места вызова позволил найти более точные аргументы" /></p>

<p>Такой анализ повторим для всех найденных мест вызова.</p>

<p><img src="/images/fromarg-fgk.png" alt="Анализ также будет проделан для другого места вызова f" /></p>

<p>Ещё может быть, что вызов нашёлся вне какой-либо функции, на top level. Тогда проделаем анализ всего глобального
контекста, ещё один обход всего кода — то есть обработаем этот случай так, как будто весь код находится внутри одной
гигантской всеобъемлющей функции. Это довольно неоптимально, но для простоты используем сейчас такой вариант.</p>

<aside>
💡 Side note: <details>
    <summary>чуть подробнее о том, что значит «до аргументов AJAX-вызова дойдёт значение <code class="language-plaintext highlighter-rouge">FROM_ARG</code>»</summary>
    Безусловно, возможно, что один из аргументов вызова это в чистом виде <code class="language-plaintext highlighter-rouge">FROM_ARG</code>. Но нередко возникают другие варианты:
        <ul>
            <li>Алгоритм анализа поддерживает массивы и объекты, соответственно, аргументом AJAX-вызова может оказаться
            массив или объект, у которого в одном из элементов/полей записано значение <code class="language-plaintext highlighter-rouge">FROM_ARG</code>. Именно такой случай, кстати, был на скриншотах выше. Чтобы такое обнаруживать, структуры данных, попадающие в аргументы вызова,
            рекурсивно обходятся в поисках <code class="language-plaintext highlighter-rouge">FROM_ARG</code>.</li>
            <li>Нередко данные преобразуются с помощью строковых операций. Значение <code class="language-plaintext highlighter-rouge">FROM_ARG</code> при участии в такой операции приведётся к строке <code class="language-plaintext highlighter-rouge">"FROM_ARG"</code>. При поиске значения <code class="language-plaintext highlighter-rouge">FROM_ARG</code> в аргументах вызова, помимо сравнения на тождество с <code class="language-plaintext highlighter-rouge">FROM_ARG</code>, ещё делается для всех строк проверка на то, не входит ли в них подстрока <code class="language-plaintext highlighter-rouge">"FROM_ARG"</code>.</li>
        </ul>
    </details>
</aside>

<h4 id="цепочки-вызовов">Цепочки вызовов</h4>

<p>При поиске аргументов может потребоваться проанализировать цепочку больше чем из двух функций. Допустим, у нас была
какая-то функция <code class="language-plaintext highlighter-rouge">f</code>, которая делала AJAX-вызов, мы нашли её вызов в какой-то другой функции (<code class="language-plaintext highlighter-rouge">g</code>). Может быть, что
<code class="language-plaintext highlighter-rouge">g</code> передаёт в вызов <code class="language-plaintext highlighter-rouge">f</code> какие-то свои аргументы (или данные, зависящие от них). Мы сможем заметить это по тому, что
при анализе цепочки <code class="language-plaintext highlighter-rouge">f</code> -&gt; <code class="language-plaintext highlighter-rouge">g</code> до AJAX-вызова снова дойдёт значение <code class="language-plaintext highlighter-rouge">FROM_ARG</code>. Это будет означать, что нужно попробовать
проанализировать цепочку большей длины — поискать в коде теперь уже вызовы <code class="language-plaintext highlighter-rouge">g</code> и повторить весь процесс с ними.</p>

<p><img src="/images/fromarg-chain3.png" alt="Цепочка из трёх функций" /></p>

<p>Такой анализ цепочек вызовов делаем после основных, обходящих весь код, проходов анализа. Таким образом анализ делается
более таргетированным — он сколько-то смотрит весь код вообще, но потом более тщательно анализирует интересующие участки.
Тут используется то, что AJAX-вызовы мы распознаём синтаксически, по именам — и поэтому «более интересные точки»,
начала цепочек вызова, получается выделять «дёшево», это не требует анализа само по себе. Но, с другой стороны, не
все AJAX-вызовы так найдутся. Кроме того, очевидно, этот метод сможет построить цепочки вызова только для вызовов
free-standing функций, точнее, вызовов, где функция задана как переменная, для которой анализатор смог найти значение.
Вызовы методов объектов (<code class="language-plaintext highlighter-rouge">x.f()</code>) таким алгоритмом не поддерживаются, как и вызовы функций, которые, скажем, были переданы
как аргументы (колбеков).</p>

<aside>
💡 Side note: <details>
    <summary>окей, ну это всё было на идейном уровне, а как это реализуется?</summary>
    <p>Анализатор поддерживает <i>очередь цепочек вызовов</i>, которые ему надо рассмотреть. <i>Цепочка вызовов</i> это
    массив пар <code class="language-plaintext highlighter-rouge">(fn_body,call_site)</code>, где <code class="language-plaintext highlighter-rouge">fn_body</code> это тело функции, участвующей в цепочке, а <code class="language-plaintext highlighter-rouge">call_site</code> это искомое место вызова внутри этой функции. И <code class="language-plaintext highlighter-rouge">fn_body</code> и <code class="language-plaintext highlighter-rouge">call_site</code> задаются задаются как AST-вершины. Для последней функции в цепочке <code class="language-plaintext highlighter-rouge">call_site</code> не указывается — внутри неё анализатор будет
    искать AJAX-вызовы.</p>
    <p>Перед рассмотрением цепочек вызовов анализатор производит основной
    проход по всему коду и, если в ходе него он обнаруживает AJAX-вызовы, в аргументы которых попало значение
    <code class="language-plaintext highlighter-rouge">FROM_ARG</code>, в очередь попадают первые цепочки вызовов (на этом этапе все добавляемые цепочки будут из 2х функций — из которых одна вызывающая и другая вызываемая).</p>
    <p>На этапе анализа цепочек вызова анализатор по одной берёт цепочки из очереди и «проходит» по каждой взятой цепочке. Для
    прохода по цепочке он берёт первый элемент в цепочке, анализирует <code class="language-plaintext highlighter-rouge">fn_body</code>. Если это не последняя функция в цепочке, он проходит по её коду, пока не наткнётся на искомый <code class="language-plaintext highlighter-rouge">call_site</code>. В этом месте анализатор
    вычисляет фактические аргументы вызова и переходит «внутрь» этого вызова — для чего он берёт следующий элемент
    цепочки и начинает анализировать взятое из него <code class="language-plaintext highlighter-rouge">fn_body</code> (предварительно задав вычисленные значения для аргументов). Если же это был последний элемент цепочки, то внутри него анализатор ищет AJAX-вызовы, собирая наборы их аргументов, как и при «обычном» анализе. Если до аргументов очередного AJAX-вызова «дошло»
    значение <code class="language-plaintext highlighter-rouge">FROM_ARG</code>, это значит, что стоит попробовать проанализировать цепочку большей длины. Для этого анализатор возьмёт текущую анализируемую цепочку, из неё возьмёт первую функцию в ней и поищет
    места её вызова. Для каждого найденного места вызова будет создано по одной новой цепочке, полученной присоединением к началу текущей цепочки пары из найденного места вызова и содержащей его функции. Созданные таким образом новые цепочки добавляются в очередь цепочек вызова. Чтобы этот процесс не затягивался до беконечности, есть лимит на максимальную длину цепочек вызова. В текущей версии в цепочке может быть максимум <b>5</b> функций.</p>

</details>
</aside>

<h3 id="специальные-объекты">Специальные объекты</h3>

<p>До этого и в этом, и в предыдущем посте термин <em>AJAX-вызов</em> использовался так, как будто это единый вызов функции,
получающий полную информацию о запросе, и отправляющий этот запрос. Но это не всегда так, иногда отправка запроса
из JS делается <em>несколькими</em> вызовами. На самом деле, первый браузерный интерфейс для отправки запросов из JS,
<code class="language-plaintext highlighter-rouge">XMLHttpRequest</code>, как раз работает так, что надо сделать несколько вызовов. Как минимум, надо создать объект класса
с помощью <code class="language-plaintext highlighter-rouge">new</code>, затем вызвать у созданного объекта метод <code class="language-plaintext highlighter-rouge">.open(...)</code>, и затем <code class="language-plaintext highlighter-rouge">.send(...)</code>. Как мы находим такое своим анализатором?</p>

<p>Если совсем кратко, идея такая — будем создавать <em>специальный объект</em>, который, можно сказать, моделирует экземпляр
этого <code class="language-plaintext highlighter-rouge">XMLHttpRequest</code>. Пусть этот объект будет одним из поддерживаемых анализатором значений и передаётся туда-сюда
при присваиваниях, обращениях к полям объектов и т. д. Будем создавать его когда увидим начало использования <code class="language-plaintext highlighter-rouge">XMLHttpRequest</code>,
и пусть этот объект запоминает данные, из которых формируется запрос. Встретив вызов <code class="language-plaintext highlighter-rouge">someObj.open(...)</code>, проверим, не является ли <code class="language-plaintext highlighter-rouge">someObj</code> одним из наших специальных объектов, моделирующих
<code class="language-plaintext highlighter-rouge">XMLHttpRequest</code>. Если да, то пусть он «запомнит» вычисленные аргументы вызова <code class="language-plaintext highlighter-rouge">open</code>. Аналогично с методом <code class="language-plaintext highlighter-rouge">.setRequestHeader(...)</code>, который выставляет заголовки. Встретив <code class="language-plaintext highlighter-rouge">.send(...)</code> для нашего объекта, также вычислим аргумент вызова — и в этот момент
у нас есть все данные, из которых формируется запрос. Ровно так мы делаем, кстати, для моделирования экземпляров <code class="language-plaintext highlighter-rouge">FormData</code>. Объекты <code class="language-plaintext highlighter-rouge">FormData</code> используются для составления наборов параметров, и у них, как и у <code class="language-plaintext highlighter-rouge">XMLHttpRequest</code>, есть <em>состояние</em>. Вызовы <code class="language-plaintext highlighter-rouge">.append(...)</code> или <code class="language-plaintext highlighter-rouge">.set(...)</code> добавляют в объект <code class="language-plaintext highlighter-rouge">FormData</code> параметры, а потом весь набор параметров отправится, когда объект будет передан в вызов <code class="language-plaintext highlighter-rouge">.send(...)</code> (или в вызов <code class="language-plaintext highlighter-rouge">fetch(...)</code>). Соответственно, у нас для этого заведён специальный класс <code class="language-plaintext highlighter-rouge">FormDataModel</code>, при обработке <code class="language-plaintext highlighter-rouge">new FormData()</code> заводится экземпляр этого <code class="language-plaintext highlighter-rouge">FormDataModel</code>, при обработке <code class="language-plaintext highlighter-rouge">append</code> и <code class="language-plaintext highlighter-rouge">set</code> в этот экземпляр записываются данные, при обработке AJAX-вызовов эти данные используются.</p>

<p>Так же можно было бы сделать и с <code class="language-plaintext highlighter-rouge">XMLHttpRequest</code> — вырабатывать специальное значение при обработке <code class="language-plaintext highlighter-rouge">new XMLHttpRequest</code>. Но мы делаем не совсем так.</p>

<h4 id="хак-для-xmlhttprequest">Хак для <code class="language-plaintext highlighter-rouge">XMLHttpRequest</code></h4>

<p>При анализе реального кода мы заметили, что, при использовании <code class="language-plaintext highlighter-rouge">XMLHttpRequest</code>, вызовы <code class="language-plaintext highlighter-rouge">.open(...)</code> и <code class="language-plaintext highlighter-rouge">.send(...)</code> всё время находятся в одной и той же функции. В теории можно было бы завести объект <code class="language-plaintext highlighter-rouge">XMLHttpRequest</code>, сделать от него <code class="language-plaintext highlighter-rouge">.open(...)</code>, после чего передать объект куда-то в другую функцию, где от него потом бы сделали <code class="language-plaintext highlighter-rouge">.send()</code> — но так никто не делает. Во всех случаях, что мы видели, всё происходит в одной и той же функции, и объект в обоих вызовах задан одинаково (часто это одна и та же переменная). Типа такого</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ...</span>
<span class="nx">xhr</span><span class="p">.</span><span class="nf">open</span><span class="p">(</span><span class="dl">"</span><span class="s2">POST</span><span class="dl">"</span><span class="p">,</span> <span class="nx">url</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
<span class="c1">// ...</span>
<span class="nx">xhr</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span>
<span class="c1">// ...</span>
</code></pre></div></div>
<p>При этом <code class="language-plaintext highlighter-rouge">new XMLHttpRequest()</code> нередко выносят в отдельную функцию — вспомогательную функцию, которая создаёт объект и возвращает его, после чего им воспользуется другой код (такую вспомогательную функцию делают для поддержки старых браузеров, где нужно использовать класс <code class="language-plaintext highlighter-rouge">ActiveXObject</code> или что-то подобное). Будет обидно, если наш «data-flow» анализ не сможет проследить путь объекта XHR от <code class="language-plaintext highlighter-rouge">new</code> до места использования, и из-за этого отправка запроса не найдётся, в то время как <code class="language-plaintext highlighter-rouge">open</code> и <code class="language-plaintext highlighter-rouge">send</code> всё время идут вместе. Особенно учитывая, что возвращаемые значения пользовательских функций мы для начала решили вообще не моделировать. Поэтому решено было сделать так:</p>
<ul>
  <li>Cоздавать объект, моделирующий <code class="language-plaintext highlighter-rouge">XMLHttpRequest</code>, при виде <code class="language-plaintext highlighter-rouge">.open(...)</code>.</li>
  <li>Идентифицировать этот объект, не обращаясь к <code class="language-plaintext highlighter-rouge">memory</code>, а просто по «виду» AST-вершины, отвечающей за объект, чей метод вызывается. То есть слева от <code class="language-plaintext highlighter-rouge">.send(...)</code> должно стоять то же самое (синтаксически), что и слева от <code class="language-plaintext highlighter-rouge">.open(...)</code>.</li>
</ul>

<p>За всё время эта эвристика ни разу не выдала false positive — да, может быть мы создадим «лишние» объекты на какие-нибудь вызовы <code class="language-plaintext highlighter-rouge">open</code>, но мы ни разу не видели такого совпадения, чтобы за этим <code class="language-plaintext highlighter-rouge">open</code> последовал <code class="language-plaintext highlighter-rouge">send</code> от того же объекта, и это не было использование <code class="language-plaintext highlighter-rouge">XMLHttpRequest</code>.</p>

<p>Чтож, пожалуй, для этого поста деталей достаточно. Спасибо, что дочитали его! Продолжение — в третьем посте про алгоритм анализа JS, <a href="../mining-requests-from-js-with-static-analysis-part3">вот тут</a>.</p>]]></content><author><name>Даниил Сигалов (@asterite3)</name></author><category term="ru" /><summary type="html"><![CDATA[В предыдущем посте мы посмотрели на базовую идею работы алгоритма и даже построили небольшой анализатор, основанный на ней. В этом посте расскажу ещё часть принципов устройства нашего алгоритма, включая поиск значений аргументов функций и хендлинг объектов типа инстансов XMLHttpRequest, а ещё кое-что про поддерживаемые операции.]]></summary></entry><entry xml:lang="ru"><title type="html">Майнинг HTTP-запросов из клиентского JS с помощью статического анализа</title><link href="http://blog.secsem.ru/ru/mining-requests-from-js-with-static-analysis/" rel="alternate" type="text/html" title="Майнинг HTTP-запросов из клиентского JS с помощью статического анализа" /><published>2024-03-12T00:00:00+03:00</published><updated>2024-03-12T00:00:00+03:00</updated><id>http://blog.secsem.ru/ru/mining-requests-from-js-with-static-analysis</id><content type="html" xml:base="http://blog.secsem.ru/ru/mining-requests-from-js-with-static-analysis/"><![CDATA[<p>Привет! Мы в лаборатории компьютерной безопасности на факультете ВМК МГУ уже какое-то время занимаемся анализом клиентского JavaScript-кода для обнаружения HTTP-запросов, которые из него могут отправляться на сервер (AJAX-запросов). Цель — передать эту информацию сканеру, который ищет веб-уязвимости на стороне сервера в «black-box» режиме (когда нет доступа к серверным исходникам). Чтобы сканер знал, какие запросы принимает сервер, а значит, в какие запросы можно подставлять атакующие вектора. И мы сами делаем сканер веб-уязвимостей <a href="https://www.solidpoint.net/">SolidPoint</a>, в котором этот анализ используется. В этом посте и нескольких следующих расскажу про этот анализ: почему мы стали его делать, как он работает, что получается в результате.</p>

<p>Кстати, мы писали про это научные статьи (вот <a href="https://journals.tsu.ru/pdm/&amp;journal_page=archive&amp;id=2153&amp;article_id=48226">первая и основная из них</a>), ещё я делал про это доклад в 2022 году (<a href="https://event.phdays.com/ru#analysis-of-the-client-javascript-code-for-detecting-http-endpoints">видео</a>, <a href="https://phd2022.solidwall.io/">слайды и другие материалы</a>). Здесь будет примерно то же самое что и там, но написанное в блог-постовом формате.</p>

<h2 id="почему">Почему</h2>

<p>Почему мы решили этим заняться? Ещё давным-давно во время пентестов мы периодически находили в клиентском JS отправку запросов к серверным ручкам, которые из интерфейса не задействовались. Бывало, что среди JS-кода на странице был код, соответствующий страницам админки, бывало такое, что вообще весь клиентский код сайта бандлился вместе и присутствовал на странице. Иногда в проверке аутентификации на сервере бывали ошибки (её могли просто забыть сделать) и тогда, найдя запрос в JS, мы могли совершить действие, которое текущему пользователю не должно было быть доступно (в том числе такое бывало и с админкой). Мы читали клиентский код и понимали, что за запрос отправляется. Так может быть, подумали мы, это можно делать как-то автоматически?</p>

<p>Запросы, отправляемые элементами HTML-разметки, найти несложно, а вот с JS труднее. Чтобы находить, какие запросы отправляет клиентский JS, сканеры обычно используют <em>динамический краулинг</em> — взаимодействие со страницами сайта с помощью управляемого браузера (headless браузера, например Headless Chrome). Это неплохо работает, но этот метод точно не найдёт такие запросы, как хотели находить мы — ведь для них нет элементов интерфейса, есть только JS-код, который был оставлен ненамеренно. Кроме того, даже с действиями, которые из интерфейса вызвать можно, у динамического краулера бывают проблемы — бывает что интерфейс страницы сложный, такой, в котором возможно много действий. А какие комбинации из этих действий приведут к отправке запроса краулер не знает. Чтобы найти запрос, который отправится после длинной цепочки действий, краулеру может потребоваться перепробовать слишком много всего, это может занять вечность. Поэтому мы решили попробовать сделать штуку, которая бы извлекала отправляемые запросы, не взаимодействуя с интерфейсом, а непосредственно из самого кода. То есть анализатор JavaScript-кода. Мы решили, что начать надо со <em>статического</em> анализа — ведь статический анализ умеет покрывать весь код вообще, независимо от того, насколько сложно его достичь при реальном выполнении.</p>

<p><img src="/images/pic.png" alt="Функция, отправляющая запрос на сервер, среди нагромождения другого кода" class="code-image" /></p>

<p><small>Мы видим глазами, какой запрос отправляет функция в этом коде — должно же быть можно как-то автоматически это найти?</small></p>

<p>Ну и мы подумали: ведь куча сайтов отправляет с помощью клиентского JS запросы, при этом клиентский JS любого сайта доступен — значит, наш анализ можно применить к очень многим сайтам, потенциально найдя запросы к новым серверным ручкам — а значит, повысив вероятность найти уязвимость. Звучит прикольно!</p>

<h3 id="существующие-анализаторы">Существующие анализаторы</h3>

<p>JavaScript очень распространён, он уже давно главный и де-факто единственный язык для клиентской части сайтов — наверняка уже существуют какие-то статические анализаторы JS, верно? Может, можно использовать какой-то из них?</p>

<p>Выяснилось, что не совсем. Да, статические анализаторы JS вроде бы и есть, даже немало, но они мало что могут. Потому что JS вообще-то очень тяжело анализировать статически. Существуют несколько известных в научных статьях статических анализаторов (например <a href="https://github.com/cs-au-dk/TAJS">TAJS</a>, <a href="https://github.com/sukyoung/safe">SAFE</a>, <a href="https://github.com/wala/WALA">WALA</a>) — но все они неспособны анализировать реальные страницы современных сайтов, на любой странице с хоть сколько-нибудь нетривиальным кодом они либо зависают навсегда, либо падают с ошибкой. Либо и то и то (зависают на очень долго и потом падают с ошибкой). Такое происходит даже на простейшей страничке, где есть только библиотека jQuery (при этом jQuery сейчас есть на 77% сайтов). С тулзами не из научных статей ситуация похожая: они либо ломаются на реальном коде, либо дают о нём слишком мало информации.</p>

<aside>
💡 Side note: <details>
    <summary>а это случайно нельзя сделать запросом в пару строк в Semgrep, CodeQL или Joern?</summary>

    <p>Про их возможности можно написать целый отдельный пост, но всё же попробую сейчас относительно кратко объяснить некоторые причины, по которым они не решают нашу задачу «из коробки».</p>

    <p><a href="https://semgrep.dev/">Semgrep</a>, <a href="https://codeql.github.com/">CodeQL</a> и <a href="https://joern.io/">Joern</a> — довольно известные статические анализаторы, которые активно используются для поиска багов в коде. Они поддерживают несколько языков, в том числе JavaScript. Их используют для анализа больших кодовых баз и, чтобы отрабатывать за разумное время и память, им приходится делать легковесный анализ. А чтобы он был легковесным его приходится делать менее точным. Все они могут находить в коде вызовы функций по синтаксическим сигнатурам (то есть по внешнему виду вызова, обычно используя название функции). Однако, понять, какие данные переданы в вызов, у них получается только в самых простых случаях.</p>
    <p>В случае <b>Semgrep</b> разработчики сделали только внутрипроцедурный анализ - то есть Semgrep не понимает как данные передаются между функциями, в том числе через аргументы и возвращаемые значения. То есть уже для вот такого кода Semgrep не поймёт что в вызов <code class="language-plaintext highlighter-rouge">fetch()</code> попадает URL <code class="language-plaintext highlighter-rouge">"/abc123"</code>:

<div class="language-js highlighter-rouge">
    <div class="highlight">
        <pre class="highlight"><code><span class="kd">function</span> <span class="nf">f</span><span class="p">(</span><span class="nx">x</span><span class="p">)</span> <span class="p">{</span>
    <span class="nf">fetch</span><span class="p">(</span><span class="nx">x</span><span class="p">);</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nf">g</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nx">u</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">/abc123</span><span class="dl">'</span><span class="p">;</span>
    <span class="nf">f</span><span class="p">(</span><span class="nx">u</span><span class="p">);</span>
<span class="p">}</span>
</code></pre>
    </div>
</div></p>
<p><b>CodeQL</b> и <b>Joern</b> более продвинутые, в них есть межпроцедурный анализ. Однако, внутри анализа у них очень простое представление для строк — они не умеют вычислять новые строки на основе существующих (как и числа, кстати). То есть, для вот такого кода они не смогут определить, какое значение получила переменная <code class="language-plaintext highlighter-rouge">u</code>:
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">f</span><span class="p">(</span><span class="nx">param</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nx">baseURL</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">/api/2.0/</span><span class="dl">'</span><span class="p">;</span>
    <span class="kd">var</span> <span class="nx">u</span> <span class="o">=</span> <span class="nx">baseURL</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">action?param=</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">param</span><span class="p">;</span>
    <span class="nf">fetch</span><span class="p">(</span><span class="nx">u</span><span class="p">);</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nx">v</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">abc123</span><span class="dl">'</span><span class="p">;</span>
    <span class="nf">f</span><span class="p">(</span><span class="nx">v</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
При этом CodeQL и Joern смогут понять, что она зависела от строк <code class="language-plaintext highlighter-rouge">'/api/2.0/'</code>, <code class="language-plaintext highlighter-rouge">'action?param='</code> и т. д. CodeQL умеет отслеживать конкретные уникальные строки, которые и так были в коде, но не вычислять производные от них. Но от чего зависели данные CodeQL и Joern стараются отследить. Получается, что их «data-flow» анализ довольно таки похож на taint анализ, и его использование даже выглядит так, что нельзя просто попросить выдать возможные значения в какой-то точке, вместо этого их интерфейс для data-flow анализа требует указать 2 точки — source и sink.</p>


<p>В теории, на основе CodeQL и Joern можно было бы попробовать построить <i><a href="https://en.wikipedia.org/wiki/Program_slicing">program slicer</a></i> (про одну такую попытку <a href="https://github.com/github/codeql/discussions/6239">написано тут</a>), или попробовать получить из них <i><a href="https://en.wikipedia.org/wiki/Call_graph">call graph</a></i> и использовать его в другом анализаторе.</p>
</details>
</aside>

<p>Вообще, security-ориентированные анализаторы клиентского JS часто используют динамический анализ. Что можно понять — кроме того, что статанализ отпугивает своей сложностью, искать уязвимости в коде, который не получается стриггерить, как будто не так интересно (если уязвимый код не выполняется, то как эксплуатировать уязвимость)?</p>

<aside>
💡 Side note: <details>
    <summary>и что, прям вот никто-никто не применяет статический анализ для поиска отправляемых запросов?</summary>

    <p>Если говорить строго, то кое-какие такие решения мы всё-таки видели, но они были очень-очень простыми. Что-то на уровне линтеров. Некоторые инструменты грепают из JS-кода строковые литералы, похожие на урлы — так делает <a href="https://portswigger.net/burp/documentation/scanner">Burp Scanner</a> и <a href="https://github.com/andresriancho/w3af">w3af</a>, на GitHub мы находили ещё несколько репозиториев, которые майнят примерно так же серверные эндпоинты из клиентского кода — ища в коде какие то <i>синтаксические</i> конструкции, но не учитывая его <i>семантику</i>. Недавно появилась тулза <a href="https://github.com/BishopFox/jsluice">https://github.com/BishopFox/jsluice</a>, чуть более продвинутая, но всё ещё очень простая — она выгрепывает AJAX-вызовы по синтаксическим шаблонам, вычисляя простейшие двуместные (binary) выражения. Переменные она не поддерживает, как и вызовы функций, поля объектов/массивов и вообще что-либо кроме двуместных выражений и литералов. Благодаря своей простоте эти анализаторы быстрые, редко ломаются. Но если мы хотим найти полный запрос (со всеми параметрами), то в большинстве случаев такого простого анализа будет недостаточно.</p>
</details>
</aside>

<p>Это всё означало, что, если мы хотели получить тулзу, которая работает на реальных приложениях, нам нужно было написать свой, новый статический анализатор. А это вообще возможно? Ведь у существующих анализаторов не получилось. И если возможно — то как?</p>

<h2 id="как">Как</h2>

<p>Так как же статически анализировать JavaScript-код веб-страниц? Ну, для начала нам надо получить его. Вот у нас есть URL веб-страницы, как собрать с неё весь JS? Есть несколько способов это сделать, мы в нашем анализаторе выбрали использовать управляемый браузер и его дебаггер. Управляемый браузер ближе всего к реальной среде, в которой рендерятся страницы и выполняется JS (по сути, браузер это <em>и есть</em> реальная среда) — соответственно, у нас меньше шансов получить проблемы из-за расхождений в нюансах парсинга и реализации стандартов веба (а этих нюансов великое множество). Кроме того, с помощью дебаггера мы можем получить любой JS-код, выполненный на странице, откуда бы он ни взялся — был ли он в коде страницы, был ли он загружен по URL, был ли он как-то сложно динамически сформирован и выполнен с помощью <code class="language-plaintext highlighter-rouge">eval</code> или <code class="language-plaintext highlighter-rouge">Function</code>. Также мы будем знать порядок скриптов — получим их в том же порядке, в котором они в реальности попадали в интерпретатор JS. Давайте посмотрим, как это можно реализовать на примере Headless Chrome. Для этого используем JS-библиотеку <code class="language-plaintext highlighter-rouge">puppeteer</code>. Её можно поставить через <code class="language-plaintext highlighter-rouge">npm</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm <span class="nb">install </span>puppeteer
</code></pre></div></div>

<p>Для начала нам надо открыть страницу с нужным URL:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">puppeteer</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">puppeteer</span><span class="dl">'</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">https://www.solidpoint.net/</span><span class="dl">'</span><span class="p">;</span>

<span class="p">(</span><span class="k">async </span><span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">browser</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">puppeteer</span><span class="p">.</span><span class="nf">launch</span><span class="p">();</span>
    <span class="kd">const</span> <span class="nx">page</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">browser</span><span class="p">.</span><span class="nf">newPage</span><span class="p">();</span>

    <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nf">goto</span><span class="p">(</span><span class="nx">url</span><span class="p">);</span>

    <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`loaded page, url: </span><span class="p">${</span><span class="nx">page</span><span class="p">.</span><span class="nf">url</span><span class="p">()}</span><span class="s2">, title: </span><span class="p">${</span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nf">title</span><span class="p">()}</span><span class="s2">`</span><span class="p">);</span>

    <span class="k">await</span> <span class="nx">browser</span><span class="p">.</span><span class="nf">close</span><span class="p">();</span>
<span class="p">})();</span>
</code></pre></div></div>

<p>Грузить страницу умеем, теперь мы хотим получить выполняемые на ней скрипты — для этого используем <a href="https://chromedevtools.github.io/devtools-protocol/tot/Debugger/">Debugger API</a>. Библиотека <code class="language-plaintext highlighter-rouge">puppeteer</code> сама по себе не предоставляет методов для работы с этим API, поэтому нам нужно будет вызывать отправку сообщений <a href="https://chromedevtools.github.io/devtools-protocol/">Chrome DevTools Protocol</a> (CDP) «вручную». Для этого нам надо попросить библиотеку предоставить нам объект-клиент для общения по протоколу CDP:</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ...</span>
<span class="p">(</span><span class="k">async </span><span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="c1">// ...</span>
    <span class="kd">const</span> <span class="nx">page</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">browser</span><span class="p">.</span><span class="nf">newPage</span><span class="p">();</span>
    <span class="kd">const</span> <span class="nx">client</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nf">target</span><span class="p">().</span><span class="nf">createCDPSession</span><span class="p">();</span>
    <span class="c1">// ...</span>
<span class="p">})</span>
</code></pre></div></div>

<p>Теперь с помощью этого клиента мы можем отправлять CDP-сообщения и получать на них ответы (с помощью <code class="language-plaintext highlighter-rouge">await client.send(...)</code>) и подписываться на события, приходящие от браузера (с помощью <code class="language-plaintext highlighter-rouge">client.on(...)</code>). Мы подпишемся на событие <a href="https://chromedevtools.github.io/devtools-protocol/tot/Debugger/#event-scriptParsed"><code class="language-plaintext highlighter-rouge">Debugger.scriptParsed</code></a>, чтобы нас оповещали о появлении новых скриптов, и используем метод <a href="https://chromedevtools.github.io/devtools-protocol/tot/Debugger/#method-getScriptSource"><code class="language-plaintext highlighter-rouge">Debugger.getScriptSource</code></a>, чтобы получать их код.</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ...</span>
<span class="p">(</span><span class="k">async </span><span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="c1">// ...</span>
    <span class="kd">const</span> <span class="nx">client</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nf">target</span><span class="p">().</span><span class="nf">createCDPSession</span><span class="p">();</span>
    <span class="c1">// в этот массив будем собирать скрипты со страницы</span>
    <span class="kd">const</span> <span class="nx">pageScripts</span> <span class="o">=</span> <span class="p">[];</span>

    <span class="k">await</span> <span class="nx">client</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="dl">'</span><span class="s1">Debugger.enable</span><span class="dl">'</span><span class="p">);</span> <span class="c1">// включим дебаггер для этой страницы</span>
    <span class="nx">client</span><span class="p">.</span><span class="nf">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">Debugger.scriptParsed</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="nf">function </span><span class="p">({</span> <span class="nx">scriptId</span><span class="p">,</span> <span class="nx">url</span> <span class="p">})</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="p">{</span> <span class="nx">scriptSource</span> <span class="p">}</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">client</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span>
            <span class="dl">'</span><span class="s1">Debugger.getScriptSource</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="nx">scriptId</span> <span class="p">}</span>
        <span class="p">);</span>
        <span class="nx">pageScripts</span><span class="p">.</span><span class="nf">push</span><span class="p">({</span> <span class="nx">scriptSource</span><span class="p">,</span> <span class="nx">url</span> <span class="p">});</span>
    <span class="p">});</span>
    <span class="c1">// мы подготовили сбор скриптов - теперь откроем URL!</span>
    <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nf">goto</span><span class="p">(</span><span class="nx">url</span><span class="p">);</span>
    <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">pageScripts</span><span class="p">);</span> <span class="c1">// напечатаем что там собралось</span>
    <span class="c1">// ...</span>
<span class="p">})();</span>
</code></pre></div></div>

<p>Теперь наша программа собирает JS-код скриптов, попадающих в интерпретатор во время загрузки страницы.</p>

<p>Что ж, код мы получили, но как его анализировать? Прежде всего, надо преобразовать код из текстового формата в формат, который удобнее обрабатывать программно. Распарсить его. Существует много парсеров JS-кода, вот пара известных примеров: <a href="https://github.com/jquery/esprima">esprima</a>, <a href="https://babeljs.io/docs/babel-parser">@babel/parser</a> (раньше он, кстати, красиво назывался Babylon).</p>

<p>Установить их можно, как и <code class="language-plaintext highlighter-rouge">puppeteer</code>, с помощью <code class="language-plaintext highlighter-rouge">npm</code>. В случае <code class="language-plaintext highlighter-rouge">@babel/parser</code> это будет такая команда:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm <span class="nb">install</span> @babel/parser
</code></pre></div></div>

<p>Распарсить JS с помощью <code class="language-plaintext highlighter-rouge">@babel/parser</code> можно вот таким кодом:</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">babelParser</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">@babel/parser</span><span class="dl">'</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">code</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">y = k*x + 5</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">ast</span> <span class="o">=</span> <span class="nx">babelParser</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">code</span><span class="p">);</span>

<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">ast</span><span class="p">);</span>
</code></pre></div></div>

<p>Парсер выдаёт абстрактное синтаксическое дерево (abstract syntax tree, обычно его называют сокращённо AST).</p>

<p><img src="/images/ast.png" alt="AST-дерево для кода &lt;&lt;y = k*x + 5&gt;&gt;" class="code-image-smaller" /></p>

<p><small>AST-дерево для кода «y = k*x + 5»</small></p>

<p>Окей, вот у нас есть AST-дерево, как нам искать отправляемые на сервер запросы? Как учил меня мой первый тимлид в мире разработки, давайте начнём с чего-то максимально простого. Мы знаем, что запросы в коде чаще всего отправляются вызовами API-функций/методов, начнём с какого-то одного таргета, пусть это будет функция <code class="language-plaintext highlighter-rouge">fetch()</code>. Поищем в коде вызовы этого <code class="language-plaintext highlighter-rouge">fetch()</code>. Чтобы сделать поиск по всему коду можно рекурсивно обойти всё AST-дерево. Для этого тоже есть готовые библиотеки, в случае Babel это будет <code class="language-plaintext highlighter-rouge">@babel/traverse</code> (также ставится через <code class="language-plaintext highlighter-rouge">npm</code>). Эта либа предоставляет функцию <code class="language-plaintext highlighter-rouge">traverse</code>, которая принимает AST и объект с колбеками. Обойти код в поисках <code class="language-plaintext highlighter-rouge">fetch</code> можно с помощью этой либы так:</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">babelParser</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">@babel/parser</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="p">{</span> <span class="na">default</span><span class="p">:</span> <span class="nx">traverse</span> <span class="p">}</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">@babel/traverse</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">fs</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">fs</span><span class="dl">'</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">code</span> <span class="o">=</span> <span class="nx">fs</span><span class="p">.</span><span class="nf">readFileSync</span><span class="p">(</span><span class="dl">'</span><span class="s1">sample.js</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">ast</span> <span class="o">=</span> <span class="nx">babelParser</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">code</span><span class="p">);</span>

<span class="nf">traverse</span><span class="p">(</span><span class="nx">ast</span><span class="p">,</span> <span class="p">{</span>
    <span class="na">enter</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">path</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">node</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">node</span><span class="p">;</span>
        <span class="k">if </span><span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">CallExpression</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">callee</span> <span class="o">=</span> <span class="nx">node</span><span class="p">.</span><span class="nx">callee</span><span class="p">;</span>
            <span class="k">if </span><span class="p">(</span><span class="nx">callee</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">Identifier</span><span class="dl">'</span> <span class="o">&amp;&amp;</span> <span class="nx">callee</span><span class="p">.</span><span class="nx">name</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">fetch</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
                <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">fetch found!</span><span class="dl">'</span><span class="p">);</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<p>Колбек с именем <code class="language-plaintext highlighter-rouge">enter</code> вызывается при <em>входе</em> в каждую AST-ноду при обходе дерева. То есть функция в этом коде будет вызвана для вообще всех вершин AST — и мы делаем проверки чтобы опознать интересующую нас. Точнее, нас интересуют <em>вызовы</em> функций - за них отвечает AST-нода типа <code class="language-plaintext highlighter-rouge">CallExpression</code>. Далее, мы хотели бы находить не все вызовы вообще, а те, которые выглядят как вызовы функции, данной как <em>идентификатор</em> <code class="language-plaintext highlighter-rouge">fetch</code> (AST-вершина c типом <code class="language-plaintext highlighter-rouge">Identifier</code>). Кстати, доку по всем видам вершин AST-дерева, которое выдаёт Babel, можно <a href="https://github.com/babel/babel/blob/main/packages/babel-parser/ast/spec.md">найти тут</a>. А смотреть как выглядит AST-дерево какого-то кода удобно в <a href="https://astexplorer.net/">AST explorer</a>.</p>

<p>Можно попробовать запустить этот код на каком-то таком примере (сохранив пример в <code class="language-plaintext highlighter-rouge">sample.js</code>):</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">f</span><span class="p">(</span><span class="nx">someCondition</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">someCondition</span><span class="p">)</span> <span class="p">{</span>
        <span class="nf">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">/api/send-data.action?mode=1</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
            <span class="na">method</span><span class="p">:</span> <span class="dl">'</span><span class="s1">POST</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
                <span class="dl">'</span><span class="s1">Content-Type</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/x-www-form-urlencoded</span><span class="dl">'</span>
            <span class="p">},</span>
            <span class="na">body</span><span class="p">:</span> <span class="dl">'</span><span class="s1">data=abc123</span><span class="dl">'</span><span class="p">,</span>
        <span class="p">});</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Код должен выдать <code class="language-plaintext highlighter-rouge">fetch found!</code></p>

<p>Ну хорошо, он нашёл этот вызов, но нам бы хотелось, чтобы он выдавал ещё и его аргументы — данные, которые в него передаются —  чтобы понимать, что там отправляется. Для этого код надо будет доработать, чтобы он смотрел на аргументы вызова. Информация о них записана у <code class="language-plaintext highlighter-rouge">CallExpression</code>-ноды в поле <code class="language-plaintext highlighter-rouge">.arguments</code>. Чтобы аргументы превратить из AST-нод в удобный для чтения код, можно использовать библиотеку <code class="language-plaintext highlighter-rouge">@babel/generator</code> — она умеет превращать AST-вершины обратно в код. Ещё, кстати, код можно сделать покороче на счёт использования колбека с именем не <code class="language-plaintext highlighter-rouge">enter</code>, а <code class="language-plaintext highlighter-rouge">CallExpression</code> — для него <code class="language-plaintext highlighter-rouge">traverse</code> будет сам делать проверку типа ноды и вызывать наш колбек только для нод <code class="language-plaintext highlighter-rouge">CallExpression</code>. И проверка <code class="language-plaintext highlighter-rouge">node.type === 'CallExpression'</code> будет не нужна. Тогда код может принять такой вид:</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">babelParser</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">@babel/parser</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="p">{</span> <span class="na">default</span><span class="p">:</span> <span class="nx">traverse</span> <span class="p">}</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">@babel/traverse</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="p">{</span> <span class="na">default</span><span class="p">:</span> <span class="nx">generate</span> <span class="p">}</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">@babel/generator</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">fs</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">fs</span><span class="dl">'</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">code</span> <span class="o">=</span> <span class="nx">fs</span><span class="p">.</span><span class="nf">readFileSync</span><span class="p">(</span><span class="dl">'</span><span class="s1">sample.js</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">ast</span> <span class="o">=</span> <span class="nx">babelParser</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">code</span><span class="p">);</span>

<span class="nf">traverse</span><span class="p">(</span><span class="nx">ast</span><span class="p">,</span> <span class="p">{</span>
    <span class="na">CallExpression</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">path</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">node</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">node</span><span class="p">;</span>
        <span class="kd">const</span> <span class="nx">callee</span> <span class="o">=</span> <span class="nx">node</span><span class="p">.</span><span class="nx">callee</span><span class="p">;</span>
        <span class="k">if </span><span class="p">(</span><span class="nx">callee</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">Identifier</span><span class="dl">'</span> <span class="o">&amp;&amp;</span> <span class="nx">callee</span><span class="p">.</span><span class="nx">name</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">fetch</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">args</span> <span class="o">=</span> <span class="p">[];</span>
            <span class="k">for </span><span class="p">(</span><span class="kd">const</span> <span class="nx">argNode</span> <span class="k">of</span> <span class="nx">node</span><span class="p">.</span><span class="nx">arguments</span><span class="p">)</span> <span class="p">{</span>
                <span class="nx">args</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nf">generate</span><span class="p">(</span><span class="nx">argNode</span><span class="p">).</span><span class="nx">code</span><span class="p">);</span>
            <span class="p">}</span>
            <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`fetch(</span><span class="p">${</span><span class="nx">args</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="dl">'</span><span class="s1">,</span><span class="dl">'</span><span class="p">)}</span><span class="s2">)`</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<p>При запуске на нашем примере выше он теперь выдаст</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">/api/send-data.action?mode=1</span><span class="dl">'</span><span class="p">,{</span>
  <span class="na">method</span><span class="p">:</span> <span class="dl">'</span><span class="s1">POST</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
    <span class="dl">'</span><span class="s1">Content-Type</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/x-www-form-urlencoded</span><span class="dl">'</span>
  <span class="p">},</span>
  <span class="na">body</span><span class="p">:</span> <span class="dl">'</span><span class="s1">data=abc123</span><span class="dl">'</span>
<span class="p">})</span>
</code></pre></div></div>

<p>Это сработало, такой вывод уже может быть полезен. Но всё-таки это не очень интересно. Что если бы URL был задан не прямо строкой в аргументах, а переменной?</p>

<h3 id="переменные">Переменные</h3>

<p>Что если бы код был каким то таким:</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">f</span><span class="p">(</span><span class="nx">someCondition</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nx">url</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">/api/get-data.action?mode=1</span><span class="dl">'</span><span class="p">;</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">someCondition</span><span class="p">)</span> <span class="p">{</span>
        <span class="nf">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Тогда наша программа выдаст на месте первого аргумента не реальное значение, а имя переменной <code class="language-plaintext highlighter-rouge">url</code>. Что не очень полезно. Так давайте попробуем добавить поддержку переменных!</p>

<p>Мы можем завести под переменные словарь (<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map">Map</a>), куда будем сохранять все переменные, что мы видели. Ключом может быть имя переменной, значением — её значение. При виде объявлений переменных (<code class="language-plaintext highlighter-rouge">VariableDeclarator</code>) будем запоминать их в словаре, при использовании - доставать оттуда. Начнём с переменных, инициализированных строковым литералом. Тогда наш обход AST изменится вот так:</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">memory</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Map</span><span class="p">();</span> <span class="c1">// variables</span>

<span class="nf">traverse</span><span class="p">(</span><span class="nx">ast</span><span class="p">,</span> <span class="p">{</span>
    <span class="na">VariableDeclarator</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">path</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="p">{</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">init</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">node</span><span class="p">;</span>

        <span class="k">if </span><span class="p">(</span><span class="nx">init</span> <span class="o">&amp;&amp;</span> <span class="nx">id</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">Identifier</span><span class="dl">'</span> <span class="o">&amp;&amp;</span> <span class="nx">init</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">StringLiteral</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">memory</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="nx">id</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span> <span class="nx">init</span><span class="p">.</span><span class="nx">value</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">},</span>
    <span class="na">CallExpression</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">path</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">node</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">node</span><span class="p">;</span>
        <span class="kd">const</span> <span class="nx">callee</span> <span class="o">=</span> <span class="nx">node</span><span class="p">.</span><span class="nx">callee</span><span class="p">;</span>
        <span class="k">if </span><span class="p">(</span><span class="nx">callee</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">Identifier</span><span class="dl">'</span> <span class="o">&amp;&amp;</span> <span class="nx">callee</span><span class="p">.</span><span class="nx">name</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">fetch</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">args</span> <span class="o">=</span> <span class="p">[];</span>
            <span class="k">for </span><span class="p">(</span><span class="kd">const</span> <span class="nx">argNode</span> <span class="k">of</span> <span class="nx">node</span><span class="p">.</span><span class="nx">arguments</span><span class="p">)</span> <span class="p">{</span>
                <span class="k">if </span><span class="p">(</span><span class="nx">argNode</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">Identifier</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
                    <span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">memory</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="nx">argNode</span><span class="p">.</span><span class="nx">name</span><span class="p">);</span>
                    <span class="nx">args</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">value</span><span class="p">));</span> <span class="c1">// JSON.stringify to add quotes</span>
                <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                    <span class="c1">// dunno what to do with this node: just convert to source</span>
                    <span class="nx">args</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nf">generate</span><span class="p">(</span><span class="nx">argNode</span><span class="p">).</span><span class="nx">code</span><span class="p">);</span>
                <span class="p">}</span>
            <span class="p">}</span>
            <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`fetch(</span><span class="p">${</span><span class="nx">args</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="dl">'</span><span class="s1">,</span><span class="dl">'</span><span class="p">)}</span><span class="s2">)`</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<p>Этот код справляется с самыми простыми строковыми переменными. Но ведь URL нередко задаётся сложнее.</p>

<h3 id="выражения">Выражения</h3>

<p>Переменная с URL-адресом может где-то переприсваиваться, задаваться как какое-то выражение от других данных, то есть вполне может быть что-то такое:</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">f</span><span class="p">(</span><span class="nx">someCondition</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nx">url</span> <span class="o">=</span> <span class="dl">''</span><span class="p">;</span>
    <span class="kd">var</span> <span class="nx">baseURL</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">/api</span><span class="dl">'</span><span class="p">,</span>
        <span class="nx">mode</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">1</span><span class="dl">'</span><span class="p">;</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">someCondition</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">url</span> <span class="o">=</span> <span class="nx">baseURL</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">/send-data.action</span><span class="dl">'</span><span class="p">;</span>
        <span class="nf">fetch</span><span class="p">(</span><span class="nx">url</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">?mode=</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">mode</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Чтобы поддерживать такие случаи, нам будет удобнее ввести уже отдельную функцию вычисления выражений. Также будет полезно завести специальное уникальное значение для случаев, когда значение <em>неизвестно</em>, найти его не удалось.</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">UNKNOWN_VALUE</span> <span class="o">=</span> <span class="p">{</span> <span class="na">toString</span><span class="p">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="dl">'</span><span class="s1">{???}</span><span class="dl">'</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span>

<span class="kd">function</span> <span class="nf">evalExpr</span><span class="p">(</span><span class="nx">node</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">switch</span><span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">type</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">case</span> <span class="dl">'</span><span class="s1">StringLiteral</span><span class="dl">'</span><span class="p">:</span>
        <span class="k">return</span> <span class="nx">node</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
    <span class="k">case</span> <span class="dl">'</span><span class="s1">Identifier</span><span class="dl">'</span><span class="p">:</span>
        <span class="k">if </span><span class="p">(</span><span class="nx">memory</span><span class="p">.</span><span class="nf">has</span><span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">name</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">return</span> <span class="nx">memory</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">name</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">break</span><span class="p">;</span>
    <span class="k">case</span> <span class="dl">'</span><span class="s1">BinaryExpression</span><span class="dl">'</span><span class="p">:</span>
        <span class="k">if </span><span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">operator</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">+</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span> <span class="nf">evalExpr</span><span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">left</span><span class="p">)</span> <span class="o">+</span> <span class="nf">evalExpr</span><span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">right</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">break</span><span class="p">;</span>        
    <span class="p">}</span>
    <span class="c1">// тут наши полномочия всё (окончены)</span>
    <span class="k">return</span> <span class="nx">UNKNOWN_VALUE</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Для задания значения переменной тоже стоит завести отдельную функцию, потому что оно может происходить из нескольких мест — из инициализации при объявлении и при присвоении (<code class="language-plaintext highlighter-rouge">AssignmentExpression</code>).</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">setVariable</span><span class="p">(</span><span class="nx">varNode</span><span class="p">,</span> <span class="nx">valueNode</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">varNode</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">Identifier</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">memory</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="nx">varNode</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span> <span class="nf">evalExpr</span><span class="p">(</span><span class="nx">valueNode</span><span class="p">));</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Тогда наш маленький алгоритм анализа может принять такой вид:</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">traverse</span><span class="p">(</span><span class="nx">ast</span><span class="p">,</span> <span class="p">{</span>
    <span class="na">VariableDeclarator</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">path</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if </span><span class="p">(</span><span class="nx">path</span><span class="p">.</span><span class="nx">node</span><span class="p">.</span><span class="nx">init</span><span class="p">)</span> <span class="p">{</span>
            <span class="nf">setVariable</span><span class="p">(</span><span class="nx">path</span><span class="p">.</span><span class="nx">node</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span> <span class="nx">path</span><span class="p">.</span><span class="nx">node</span><span class="p">.</span><span class="nx">init</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">},</span>
    <span class="c1">// process assignments, not only initializations</span>
    <span class="na">AssignmentExpression</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">path</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if </span><span class="p">(</span><span class="nx">path</span><span class="p">.</span><span class="nx">node</span><span class="p">.</span><span class="nx">operator</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">=</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
            <span class="nf">setVariable</span><span class="p">(</span><span class="nx">path</span><span class="p">.</span><span class="nx">node</span><span class="p">.</span><span class="nx">left</span><span class="p">,</span> <span class="nx">path</span><span class="p">.</span><span class="nx">node</span><span class="p">.</span><span class="nx">right</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">},</span>
    <span class="na">CallExpression</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">path</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">node</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">node</span><span class="p">;</span>
        <span class="kd">const</span> <span class="nx">callee</span> <span class="o">=</span> <span class="nx">node</span><span class="p">.</span><span class="nx">callee</span><span class="p">;</span>
        <span class="k">if </span><span class="p">(</span><span class="nx">callee</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">Identifier</span><span class="dl">'</span> <span class="o">&amp;&amp;</span> <span class="nx">callee</span><span class="p">.</span><span class="nx">name</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">fetch</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">args</span> <span class="o">=</span> <span class="p">[];</span>
            <span class="k">for </span><span class="p">(</span><span class="kd">const</span> <span class="nx">argNode</span> <span class="k">of</span> <span class="nx">node</span><span class="p">.</span><span class="nx">arguments</span><span class="p">)</span> <span class="p">{</span>
                <span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nf">evalExpr</span><span class="p">(</span><span class="nx">argNode</span><span class="p">);</span>
                <span class="k">if </span><span class="p">(</span><span class="nx">value</span> <span class="o">!==</span> <span class="nx">UNKNOWN_VALUE</span><span class="p">)</span> <span class="p">{</span>
                    <span class="nx">args</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">value</span><span class="p">));</span>
                <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                    <span class="nx">args</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nf">generate</span><span class="p">(</span><span class="nx">argNode</span><span class="p">).</span><span class="nx">code</span><span class="p">);</span>
                <span class="p">}</span>
            <span class="p">}</span>
            <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`fetch(</span><span class="p">${</span><span class="nx">args</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="dl">'</span><span class="s1">,</span><span class="dl">'</span><span class="p">)}</span><span class="s2">)`</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<h3 id="области-видимости">Области видимости</h3>

<p>Программа, которую мы сейчас написали, идентифицирует переменные просто по их именам (то есть строкам). Но в JavaScript может быть несколько разных переменных с одинаковыми именами — в разных областях видимости. Наш анализатор сейчас будет их путать. Это может стать проблемой, особенно если в программе много однобуквенных, неуникальных имён. В реальном клиентском коде такое происходит постоянно из-за минификации: имена всех переменных меняются на максимально короткие. Проблема возникла бы, например, в таком коде:</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">f</span><span class="p">(</span><span class="nx">someCondition</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nx">u</span> <span class="o">=</span> <span class="dl">''</span><span class="p">;</span>
    <span class="kd">var</span> <span class="nx">b</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">/api</span><span class="dl">'</span><span class="p">,</span>
        <span class="nx">m</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">1</span><span class="dl">'</span><span class="p">;</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">someCondition</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">u</span> <span class="o">=</span> <span class="nx">b</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">/get-data.action</span><span class="dl">'</span><span class="p">;</span>
        <span class="kd">var</span> <span class="nx">errCb</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
            <span class="kd">var</span> <span class="nx">u</span><span class="p">,</span> <span class="nx">m</span><span class="p">;</span>
            <span class="nx">u</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">at url addr </span><span class="dl">'</span> <span class="o">+</span> <span class="nx">location</span><span class="p">.</span><span class="nx">href</span><span class="p">;</span>
            <span class="nx">m</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">msg </span><span class="dl">'</span> <span class="o">+</span> <span class="nx">err</span><span class="p">.</span><span class="nx">message</span><span class="p">;</span>
            <span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="s2">`Page </span><span class="p">${</span><span class="nx">u</span><span class="p">}</span><span class="s2">: got error </span><span class="p">${</span><span class="nx">m</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="nf">fetch</span><span class="p">(</span><span class="nx">u</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">?mode=</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">m</span><span class="p">).</span><span class="k">catch</span><span class="p">(</span><span class="nx">errCb</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Внутри вложенной функции <code class="language-plaintext highlighter-rouge">errCb</code> уже другая область видимости, и переменные там другие — но наш анализ этого не поймёт. Чтобы это исправить, нам хорошо было бы различать одноимённые переменные из разных скоупов. Для этого можно было бы переименовать переменные в программе так, чтобы все переменные имели уникальные имена (то есть преобразовать немного анализируемый код) — так нередко делают на практике. Ещё один вариант — вместо просто имени переменной использовать в качестве ключа в <code class="language-plaintext highlighter-rouge">memory</code> какие-то специальные значения, которые бы переменным однозначно соответствовали (были бы разными для разных переменных). К счастью, библиотека Babel уже предоставляет нам это: помимо просто AST-дерева, она разбирается какие в программе есть области видимости — и предоставляет эту инфу. Для каждой области видимости Babel создаёт объект класса <a href="https://github.com/jamiebuilds/babel-handbook/blob/920aa8c/translations/en/plugin-handbook.md#scopes"><code class="language-plaintext highlighter-rouge">Scope</code></a>, а для каждой переменной — объект класса <a href="https://github.com/jamiebuilds/babel-handbook/blob/920aa8c/translations/en/plugin-handbook.md#bindings"><code class="language-plaintext highlighter-rouge">Binding</code></a>. <code class="language-plaintext highlighter-rouge">Scope</code>, в котором находится текущая нода, можно получить из свойства <code class="language-plaintext highlighter-rouge">path.scope</code>. Чтобы получить <code class="language-plaintext highlighter-rouge">Binding</code> можно использовать метод <code class="language-plaintext highlighter-rouge">.getBinding(name)</code>, который есть у объектов класса <code class="language-plaintext highlighter-rouge">Scope</code>. Таким образом, задание переменной можно поменять вот так:</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">setVariable</span><span class="p">(</span><span class="nx">varNode</span><span class="p">,</span> <span class="nx">valueNode</span><span class="p">,</span> <span class="nx">scope</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">varNode</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">Identifier</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">binding</span> <span class="o">=</span> <span class="nx">scope</span><span class="p">.</span><span class="nf">getBinding</span><span class="p">(</span><span class="nx">varNode</span><span class="p">.</span><span class="nx">name</span><span class="p">);</span>
        <span class="nx">memory</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="nx">binding</span><span class="p">,</span> <span class="nf">evalExpr</span><span class="p">(</span><span class="nx">valueNode</span><span class="p">,</span> <span class="nx">scope</span><span class="p">));</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>А вычисление выражений, которое может доставать из <code class="language-plaintext highlighter-rouge">memory</code> значения переменных, так:</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">evalExpr</span><span class="p">(</span><span class="nx">node</span><span class="p">,</span> <span class="nx">scope</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">switch</span><span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">type</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">case</span> <span class="dl">'</span><span class="s1">StringLiteral</span><span class="dl">'</span><span class="p">:</span>
        <span class="k">return</span> <span class="nx">node</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
    <span class="k">case</span> <span class="dl">'</span><span class="s1">Identifier</span><span class="dl">'</span><span class="p">:</span>
        <span class="kd">const</span> <span class="nx">binding</span> <span class="o">=</span> <span class="nx">scope</span><span class="p">.</span><span class="nf">getBinding</span><span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">name</span><span class="p">);</span>
        <span class="k">if </span><span class="p">(</span><span class="nx">memory</span><span class="p">.</span><span class="nf">has</span><span class="p">(</span><span class="nx">binding</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">return</span> <span class="nx">memory</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="nx">binding</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">break</span><span class="p">;</span>
    <span class="k">case</span> <span class="dl">'</span><span class="s1">BinaryExpression</span><span class="dl">'</span><span class="p">:</span>
        <span class="k">if </span><span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">operator</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">+</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span> <span class="nf">evalExpr</span><span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">left</span><span class="p">,</span> <span class="nx">scope</span><span class="p">)</span> <span class="o">+</span> <span class="nf">evalExpr</span><span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">right</span><span class="p">,</span> <span class="nx">scope</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">break</span><span class="p">;</span>        
    <span class="p">}</span>
    <span class="c1">// тут наши полномочия всё (окончены)</span>
    <span class="k">return</span> <span class="nx">UNKNOWN_VALUE</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Ну и, соответственно, при обработке вершин AST эти объекты <code class="language-plaintext highlighter-rouge">scope</code> надо теперь в эти функции передавать:</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">traverse</span><span class="p">(</span><span class="nx">ast</span><span class="p">,</span> <span class="p">{</span>
    <span class="na">VariableDeclarator</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">path</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if </span><span class="p">(</span><span class="nx">path</span><span class="p">.</span><span class="nx">node</span><span class="p">.</span><span class="nx">init</span><span class="p">)</span> <span class="p">{</span>
            <span class="nf">setVariable</span><span class="p">(</span><span class="nx">path</span><span class="p">.</span><span class="nx">node</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span> <span class="nx">path</span><span class="p">.</span><span class="nx">node</span><span class="p">.</span><span class="nx">init</span><span class="p">,</span> <span class="nx">path</span><span class="p">.</span><span class="nx">scope</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">},</span>
    <span class="c1">// process assignments, not only initializations</span>
    <span class="na">AssignmentExpression</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">path</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if </span><span class="p">(</span><span class="nx">path</span><span class="p">.</span><span class="nx">node</span><span class="p">.</span><span class="nx">operator</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">=</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
            <span class="nf">setVariable</span><span class="p">(</span><span class="nx">path</span><span class="p">.</span><span class="nx">node</span><span class="p">.</span><span class="nx">left</span><span class="p">,</span> <span class="nx">path</span><span class="p">.</span><span class="nx">node</span><span class="p">.</span><span class="nx">right</span><span class="p">,</span> <span class="nx">path</span><span class="p">.</span><span class="nx">scope</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">},</span>
    <span class="na">CallExpression</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">path</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">node</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">node</span><span class="p">;</span>
        <span class="kd">const</span> <span class="nx">callee</span> <span class="o">=</span> <span class="nx">node</span><span class="p">.</span><span class="nx">callee</span><span class="p">;</span>
        <span class="k">if </span><span class="p">(</span><span class="nx">callee</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">Identifier</span><span class="dl">'</span> <span class="o">&amp;&amp;</span> <span class="nx">callee</span><span class="p">.</span><span class="nx">name</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">fetch</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">args</span> <span class="o">=</span> <span class="p">[];</span>
            <span class="k">for </span><span class="p">(</span><span class="kd">const</span> <span class="nx">argNode</span> <span class="k">of</span> <span class="nx">node</span><span class="p">.</span><span class="nx">arguments</span><span class="p">)</span> <span class="p">{</span>
                <span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nf">evalExpr</span><span class="p">(</span><span class="nx">argNode</span><span class="p">,</span> <span class="nx">path</span><span class="p">.</span><span class="nx">scope</span><span class="p">);</span>
                <span class="k">if </span><span class="p">(</span><span class="nx">value</span> <span class="o">!==</span> <span class="nx">UNKNOWN_VALUE</span><span class="p">)</span> <span class="p">{</span>
                    <span class="nx">args</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">value</span><span class="p">));</span>
                <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                    <span class="nx">args</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nf">generate</span><span class="p">(</span><span class="nx">argNode</span><span class="p">).</span><span class="nx">code</span><span class="p">);</span>
                <span class="p">}</span>
            <span class="p">}</span>
            <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`fetch(</span><span class="p">${</span><span class="nx">args</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="dl">'</span><span class="s1">,</span><span class="dl">'</span><span class="p">)}</span><span class="s2">)`</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<p>Теперь ключами в нашем <code class="language-plaintext highlighter-rouge">memory</code> будут биндинги переменных, и одноимённые переменные будут различаться.</p>

<p>Ну и ещё добавим поддержку функции <code class="language-plaintext highlighter-rouge">$.ajax</code> — а то чего у нас один единственный <code class="language-plaintext highlighter-rouge">fetch</code> находится. Чтобы код был покороче, используем хелпер-функции из <code class="language-plaintext highlighter-rouge">@babel/types</code>:</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* ... */</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">isIdentifier</span><span class="p">,</span> <span class="nx">isMemberExpression</span> <span class="p">}</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">@babel/types</span><span class="dl">'</span><span class="p">);</span>
<span class="cm">/* ... */</span>
<span class="nf">traverse</span><span class="p">(</span><span class="nx">ast</span><span class="p">,</span> <span class="p">{</span>
    <span class="cm">/* ... */</span>
    <span class="na">CallExpression</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">path</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">node</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">node</span><span class="p">;</span>
        <span class="kd">const</span> <span class="nx">callee</span> <span class="o">=</span> <span class="nx">node</span><span class="p">.</span><span class="nx">callee</span><span class="p">;</span>
        <span class="kd">let</span> <span class="nx">ajaxFunction</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
        <span class="k">if </span><span class="p">(</span><span class="nf">isIdentifier</span><span class="p">(</span><span class="nx">callee</span><span class="p">,</span> <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">fetch</span><span class="dl">'</span> <span class="p">}))</span> <span class="p">{</span>
            <span class="nx">ajaxFunction</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">fetch</span><span class="dl">'</span><span class="p">;</span>
        <span class="p">}</span> <span class="k">else</span> <span class="k">if </span><span class="p">(</span>
            <span class="nf">isMemberExpression</span><span class="p">(</span><span class="nx">callee</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="nf">isIdentifier</span><span class="p">(</span><span class="nx">callee</span><span class="p">.</span><span class="nx">object</span><span class="p">,</span> <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">$</span><span class="dl">'</span><span class="p">})</span> <span class="o">&amp;&amp;</span>
            <span class="nf">isIdentifier</span><span class="p">(</span><span class="nx">callee</span><span class="p">.</span><span class="nx">property</span><span class="p">,</span> <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">ajax</span><span class="dl">'</span><span class="p">})</span>
        <span class="p">)</span> <span class="p">{</span>
            <span class="nx">ajaxFunction</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">$.ajax</span><span class="dl">'</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="k">if </span><span class="p">(</span><span class="nx">ajaxFunction</span><span class="p">)</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">args</span> <span class="o">=</span> <span class="p">[];</span>
            <span class="k">for </span><span class="p">(</span><span class="kd">const</span> <span class="nx">argNode</span> <span class="k">of</span> <span class="nx">node</span><span class="p">.</span><span class="nx">arguments</span><span class="p">)</span> <span class="p">{</span>
                <span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nf">evalExpr</span><span class="p">(</span><span class="nx">argNode</span><span class="p">,</span> <span class="nx">path</span><span class="p">.</span><span class="nx">scope</span><span class="p">);</span>
                <span class="k">if </span><span class="p">(</span><span class="nx">value</span> <span class="o">!==</span> <span class="nx">UNKNOWN_VALUE</span><span class="p">)</span> <span class="p">{</span>
                    <span class="nx">args</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">value</span><span class="p">));</span>
                <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                    <span class="nx">args</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nf">generate</span><span class="p">(</span><span class="nx">argNode</span><span class="p">).</span><span class="nx">code</span><span class="p">);</span>
                <span class="p">}</span>
            <span class="p">}</span>
            <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">ajaxFunction</span><span class="p">}</span><span class="s2">(</span><span class="p">${</span><span class="nx">args</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="dl">'</span><span class="s1">,</span><span class="dl">'</span><span class="p">)}</span><span class="s2">)`</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<p><em>— И что, вот это сейчас статический анализ был? —</em> спросите вы. Вообще-то да, и не самый тривиальный даже. К статическим анализаторам относят в том числе и линтеры, которые учитывают только синтаксис языка — а у нас тут даже немного учитывается семантика! Несомненно, этот анализатор наивный, он много чего не учитывает. Если вам интересен статический анализ и хочется получше узнать как его можно делать, я очень советую (более серьёзный) рассказ про него от потрясающего Мэтта Майта (<a href="https://www.youtube.com/watch?v=POvX4hYIoxg">видео</a>, <a href="https://matt.might.net/articles/intro-static-analysis/">то же в виде блог-поста</a>). В видео лекции Мэтта Майта неидеальный звук, но всё равно, она очень хороша. Ещё несколько ссылок (на учебные курсы и учебник) будут в конце поста. Тем не менее, наш маленький анализ уже работает и будет находить кое-какие вызовы даже сейчас. Попробуйте запустить его на какой-нибудь страничке! Если не знаете какую взять, то попробуйте, например, на <a href="https://promote.telegram.org/">promote.telegram.org</a> или на <a href="https://secsem.ru/ctf/autumn_2014.html">вот этой страничке</a> сайта нашей лаборатории. Этот алгоритм несложно доработать чтобы поддерживать другие имена AJAX-функций, добавить поддержку объектных литералов, чтобы внутри объектов тоже работало вычисление выражений и подстановка переменных. Вот в <a href="https://github.com/asterite3/mini-js-request-miner">этом репозитории</a> можно посмотреть полный финальный код описанного тут мини-анализатора, интегрированного той частью, которая собирает скрипты со страницы.</p>

<h3 id="наш-алгоритм">Наш алгоритм</h3>

<p>Окей, выше был разобран простенький игровой алгоритм анализа JS для поиска вызовов, отправляющих запросы на сервер. Но как работает настоящий алгоритм анализа, который мы разработали в лаборатории, и про который я обещал рассказать?</p>

<p>А работает он на идейном уровне примерно так же. Алгоритм делает рекурсивный обход AST-дерева кода в глубину, для присваиваний (и инициализаций) переменных он запоминает, что было записано в качестве значения переменной. У него тоже есть специальное <em>неизвестное</em> значение, которое называется просто <code class="language-plaintext highlighter-rouge">UNKNOWN</code>. У него есть такая же вот мапа <code class="language-plaintext highlighter-rouge">memory</code>, ключом в которой являются объекты <code class="language-plaintext highlighter-rouge">Binding</code>. Нет никакой специальной обработки циклов: обходя AST, алгоритм заходит в тело цикла так же, как в другие вершины — то есть, циклы анализируются так, как будто они все делали по ровно одной итерации. Вызовы, отправляющие запросы на сервер, наш алгоритм опознаёт так же: с помощью имени функции (или имени объекта + имени метода). Наткнувшись на такой вызов при обходе AST, алгоритм пытается вычислить аргументы этого вызова — и вычисленные аргументы вместе с названием найденной функции добавляются в массив результатов анализа кода. Код он получает тоже с помощью управляемого браузера и его дебаггера. Но остаётся ещё немало подробностей, например:</p>

<ul>
  <li>Одного обхода AST-дерева на самом деле недостаточно, их делается несколько.</li>
  <li>Как быть с передачей данных между функциями? Как определить данные, приходящие из аргументов функции?</li>
  <li>Как быть с классом <code class="language-plaintext highlighter-rouge">XMLHttpRequest</code> — ведь у него нет единой точки, где в него передаются все данные, вместо этого разные части запроса задаются несколькими разными вызовами.</li>
  <li>Мы хотели бы на выход получить всё таки не имена функций с аргументами, а HTTP-запросы — как быть с этим?</li>
</ul>

<p>Про эти и кое-какие другие подробности нашего алгоритма, а также про то, какие получаются результаты его работы и какие выводы мы сделали, расскажу в следующих постах — <a href="../mining-requests-from-js-with-static-analysis-part2">вот первый из них</a>, а <a href="../mining-requests-from-js-with-static-analysis-part3">вот второй</a>!</p>

<h2 id="ссылки">Ссылки</h2>

<p>Если вы хотите узнать больше про статический анализ, всячески рекомендую вот это:</p>

<ol>
  <li>Matt Might — «What is Static Analysis?» — <a href="https://www.youtube.com/watch?v=POvX4hYIoxg">видео</a>, <a href="https://matt.might.net/articles/intro-static-analysis/">то же в  виде блог-поста</a> (но на самом деле всё таки чуть больше)</li>
  <li>Учебник: Anders Møller, Michael I. Schwartzbach — <a href="https://cs.au.dk/~amoeller/spa/">«Static Program Analysis»</a></li>
  <li>Учебный курс Артура Хашаева «Анализ программ». Этот курс на русском языке
    <ul>
      <li><a href="https://khashaev.ru/program-analysis/msu/program-analysis/">Страница курса</a></li>
      <li>Чтобы получить доступ к видео лекций, надо написать Артуру Хашаеву на почту <a href="mailto:arthur@khashaev.ru">arthur@khashaev.ru</a> или в telegram, его юзернейм это @inviz (для доступа к заданиям возможно тоже надо написать, но они могут быть и выложены на страничке курса)</li>
    </ul>
  </li>
  <li>Учебный курс: Michael Pradel — <a href="https://www.youtube.com/playlist?list=PLBmY8PAxzwIEGtnJiucyGAnwWpxACE633">Program Analysis at University of Stuttgart</a></li>
</ol>]]></content><author><name>Даниил Сигалов (@asterite3)</name></author><category term="ru" /><summary type="html"><![CDATA[Привет! Мы в лаборатории компьютерной безопасности на факультете ВМК МГУ уже какое-то время занимаемся анализом клиентского JavaScript-кода для обнаружения HTTP-запросов, которые из него могут отправляться на сервер (AJAX-запросов). Цель — передать эту информацию сканеру, который ищет веб-уязвимости на стороне сервера в «black-box» режиме (когда нет доступа к серверным исходникам). Чтобы сканер знал, какие запросы принимает сервер, а значит, в какие запросы можно подставлять атакующие вектора. И мы сами делаем сканер веб-уязвимостей SolidPoint, в котором этот анализ используется. В этом посте и нескольких следующих расскажу про этот анализ: почему мы стали его делать, как он работает, что получается в результате.]]></summary></entry></feed>