<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Форензика on CTF.MSK.RU</title><link>https://ctf.msk.ru/tags/%D1%84%D0%BE%D1%80%D0%B5%D0%BD%D0%B7%D0%B8%D0%BA%D0%B0/</link><description>Recent content in Форензика on CTF.MSK.RU</description><generator>Hugo -- gohugo.io</generator><language>ru-RU</language><lastBuildDate>Fri, 13 Nov 2026 00:00:00 +0300</lastBuildDate><atom:link href="https://ctf.msk.ru/tags/%D1%84%D0%BE%D1%80%D0%B5%D0%BD%D0%B7%D0%B8%D0%BA%D0%B0/index.xml" rel="self" type="application/rss+xml"/><item><title>От HTTP-ответа к выполнению кода: читаем опасную цепочку в JavaScript</title><link>https://ctf.msk.ru/p/repo-traps-data-to-code/</link><pubDate>Fri, 13 Nov 2026 00:00:00 +0300</pubDate><guid>https://ctf.msk.ru/p/repo-traps-data-to-code/</guid><description>&lt;p&gt;Поиск по репозиторию нашёл &lt;code&gt;Function.constructor&lt;/code&gt;. Кажется, подозрение подтвердилось. Но что именно мы доказали? Динамическое создание функции разрешено языком JavaScript. Чтобы объяснить опасность, нужно показать, откуда пришёл её текст, будет ли она вызвана и какие возможности получит.&lt;/p&gt;
&lt;p&gt;Это обычная задача на чтение потока данных. В CTF такой подход помогает найти уязвимость; при проверке внешнего проекта — восстановить устройство загрузчика и условия его работы.&lt;/p&gt;
&lt;h2 id="что-превращается-в-код"&gt;Что превращается в код
&lt;/h2&gt;&lt;p&gt;Рассмотрим сокращённый учебный фрагмент. Он дан для чтения, не для запуска: функция объявлена, но не вызывается; реальный HTTP-клиент и адрес не заданы. Предположим, что при вызове клиент вернул бы текст ответа в &lt;code&gt;response.data&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;loadSettings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;require&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Название &lt;code&gt;loadSettings&lt;/code&gt; обещает загрузку настроек. Реальная последовательность другая. Ответ HTTP-запроса попадает в &lt;code&gt;body&lt;/code&gt;. Эта строка становится телом новой функции. Затем функция вызывается с аргументом &lt;code&gt;require&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Конструктор &lt;code&gt;Function&lt;/code&gt; компилирует переданную строку как тело функции; само создание функции ещё не исполняет её тело. В примере выполнение происходит на последней строке. Новая функция не захватывает локальную область видимости создавшей её функции так, как обычное замыкание. Поэтому явная передача &lt;code&gt;require&lt;/code&gt; — существенная деталь. [1]&lt;/p&gt;
&lt;p&gt;В типичном CommonJS-окружении Node.js это даёт переданному коду возможность обращаться к доступным модулям. Но пределы действий определяются реальной средой и правами процесса, а не словом RCE в отчёте. [2]&lt;/p&gt;
&lt;p&gt;В реальном разборе нужно проверить и тип &lt;code&gt;response.data&lt;/code&gt;: HTTP-клиент может вернуть уже разобранный объект, а не исходную строку. &lt;code&gt;Function&lt;/code&gt; приводит аргумент тела к строке, после чего проверяет синтаксис. Ошибка на этом этапе не даст дойти до &lt;code&gt;apply&lt;/code&gt;. Это ещё одна причина не объявлять успешное исполнение по одному имени свойства. [1]&lt;/p&gt;
&lt;h2 id="как-оформить-доказательство"&gt;Как оформить доказательство
&lt;/h2&gt;&lt;p&gt;Для такой находки полезна короткая цепочка:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Источник: тело HTTP-ответа.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Преобразование: response.data передаётся без проверки в конструктор функции.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Исполнение: созданная функция вызывается.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Возможности: ей передаётся require текущего CommonJS-модуля.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Неизвестно: была ли вызвана loadSettings и какой ответ получил этот хост.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Последняя строка не обесценивает находку. Она отделяет установленный механизм от событий, для которых пока нет свидетельств.&lt;/p&gt;
&lt;p&gt;Если функцию вызывает код на верхнем уровне модуля, нужно проследить путь импорта от точки входа. Наличие подозрительного файла в &lt;code&gt;src/&lt;/code&gt; ещё не означает, что он используется текущей сборкой. Нужно также проверить ранние ошибки, условия, обработчики исключений и доступность сетевого ответа.&lt;/p&gt;
&lt;h2 id="eval-function-и-functionconstructor"&gt;&lt;code&gt;eval&lt;/code&gt;, &lt;code&gt;Function&lt;/code&gt; и &lt;code&gt;Function.constructor&lt;/code&gt;
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;eval&lt;/code&gt; и &lt;code&gt;Function&lt;/code&gt; различаются по области видимости и правилам выполнения, однако обе конструкции могут превратить строку в код. Поэтому при аудите важнее происхождение строки, чем выбор одного из этих механизмов. [1][3]&lt;/p&gt;
&lt;p&gt;Выражение &lt;code&gt;Function.constructor&lt;/code&gt; в обычной, не изменённой среде JavaScript обращается к тому же конструктору &lt;code&gt;Function&lt;/code&gt;. Но автоматическое правило, ищущее только &lt;code&gt;new Function(&lt;/code&gt;, может пропустить эту запись. Поиск текста полезен как первый фильтр; он не равен анализу семантики. [1][4]&lt;/p&gt;
&lt;p&gt;Отдельная ошибка — смешивать браузер и Node.js. Браузерные строковые таймеры и политика CSP относятся к веб-среде. В Node.js &lt;code&gt;setTimeout&lt;/code&gt; ожидает функцию и при передаче другого типа выдаёт ошибку. Браузерная CSP не защищает запущенный на ноутбуке Node-процесс. [5][6]&lt;/p&gt;
&lt;h2 id="деталь-api-может-изменить-вывод"&gt;Деталь API может изменить вывод
&lt;/h2&gt;&lt;p&gt;В одном из прежних фрагментов разбора встречалась конструкция следующего вида:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;x-secret-header&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;secret&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;В стандартном API Axios второй аргумент &lt;code&gt;post&lt;/code&gt; — данные запроса, а конфигурация передаётся третьим. Следовательно, такой фрагмент сам по себе не устанавливает отправку HTTP-заголовка &lt;code&gt;x-secret-header&lt;/code&gt;: он описывает объект в теле запроса. Заголовки могут добавляться другими настройками, но это нужно проверить отдельно. [7]&lt;/p&gt;
&lt;p&gt;Для расследования различие практическое. Искомая строка может оказаться в другом месте сетевого обмена, а ожидаемый сервером заголовок вообще не отправиться. Это не делает вызов динамического кода безопасным, но влияет на то, могла ли сработать конкретная ветвь.&lt;/p&gt;
&lt;h2 id="что-делать-с-находкой"&gt;Что делать с находкой
&lt;/h2&gt;&lt;p&gt;Сначала проследите путь значения до исполнения. Зафиксируйте файл, строки, точку входа и условия вызова. Не обращайтесь к обнаруженному адресу ради любопытства: ответ сегодня не доказывает, какой ответ был получен во время инцидента.&lt;/p&gt;
&lt;p&gt;В собственном приложении загрузку настроек обычно стоит проектировать как получение данных: разбор JSON, проверка схемы и явное применение допустимых полей. Замена &lt;code&gt;eval&lt;/code&gt; на &lt;code&gt;new Function&lt;/code&gt; не решает проблему доверия.&lt;/p&gt;
&lt;p&gt;Если действительно требуется исполнение внешних программ, нужна отдельная архитектура ограничения полномочий. &lt;code&gt;node:vm&lt;/code&gt; не следует считать защитной песочницей: документация Node.js прямо предупреждает, что этот модуль не предназначен для безопасного выполнения недоверенного кода. [8]&lt;/p&gt;
&lt;h2 id="задача-исполнение-или-только-подготовка"&gt;Задача: исполнение или только подготовка?
&lt;/h2&gt;&lt;p&gt;Файлы для самостоятельного разбора доступны в &lt;a class="link" href="https://ctf.msk.ru/p/repo-traps/#%d0%bf%d1%80%d0%b0%d0%ba%d1%82%d0%b8%d0%ba%d1%83%d0%bc" &gt;синтетическом практикуме&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Сравните три учебные строки независимо друг от друга. Предполагаются обычные встроенные функции JavaScript и CommonJS-окружение с доступным &lt;code&gt;require&lt;/code&gt;; при разборе C учитывайте, что создание функции может завершиться ошибкой:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;return 2 + 2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;require&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;details class="repo-traps-answer"&gt;
&lt;summary&gt;Показать разбор&lt;/summary&gt;
&lt;p&gt;&lt;strong&gt;Разбор.&lt;/strong&gt; В A строка разбирается как JSON; само это действие не исполняет её как JavaScript, хотя дальнейшее использование данных тоже требует проверки. В B создаётся функция с постоянным телом, но в показанной строке не вызывается. В C ответ передаётся конструктору как тело функции; если создание функции успешно, она сразу вызывается. Для C нужно выяснить происхождение &lt;code&gt;response&lt;/code&gt;, достижимость строки и среду выполнения.&lt;/p&gt;
&lt;p&gt;Ни одному из этих выводов не требуется слово «почти всегда». Достаточно назвать конкретное действие и его условия.&lt;/p&gt;
&lt;/details&gt;
&lt;h3 id="источники"&gt;Источники
&lt;/h3&gt;&lt;p&gt;[1] &lt;a class="link" href="https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-function-constructor" target="_blank" rel="noopener"
&gt;ECMAScript: конструктор Function и CreateDynamicFunction&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[2] &lt;a class="link" href="https://nodejs.org/api/modules.html" target="_blank" rel="noopener"
&gt;Node.js: CommonJS modules&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[3] &lt;a class="link" href="https://tc39.es/ecma262/multipage/global-object.html#sec-eval-x" target="_blank" rel="noopener"
&gt;ECMAScript: eval&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[4] &lt;a class="link" href="https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-function.prototype.constructor" target="_blank" rel="noopener"
&gt;ECMAScript: свойства Function.prototype&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[5] &lt;a class="link" href="https://nodejs.org/api/timers.html" target="_blank" rel="noopener"
&gt;Node.js: Timers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[6] &lt;a class="link" href="https://www.w3.org/TR/CSP3/" target="_blank" rel="noopener"
&gt;W3C: Content Security Policy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[7] &lt;a class="link" href="https://github.com/axios/axios#request-method-aliases" target="_blank" rel="noopener"
&gt;Axios: сигнатуры методов в официальном репозитории&lt;/a&gt;, &lt;a class="link" href="https://axios-http.com/docs/api_intro" target="_blank" rel="noopener"
&gt;документация API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[8] &lt;a class="link" href="https://nodejs.org/api/vm.html" target="_blank" rel="noopener"
&gt;Node.js: VM — предупреждение о границах безопасности&lt;/a&gt;.&lt;/p&gt;
&lt;nav class="repo-traps-nav" aria-label="Навигация по циклу"&gt;
&lt;p&gt;Статья 3 из 8 · &lt;a href="https://ctf.msk.ru/p/repo-traps/"&gt;Оглавление цикла&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel="prev" href="https://ctf.msk.ru/p/repo-traps-assets/"&gt;← Предыдущая: Шрифт, который оказался скриптом: проверяем формат и назначение файла&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel="next" href="https://ctf.msk.ru/p/repo-traps-npm/"&gt;Следующая: Что выполняется при npm install: хуки, зависимости и границы ignore-scripts →&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/nav&gt;</description></item><item><title>После запуска подозрительного проекта: как собрать факты и ограничить ущерб</title><link>https://ctf.msk.ru/p/repo-traps-response/</link><pubDate>Fri, 13 Nov 2026 00:00:00 +0300</pubDate><guid>https://ctf.msk.ru/p/repo-traps-response/</guid><description>&lt;p&gt;В проекте нашли загрузчик. Владелец компьютера вспоминает, что уже открывал папку и устанавливал зависимости. Теперь важно не только понять код: нужно ограничить возможный ущерб и сохранить сведения о том, что произошло.&lt;/p&gt;
&lt;p&gt;У этой задачи две линии работы. Первая — реагирование: изоляция, защита доступов, восстановление. Вторая — расследование: какой процесс запускался, что получил по сети и какие изменения оставил. Они связаны, но ни одна не требует придумывать недостающие события.&lt;/p&gt;
&lt;h2 id="сначала-устанавливаем-что-известно"&gt;Сначала устанавливаем, что известно
&lt;/h2&gt;&lt;p&gt;Фраза «нашёл вредоносный файл» может описывать совершенно разные ситуации. Архив лежит в загрузках. Либо команда пыталась стартовать и завершилась ошибкой. Либо неизвестный код действительно исполнялся в рабочем окружении.&lt;/p&gt;
&lt;p&gt;Для первой записи достаточно нескольких строк:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Объект: компьютер и учётная запись.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Время: когда получен проект, с указанием часового пояса.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Действия: что пользователь действительно делал.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Свидетельства: журнал, процесс, файл, сетевое событие.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Неизвестно: что пока не удалось установить.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Слова пользователя сохраняют как сообщение пользователя. Событие из журнала — как событие из конкретного журнала. «Я ничего не запускал» может означать «не нажимал кнопку запуска приложения», поэтому уточняют установку, задачи редактора и команды проверки.&lt;/p&gt;
&lt;h2 id="ограничить-связь-не-уничтожив-следы"&gt;Ограничить связь, не уничтожив следы
&lt;/h2&gt;&lt;p&gt;При вероятном исполнении неизвестного кода на рабочей машине её изолируют от сети и сообщают ответственному за реагирование. Для организации предпочтителен установленный процесс изоляции через средства защиты. Для личной машины прекращение сетевого подключения может стать первым практическим шагом.&lt;/p&gt;
&lt;p&gt;Не стоит начинать с удаления подозрительных каталогов, очистки кэшей и завершения всех процессов &lt;code&gt;node&lt;/code&gt;. Так можно потерять нужные следы и остановить посторонние приложения. Если изоляция выполнена и нет продолжающегося разрушительного действия, сначала фиксируют доступные сведения о процессах и соединениях. При непосредственном ущербе остановка опасного процесса важнее полноты сбора, но вмешательство записывают.&lt;/p&gt;
&lt;p&gt;Руководство CISA по реагированию отдельно подчёркивает изоляцию и сохранение изменчивых данных. Оно посвящено вымогательским атакам; здесь мы используем общий принцип сохранения свидетельств, а не приписываем рассматриваемому проекту функции шифровальщика. [1]&lt;/p&gt;
&lt;h2 id="минимальный-сбор-на-macos"&gt;Минимальный сбор на macOS
&lt;/h2&gt;&lt;p&gt;На изолированном компьютере можно сохранить исходные результаты чтения процессов и соединений. Команды выполняют из обычного терминала, не через задачи или скрипты подозрительного проекта. Это не полноценный криминалистический образ, а первичная фиксация:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;umask&lt;/span&gt; &lt;span class="m"&gt;077&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;TRIAGE_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;/usr/bin/mktemp -d &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TMPDIR&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="p"&gt;/tmp&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/repo-triage.XXXXXX&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/bin/date -u &lt;span class="s1"&gt;&amp;#39;+%Y-%m-%dT%H:%M:%SZ&amp;#39;&lt;/span&gt; &amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$TRIAGE_DIR&lt;/span&gt;&lt;span class="s2"&gt;/collected-at-utc.txt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/usr/bin/sw_vers &amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$TRIAGE_DIR&lt;/span&gt;&lt;span class="s2"&gt;/macos.txt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/bin/ps -axww -o pid,ppid,user,lstart,command &amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$TRIAGE_DIR&lt;/span&gt;&lt;span class="s2"&gt;/processes.txt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/usr/sbin/lsof -nP -i &amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$TRIAGE_DIR&lt;/span&gt;&lt;span class="s2"&gt;/connections.txt&amp;#34;&lt;/span&gt; 2&amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$TRIAGE_DIR&lt;/span&gt;&lt;span class="s2"&gt;/lsof-errors.txt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Сведения сохранены: %s\n&amp;#39;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$TRIAGE_DIR&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Команды меняют диск, создавая отчёт; это следует учитывать. Временный каталог не предназначен для длительного хранения: после сбора результаты сохраняют в защищённом хранилище вместе с временем и описанием действий. Без дополнительных прав часть сведений может быть недоступна. После отключения сети некоторые соединения исчезнут. Командные строки и журналы могут содержать секреты, поэтому результаты нельзя бездумно публиковать или отправлять в публичный анализатор.&lt;/p&gt;
&lt;p&gt;Снимок процессов отражает только момент сбора, а сведения от потенциально скомпрометированной ОС требуют сопоставления с независимыми журналами. Не устанавливайте для этой проверки инструменты из подозрительного проекта. При серьёзном инциденте состав и порядок сбора определяет специалист, включая сохранение памяти, диска и внешних журналов.&lt;/p&gt;
&lt;h2 id="где-искать-продолжение-цепочки"&gt;Где искать продолжение цепочки
&lt;/h2&gt;&lt;p&gt;Рабочие записи исходного случая упоминали пути &lt;code&gt;~/node/main.js&lt;/code&gt; и &lt;code&gt;~/Programs_X64/main.js&lt;/code&gt;. Это ориентиры для поиска по определённым образцам, а не универсальные признаки заражения. Каталог с таким именем может существовать по другой причине; отсутствие каталога не доказывает отсутствия исполнения.&lt;/p&gt;
&lt;p&gt;Сначала сравнивают содержимое, хэши, время появления и связь с процессом. Затем смотрят места фонового запуска. Для macOS к ним относятся пользовательские и системные LaunchAgents/LaunchDaemons; формат launchd и роль этих каталогов описаны Apple. [2]&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;~/Library/LaunchAgents/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/Library/LaunchAgents/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/Library/LaunchDaemons/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Проверка этих каталогов не исчерпывает возможные механизмы закрепления. И сам файл &lt;code&gt;.plist&lt;/code&gt;, даже запускающий Node.js, не обязательно вредоносен. Нужны его происхождение, команда, связанный файл и сведения о загрузке задания.&lt;/p&gt;
&lt;h2 id="доступы-защищают-с-доверенного-устройства"&gt;Доступы защищают с доверенного устройства
&lt;/h2&gt;&lt;p&gt;Если неизвестный код мог исполняться с правами пользователя, составляют перечень доступных ему секретов и активных сессий. Это оценка возможного доступа, а не доказательство, что всё было украдено.&lt;/p&gt;
&lt;p&gt;Приоритет определяют полномочия: доступ к почте и управлению учётными записями, инфраструктуре, репозиториям, платёжным системам и другим критичным ресурсам. Действующие сессии и токены отзывают, ключи заменяют, журналы входов проверяют с доверенного устройства. Универсальный порядок «сначала Git, потом SSH, пароли в конце» здесь не подходит: почта для восстановления может быть важнее любого отдельного токена.&lt;/p&gt;
&lt;p&gt;Новые секреты не возвращают на ещё не восстановленную машину. Переустановка также не отзывает доступ, который уже существует в облачном сервисе: восстановление компьютера и защита учётных записей — разные задачи.&lt;/p&gt;
&lt;h2 id="когда-восстанавливать-систему-заново"&gt;Когда восстанавливать систему заново
&lt;/h2&gt;&lt;p&gt;Сам факт наличия опасного файла в скачанном архиве не является автоматическим основанием для стирания системы. Но при подтверждённом исполнении неизвестной следующей стадии и значимых доступах доверие к окружению может быть невозможно восстановить одной точечной очисткой.&lt;/p&gt;
&lt;p&gt;В таком случае после необходимого сбора рассматривают восстановление из доверенного источника и осторожный возврат данных. Это не обещание абсолютной гарантии. Решение зависит от масштаба, полноты наблюдений, допустимого риска и возможностей расследования.&lt;/p&gt;
&lt;p&gt;Отсутствие находок при поверхностном поиске означает именно отсутствие найденных следов, а не доказанную чистоту.&lt;/p&gt;
&lt;h2 id="задача-что-подтверждают-события"&gt;Задача: что подтверждают события?
&lt;/h2&gt;&lt;p&gt;Даны учебные события:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;10:02 — пользователь открыл папку;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;10:03 — журнал содержит запуск node с аргументом .vscode/helper.dict;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;10:03 — зарегистрировано исходящее соединение этого процесса;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;10:04 — процесс завершился; тело ответа не сохранено.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Составьте короткое заключение: что подтверждено и какие сведения о возможной следующей стадии отсутствуют?&lt;/p&gt;
&lt;details class="repo-traps-answer"&gt;
&lt;summary&gt;Показать разбор&lt;/summary&gt;
&lt;p&gt;&lt;strong&gt;Вывод:&lt;/strong&gt; подтверждены запуск процесса и соединение. Получена ли следующая стадия, что она содержала и исполнялась ли, неизвестно. Изоляция и оценка доступов обоснованны; утверждение «подтверждена кража всех паролей» — нет.&lt;/p&gt;
&lt;p&gt;Именно последняя граница должна попасть в отчёт. Она помогает следующему специалисту понять, где заканчиваются факты и начинается работа по проверке гипотез.&lt;/p&gt;
&lt;/details&gt;
&lt;h3 id="источники"&gt;Источники
&lt;/h3&gt;&lt;p&gt;[1] &lt;a class="link" href="https://www.ic3.gov/CSA/2023/231019.pdf" target="_blank" rel="noopener"
&gt;CISA, FBI, NSA и MS-ISAC: #StopRansomware Guide, Part 2 — изоляция и сохранение свидетельств; копия на сайте FBI/IC3&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[2] &lt;a class="link" href="https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html" target="_blank" rel="noopener"
&gt;Apple: Creating Launch Daemons and Agents&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Связанный материал сайта: &lt;a class="link" href="https://ctf.msk.ru/p/forensics-basics/" &gt;основы форензики&lt;/a&gt;. Все события в упражнении синтетические; это не журнал компьютера участника исходного случая.&lt;/p&gt;
&lt;nav class="repo-traps-nav" aria-label="Навигация по циклу"&gt;
&lt;p&gt;Статья 5 из 8 · &lt;a href="https://ctf.msk.ru/p/repo-traps/"&gt;Оглавление цикла&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel="prev" href="https://ctf.msk.ru/p/repo-traps-npm/"&gt;← Предыдущая: Что выполняется при npm install: хуки, зависимости и границы ignore-scripts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel="next" href="https://ctf.msk.ru/p/repo-traps-static-control/"&gt;Следующая: Проверка репозитория до запуска: как устроить безопасный статический контроль →&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/nav&gt;</description></item><item><title>Проверка репозитория до запуска: как устроить безопасный статический контроль</title><link>https://ctf.msk.ru/p/repo-traps-static-control/</link><pubDate>Fri, 13 Nov 2026 00:00:00 +0300</pubDate><guid>https://ctf.msk.ru/p/repo-traps-static-control/</guid><description>&lt;p&gt;После нескольких разборов хочется автоматизировать проверку: искать автозадачи, установочные хуки и динамическое исполнение. Но возникает неприятная ловушка. Команда «сначала запустим линтер» сама может выполнить код из проверяемого проекта.&lt;/p&gt;
&lt;p&gt;Поэтому задача этой статьи — не собрать максимальный список запретов. Нужно устроить процесс так, чтобы проверяемые файлы оставались данными, а решения о запуске принимались отдельно.&lt;/p&gt;
&lt;h2 id="кто-управляет-проверкой"&gt;Кто управляет проверкой?
&lt;/h2&gt;&lt;p&gt;У ESLint есть конфигурации &lt;code&gt;eslint.config.js&lt;/code&gt;, &lt;code&gt;.mjs&lt;/code&gt;, &lt;code&gt;.cjs&lt;/code&gt; и другие поддерживаемые варианты. Конфигурация может импортировать модули и плагины. Значит, запуск линтера с настройками неизвестного проекта — это не просто чтение текста: он включает загрузку исполняемой конфигурации. [1]&lt;/p&gt;
&lt;p&gt;Сначала проверяющий инструмент, его правила и зависимости должны находиться в доверенном окружении. Подозрительный репозиторий передаётся туда только как материал. Это относится и к скрипту под названием &lt;code&gt;security-check&lt;/code&gt;, и к предложенному README вызову &lt;code&gt;npx&lt;/code&gt;, и к конфигурации CI из самого репозитория.&lt;/p&gt;
&lt;p&gt;Особенно опасно переносить непроверенный код на постоянный CI-исполнитель с секретами или доступом во внутреннюю сеть. Документация GitHub прямо предупреждает, что self-hosted runners могут быть скомпрометированы недоверенным кодом и сохранить изменения между запусками. [2]&lt;/p&gt;
&lt;h2 id="три-слоя-проверки"&gt;Три слоя проверки
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Первый слой — состав материала.&lt;/strong&gt; Получаем перечень файлов, размеры, ожидаемые форматы и контрольные суммы. Для архивов заранее задаём пределы количества и суммарного размера распакованных данных, проверяем пути, ссылки и повторяющиеся имена. Открытие архива не равно исполнению его скриптов, но само распаковывание тоже требует аккуратности. [3]&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Второй слой — признаки исполнения.&lt;/strong&gt; Читаем &lt;code&gt;.vscode&lt;/code&gt;, &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;.npmrc&lt;/code&gt;, точки входа и конфигурации инструментов. Не ограничиваемся расширениями &lt;code&gt;.js&lt;/code&gt;: предыдущая статья о поддельном шрифте как раз показывает, почему такой фильтр недостаточен.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Третий слой — связная проверка находки.&lt;/strong&gt; Для подозрительной конструкции устанавливаем источник данных и достижимость вызова. Этот этап нельзя полностью заменить поиском слова &lt;code&gt;eval&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Все три слоя выполняются без установки зависимостей исследуемого проекта. Если затем нужна динамическая проверка, это уже другая стадия с отдельной изоляцией и решением о допустимой сети.&lt;/p&gt;
&lt;h2 id="простой-поиск-полезный-но-ограниченный"&gt;Простой поиск: полезный, но ограниченный
&lt;/h2&gt;&lt;p&gt;В небольшой, уже безопасно подготовленной копии можно начать с доверенного &lt;code&gt;rg&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rg --no-config --hidden --no-ignore --text --files-with-matches &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --glob &lt;span class="s1"&gt;&amp;#39;!.git&amp;#39;&lt;/span&gt; --max-filesize 2M &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;Function\s*\.\s*constructor|new\s+Function\s*\(|eval\s*\(|&amp;#34;runOn&amp;#34;\s*:\s*&amp;#34;folderOpen&amp;#34;|&amp;#34;(preinstall|install|postinstall|prepare)&amp;#34;\s*:&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -- /analysis/input
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Путь &lt;code&gt;/analysis/input&lt;/code&gt; здесь обозначает копию материала в подготовленной среде. Команда показывает имена файлов с совпадениями, а не запускает их. Она включает скрытые файлы и ищет текст независимо от расширения. Шаблон &lt;code&gt;!.git&lt;/code&gt; исключает каталоги и файлы с этим именем, в том числе при передаче абсолютного пути. Символические ссылки по умолчанию не обходятся; их наличие проверяют при подготовке копии. Поведение параметров описано в документации ripgrep. [4]&lt;/p&gt;
&lt;p&gt;Предел размера — осознанное ограничение примера, не способ объявить большие файлы безопасными. Их нужно учесть отдельно. Как и ошибки чтения, неподдержанные кодировки, многослойную обфускацию и конструкции, записанные иначе. У &lt;code&gt;rg&lt;/code&gt; код 0 означает найденные совпадения, 1 — отсутствие совпадений, 2 — ошибку. Ошибку нельзя выдавать за чистый результат; отсутствие совпадений тоже не превращает поиск в антивирус.&lt;/p&gt;
&lt;h2 id="что-делать-с-результатом"&gt;Что делать с результатом
&lt;/h2&gt;&lt;p&gt;Полезно разделить два типа правил.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Политика процесса:&lt;/strong&gt; «В принимаемом тестовом задании запрещён автозапуск при открытии». Нарушение может блокировать приём, даже если задача безобидна. Причина блокировки — несоответствие политике, не доказанная малварь.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Техническая находка:&lt;/strong&gt; «Ответ внешнего сервера передаётся в исполняемое тело функции и вызывается из импортируемого модуля». Здесь требуется разбор конкретной цепочки.&lt;/p&gt;
&lt;p&gt;Исключения должны утверждаться вне проверяемого изменения. Комментарий &lt;code&gt;eslint-disable&lt;/code&gt; или &lt;code&gt;nosemgrep&lt;/code&gt;, добавленный автором подозрительного кода, не может сам себе разрешить опасное поведение. Для доверенных проектов линтеры с правилами &lt;code&gt;no-eval&lt;/code&gt; и &lt;code&gt;no-new-func&lt;/code&gt; полезны как часть обычной разработки. Для недоверенного материала сначала устанавливается граница между данными и инструментом. [1][5]&lt;/p&gt;
&lt;p&gt;Semgrep позволяет задавать правила анализа структуры кода и текстовые шаблоны. Но готовое правило нужно проверять на своих примерах, включая переименованные файлы и заведомо легитимный код. Наличие названия инструмента в CI ещё ничего не говорит о покрытии. [6]&lt;/p&gt;
&lt;h2 id="задача-проверьте-проверяющего"&gt;Задача: проверьте проверяющего
&lt;/h2&gt;&lt;p&gt;Предложены три способа:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;А. Запустить npm run security-check из присланного проекта.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Б. Передать копию файлов локальному сканеру вне проекта, с доверенными правилами.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;В. Собрать проект на постоянном CI-сервере, а потом просканировать результат.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Для отдельной проверки поискового шаблона возьмите &lt;code&gt;literal-positive.txt&lt;/code&gt;, &lt;code&gt;literal-negative.txt&lt;/code&gt; и &lt;code&gt;flow.js.txt&lt;/code&gt; из &lt;a class="link" href="https://ctf.msk.ru/p/repo-traps/#%d0%bf%d1%80%d0%b0%d0%ba%d1%82%d0%b8%d0%ba%d1%83%d0%bc" &gt;практикума&lt;/a&gt;. Предскажите, какие файлы совпадут с шаблоном, и проверьте себя поиском. Для каждого совпадения укажите, откуда берётся тело функции и при каких условиях оно могло бы исполниться. Читайте примеры как текст.&lt;/p&gt;
&lt;details class="repo-traps-answer"&gt;
&lt;summary&gt;Показать разбор&lt;/summary&gt;
&lt;p&gt;&lt;strong&gt;Разбор.&lt;/strong&gt; В А порядок доверия перевёрнут: репозиторий сам определяет программу своей проверки. В Б появляется нужная граница, но ещё надо проверить изоляцию инструмента, лимиты и отсутствие исполнения конфигурации проекта. Здесь не предлагается загружать исходники в сторонний сервис: в них могут быть секреты и закрытая информация. В В недоверенный код уже выполняется до оценки, причём на потенциально ценной машине.&lt;/p&gt;
&lt;p&gt;Шаблон совпадает в &lt;code&gt;literal-positive.txt&lt;/code&gt; и &lt;code&gt;flow.js.txt&lt;/code&gt;. В первом тело функции задано строковым литералом, а сама функция в этом фрагменте не вызывается. Во втором тело берётся из данных ответа. До вызова программа должна получить и разобрать ответ, прочитать нужное поле и успешно создать функцию. Поиск показывает место для проверки. Из текста можно вывести условия вызова, но для утверждения о состоявшемся исполнении понадобятся наблюдения за запуском. В &lt;code&gt;literal-negative.txt&lt;/code&gt; есть только присваивание числа.&lt;/p&gt;
&lt;/details&gt;
&lt;h2 id="что-стоит-запомнить"&gt;Что стоит запомнить
&lt;/h2&gt;&lt;p&gt;Автоматизация полезна тогда, когда экономит внимание и сохраняет проверяемость. Хороший результат статического контроля — список находок с причинами, ограничениями покрытия и решениями о допуске. Формулировка «угроз не найдено» без описания того, что проверялось, слишком мало сообщает следующему человеку.&lt;/p&gt;
&lt;h3 id="источники"&gt;Источники
&lt;/h3&gt;&lt;p&gt;[1] &lt;a class="link" href="https://eslint.org/docs/latest/use/configure/configuration-files" target="_blank" rel="noopener"
&gt;ESLint: Configuration Files&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[2] &lt;a class="link" href="https://docs.github.com/en/actions/reference/security/secure-use" target="_blank" rel="noopener"
&gt;GitHub Actions: Secure use reference — self-hosted runners&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[3] &lt;a class="link" href="https://docs.python.org/3/library/zipfile.html" target="_blank" rel="noopener"
&gt;Python: zipfile — предупреждения о распаковке и ограничениях&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[4] &lt;a class="link" href="https://github.com/BurntSushi/ripgrep/blob/master/crates/core/flags/doc/template.rg.1" target="_blank" rel="noopener"
&gt;ripgrep: руководство по параметрам&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[5] &lt;a class="link" href="https://eslint.org/docs/latest/rules/no-new-func" target="_blank" rel="noopener"
&gt;ESLint: no-new-func&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[6] &lt;a class="link" href="https://docs.semgrep.dev/writing-rules/rule-syntax" target="_blank" rel="noopener"
&gt;Semgrep: Rule syntax&lt;/a&gt;.&lt;/p&gt;
&lt;nav class="repo-traps-nav" aria-label="Навигация по циклу"&gt;
&lt;p&gt;Статья 6 из 8 · &lt;a href="https://ctf.msk.ru/p/repo-traps/"&gt;Оглавление цикла&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel="prev" href="https://ctf.msk.ru/p/repo-traps-response/"&gt;← Предыдущая: После запуска подозрительного проекта: как собрать факты и ограничить ущерб&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel="next" href="https://ctf.msk.ru/p/repo-traps-hiring/"&gt;Следующая: Тестовое задание на отдельной машине: порядок приёма внешнего кода →&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/nav&gt;</description></item><item><title>Репозиторий как ловушка: от настроек VS Code к запуску кода</title><link>https://ctf.msk.ru/p/repo-traps-vscode/</link><pubDate>Fri, 13 Nov 2026 00:00:00 +0300</pubDate><guid>https://ctf.msk.ru/p/repo-traps-vscode/</guid><description>&lt;p&gt;Разработчику присылают проект как часть тестового задания. Нужно посмотреть код, установить зависимости и показать, что приложение работает. Всё выглядит привычно — до тех пор, пока в настройках редактора не обнаруживается команда, запускающая файл из каталога шрифтов.&lt;/p&gt;
&lt;p&gt;С такой истории начался этот цикл. В январе 2026 года мы обсуждали три архива репозиториев Bitbucket. По сообщению участника разбора, один проект успели запустить на MacBook; два других изучали без запуска. Рабочие записи описывали автозадачи редактора, JavaScript под видом шрифта или словаря и получение исполняемого кода из сети. Само сообщение о запуске ещё не устанавливает, какая часть цепочки сработала и что произошло дальше.&lt;/p&gt;
&lt;p&gt;Далее мы будем разбирать эти механизмы на сокращённых учебных примерах. Это позволит повторить рассуждение без обращения к подозрительным серверам. Примеры не являются дословными листингами исходных архивов, а сообщения о находках не заменяют исследование конкретного компьютера.&lt;/p&gt;
&lt;h2 id="с-чего-начинается-проверка"&gt;С чего начинается проверка
&lt;/h2&gt;&lt;p&gt;В CTF полезно сначала спросить: какие данные нам дали и что именно требуется доказать? Здесь исходные данные — файлы проекта. Вопрос — может ли какой-либо из них привести к исполнению команд до обычного запуска приложения?&lt;/p&gt;
&lt;p&gt;Начнём с &lt;code&gt;.vscode/tasks.json&lt;/code&gt;. Это конфигурация задач VS Code. Задачи могут вызывать внешние инструменты, а значение &lt;code&gt;runOptions.runOn: &amp;quot;folderOpen&amp;quot;&lt;/code&gt; задаёт запуск при открытии содержащей их папки. Однако автоматическое выполнение зависит от разрешений: в недоверенной рабочей области такие задачи не запускаются; в доверенной отдельно учитывается разрешение автоматических задач. [1]&lt;/p&gt;
&lt;p&gt;Поэтому формула «открыл папку — заразился» пропускает существенную часть условий. Нужно установить, была ли папка доверенной, были ли разрешены автозадачи и смогла ли команда выполниться. Workspace Trust мог быть задан раньше, в том числе для родительского каталога. [2]&lt;/p&gt;
&lt;p&gt;В нынешнем VS Code автоматическими задачами управляет &lt;code&gt;task.allowAutomaticTasks&lt;/code&gt;. Явно заданное значение &lt;code&gt;off&lt;/code&gt; запрещает их запуск; &lt;code&gt;on&lt;/code&gt; разрешает запуск в доверенной рабочей области без нового вопроса. Если выбор для неё ещё не сделан, редактор предлагает разрешить или запретить автозадачи. Поэтому отсутствие диалога при конкретном открытии папки само по себе ничего не доказывает. [1]&lt;/p&gt;
&lt;h2 id="читаем-задачу-как-программу"&gt;Читаем задачу как программу
&lt;/h2&gt;&lt;p&gt;Ниже одна задача из массива &lt;code&gt;tasks&lt;/code&gt;, а не полный файл конфигурации. Фрагмент нужно читать, а не сохранять в действующий &lt;code&gt;.vscode/tasks.json&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;label&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Prepare preview&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;process&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;command&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;node&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;args&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;webfonts/preview.woff2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;runOptions&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;runOn&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;folderOpen&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Название &lt;code&gt;Prepare preview&lt;/code&gt; ничего не доказывает: это подпись в интерфейсе. Значимые сведения находятся в других полях. Исполняемой программой указан &lt;code&gt;node&lt;/code&gt;; аргумент похож на путь к шрифту; повод для запуска — открытие папки.&lt;/p&gt;
&lt;p&gt;Из этого пока следует один вывод: &lt;strong&gt;конфигурация предлагает передать файл с расширением &lt;code&gt;.woff2&lt;/code&gt; интерпретатору Node.js&lt;/strong&gt;. У задачи необычное назначение, и целевой файл нужно исследовать. Но утверждение «из компьютера уже украли ключи» здесь не имеет опоры.&lt;/p&gt;
&lt;p&gt;В настоящем проекте также проверяют рабочий каталог задачи, зависимости от других задач и отдельные варианты команд для разных операционных систем. Один просмотр строки &lt;code&gt;command&lt;/code&gt; может не показать всей последовательности. [1]&lt;/p&gt;
&lt;h2 id="три-разных-вопроса-вместо-одного"&gt;Три разных вопроса вместо одного
&lt;/h2&gt;&lt;p&gt;Разбор становится точнее, когда мы разделяем возможность, событие и последствие.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Что умеет делать проект?&lt;/strong&gt; Ответ дают конфигурация, вызываемые файлы и доступные ветви кода.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Что действительно выполнялось?&lt;/strong&gt; Нужны журналы задач, данные о процессах, сообщения пользователя и другие следы. Настроенный автозапуск не равен зарегистрированному запуску.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;К чему привело выполнение?&lt;/strong&gt; Здесь потребуются сведения о полученном ответе сервера, созданных файлах, доступе к данным и действиях в учётных записях. Наличие загрузчика само по себе не описывает содержимое следующей стадии.&lt;/p&gt;
&lt;p&gt;Это разделение пригодится во всём цикле. Оно позволяет серьёзно отнестись к риску, не превращая вероятный сценарий в установленный факт.&lt;/p&gt;
&lt;h2 id="как-осматривать-внешний-проект"&gt;Как осматривать внешний проект
&lt;/h2&gt;&lt;p&gt;Первое чтение лучше проводить в отдельной среде без рабочих секретов. Не используйте содержащиеся в проекте команды проверки, скрипты установки и конфигурации инструментов, пока не поняли их назначение. Для начала достаточно прочитать список файлов и интересующие фрагменты как данные.&lt;/p&gt;
&lt;p&gt;Если используется VS Code, оставьте проект в Restricted Mode и проверьте, не наследует ли каталог доверие. Этот режим ограничивает определённые возможности редактора, но не изолирует команду, которую вы сами затем запустите в терминале. [2]&lt;/p&gt;
&lt;p&gt;Для незнакомого проекта важны не только настройки IDE. В следующих статьях мы отдельно посмотрим на содержимое «ассетов», импорты JavaScript и хуки установки. Удаление одной подозрительной задачи не гарантирует, что других путей запуска нет.&lt;/p&gt;
&lt;h2 id="задача-какой-вывод-выдерживает-проверку"&gt;Задача: какой вывод выдерживает проверку?
&lt;/h2&gt;&lt;p&gt;Файлы для самостоятельного разбора доступны в &lt;a class="link" href="https://ctf.msk.ru/p/repo-traps/#%d0%bf%d1%80%d0%b0%d0%ba%d1%82%d0%b8%d0%ba%d1%83%d0%bc" &gt;синтетическом практикуме&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Вам дали фрагмент выше и три сообщения:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;А. Архив скачан. Папка в редакторе не открывалась.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Б. Папка открывалась в Restricted Mode. Задачи не разрешали.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;В. Сохранилась запись запуска: node webfonts/preview.woff2.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Что можно утверждать в каждом случае?&lt;/p&gt;
&lt;details class="repo-traps-answer"&gt;
&lt;summary&gt;Показать разбор&lt;/summary&gt;
&lt;p&gt;&lt;strong&gt;Разбор.&lt;/strong&gt; В случае А известен только факт получения архива. В случае Б описанный механизм автозадачи должен быть заблокирован режимом доверия; это не универсальное заключение о любых возможных действиях на машине. В случае В есть свидетельство запуска интерпретатора с конкретным аргументом. Для вывода об успешном выполнении файла нужны результат запуска и последующие события.&lt;/p&gt;
&lt;p&gt;Следующий шаг для всех трёх случаев одинаков: проверить содержимое &lt;code&gt;preview.woff2&lt;/code&gt;. Следующий шаг для расследования компьютера — разный: в случае В потребуется восстановить ход выполнения.&lt;/p&gt;
&lt;/details&gt;
&lt;h2 id="что-стоит-запомнить"&gt;Что стоит запомнить
&lt;/h2&gt;&lt;p&gt;Опасность находится не в названии каталога &lt;code&gt;.vscode&lt;/code&gt;, а в переходе от полученных файлов к исполнению команд. Хороший разбор показывает этот переход и его условия. Именно такой навык нужен и в CTF, и при проверке чужого проекта.&lt;/p&gt;
&lt;h3 id="источники"&gt;Источники
&lt;/h3&gt;&lt;p&gt;[1] &lt;a class="link" href="https://code.visualstudio.com/docs/debugtest/tasks" target="_blank" rel="noopener"
&gt;VS Code: Tasks — настройка задач и управление автоматическим запуском&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[2] &lt;a class="link" href="https://code.visualstudio.com/docs/editing/workspaces/workspace-trust" target="_blank" rel="noopener"
&gt;VS Code: Workspace Trust — Restricted Mode и наследование доверия&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;История происхождения цикла основана на сообщениях участников и рабочих записях разбора от 12 января 2026 года. Повторная проверка исходных архивов не входит в эту редакцию. Поведение редактора сверено с документацией при редакции 13 ноября 2026 года; для расследования старого запуска нужна версия редактора того времени.&lt;/p&gt;
&lt;nav class="repo-traps-nav" aria-label="Навигация по циклу"&gt;
&lt;p&gt;Статья 1 из 8 · &lt;a href="https://ctf.msk.ru/p/repo-traps/"&gt;Оглавление цикла&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel="next" href="https://ctf.msk.ru/p/repo-traps-assets/"&gt;Следующая: Шрифт, который оказался скриптом: проверяем формат и назначение файла →&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/nav&gt;</description></item><item><title>Следы в сети: как связать процесс, запрос и файл — и не додумать лишнего</title><link>https://ctf.msk.ru/p/repo-traps-network/</link><pubDate>Fri, 13 Nov 2026 00:00:00 +0300</pubDate><guid>https://ctf.msk.ru/p/repo-traps-network/</guid><description>&lt;p&gt;В подозрительном проекте найден сетевой адрес. В журнале есть обращение к похожему домену. Возникает соблазн сразу написать: «Загрузчик связался с командным сервером и похитил данные».&lt;/p&gt;
&lt;p&gt;Но между этими предложениями спрятано несколько неподтверждённых переходов. Последняя статья цикла посвящена тому, как собрать связное объяснение из сетевых и системных следов, не приписывая им больше, чем они показывают.&lt;/p&gt;
&lt;h2 id="что-сообщает-каждый-источник"&gt;Что сообщает каждый источник
&lt;/h2&gt;&lt;p&gt;DNS-журнал может показать запрос имени и ответ резолвера. Сам по себе он не устанавливает успешное HTTP-соединение, получение полезной нагрузки или исполнение ответа. Процесс мог запросить имя и завершиться с ошибкой; запрос мог выполнить другой компонент.&lt;/p&gt;
&lt;p&gt;Сетевой поток показывает соединение и, в зависимости от источника, адреса, время и объём передачи. Для HTTPS путь URL, заголовки и тело запроса защищены TLS. Обычный пассивный захват без ключей расшифрования не позволяет искать в них &lt;code&gt;x-secret-header&lt;/code&gt;. Такие сведения могут быть доступны в журнале приложения, на узле завершения TLS или в разрешённой системе инспекции. [1]&lt;/p&gt;
&lt;p&gt;Имя из SNI иногда помогает, но его наблюдаемость нельзя считать обязательной: ECH предназначен для шифрования внутреннего ClientHello, включая целевое имя. При его использовании наблюдаемое внешнее имя может относиться к общей инфраструктуре, а не к целевому сайту. При этом ECH сам по себе не скрывает IP-адрес назначения и открытые DNS-запросы. [2]&lt;/p&gt;
&lt;p&gt;Журнал процессов связывает событие с программой, аргументами и родительским процессом. Для корреляции желательно иметь не только PID, но и время создания либо устойчивый идентификатор процесса: один числовой PID может использоваться повторно. Точный состав полей зависит от средства сбора.&lt;/p&gt;
&lt;h2 id="собираем-цепочку"&gt;Собираем цепочку
&lt;/h2&gt;&lt;p&gt;Для учебной задачи полезна следующая схема:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;действие в рабочем проекте
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → процесс и его родитель
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → сетевое событие того же процесса
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → полученный или созданный файл
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → запуск этого файла либо другое последующее действие
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Это план проверки, а не описание обязательной атаки. Следующая стадия может исполняться в памяти, сеть может не сработать, а сохранённый файл может оказаться безобидным. Каждая стрелка требует собственного свидетельства.&lt;/p&gt;
&lt;p&gt;Хорошая корреляция использует один компьютер, согласованное время, устойчивую связь процессов и содержание артефактов. Совпадение «где-то запущен Node.js» и «кто-то обратился к облачному сервису» слишком широко для вывода о вредоносном поведении.&lt;/p&gt;
&lt;h2 id="учебный-журнал"&gt;Учебный журнал
&lt;/h2&gt;&lt;p&gt;Файлы для самостоятельного разбора доступны в &lt;a class="link" href="https://ctf.msk.ru/p/repo-traps/#%d0%bf%d1%80%d0%b0%d0%ba%d1%82%d0%b8%d0%ba%d1%83%d0%bc" &gt;синтетическом практикуме&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Ниже синтетические данные одного компьютера за один день; время приведено в UTC. Имя &lt;code&gt;delivery.invalid&lt;/code&gt; и идентификаторы придуманы для упражнения; обращаться к ним не нужно. Поле &lt;code&gt;entity&lt;/code&gt; обозначает устойчивый идентификатор экземпляра процесса, а &lt;code&gt;status=connected&lt;/code&gt; в этом условном формате — успешно установленное TLS-соединение.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;10:00:02 host=lab-1 event=process entity=p17 parent=editor-1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; command=&amp;#34;node webfonts/preview.woff2&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;10:00:03 host=lab-1 event=dns process=p17 query=delivery.invalid
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;10:00:04 host=lab-1 event=tls process=p17 status=connected
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;10:00:05 host=lab-1 event=file-write process=p17 path=/lab/stage.js
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;10:00:06 host=lab-1 event=process entity=p18 parent=p17
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; command=&amp;#34;node /lab/stage.js&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Сформулируйте подтверждённые события и укажите, каких данных не хватает для вывода о доставке кода и краже секретов.&lt;/p&gt;
&lt;details class="repo-traps-answer"&gt;
&lt;summary&gt;Показать разбор журнала&lt;/summary&gt;
&lt;p&gt;&lt;strong&gt;Что подтверждено при условии достоверности журнала?&lt;/strong&gt; Процесс &lt;code&gt;p17&lt;/code&gt; запущен с необычным аргументом, ему сопоставлены сетевые события и запись файла. Затем зарегистрирован дочерний процесс Node.js с путём к этому файлу в аргументах.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Чего не хватает?&lt;/strong&gt; В событии TLS нет адреса назначения или идентификатора, связывающего его с DNS-ответом: даже один и тот же процесс может параллельно обращаться к разным серверам. Без содержимого сетевого ответа нельзя утверждать, что файл целиком получен именно из этого соединения. Для связи записанного файла с исполненным также нужны сведения о его содержимом и изменениях между событиями: одно совпадение пути не устанавливает тождество байтов. Без содержимого &lt;code&gt;stage.js&lt;/code&gt; и дальнейших следов неизвестно, выполнилась ли кража данных. Даже успешный запуск интерпретатора не доказывает, что программа дошла до нужной исследователю ветви.&lt;/p&gt;
&lt;p&gt;Итоговая запись может звучать так: «Зафиксирована последовательность запуска, сети, записи файла и запуска дочернего процесса; предполагаемая доставка следующей стадии требует подтверждения содержимым артефактов».&lt;/p&gt;
&lt;/details&gt;
&lt;h2 id="как-использовать-индикаторы"&gt;Как использовать индикаторы
&lt;/h2&gt;&lt;p&gt;Индикатор полезен вместе с происхождением: из какого файла или события он получен, когда наблюдался, что предположительно обозначает и насколько точен.&lt;/p&gt;
&lt;p&gt;Хэш идентифицирует конкретные байты. Путь может совпасть у постороннего файла. Домен со временем может сменить содержимое или владельца. Поэтому старый адрес из исходного кода — историческая зацепка, а не доказательство сегодняшней активности.&lt;/p&gt;
&lt;p&gt;Для публичного текста подозрительные адреса удобно обезвреживать записью вида &lt;code&gt;hxxps://host[.]example/path&lt;/code&gt;. Это защита от случайного перехода, не способ изменить смысл индикатора. Для машинного использования исходное значение хранят отдельно, с контролем доступа и отметкой источника.&lt;/p&gt;
&lt;p&gt;Особенно осторожно следует обращаться с общей инфраструктурой. Наблюдение одного подозрительного поддомена не делает вредоносными весь &lt;code&gt;vercel.app&lt;/code&gt;, все соседние проекты или общий IP-адрес. Блокировка должна соответствовать доказанному объекту и учитывать сопутствующий ущерб.&lt;/p&gt;
&lt;h2 id="mitre-attck-описывает-действия-а-не-заменяет-доказательства"&gt;MITRE ATT&amp;amp;CK описывает действия, а не заменяет доказательства
&lt;/h2&gt;&lt;p&gt;Если установлен контекст атаки, исполнение JavaScript можно соотнести с T1059.007, а подтверждённую доставку дополнительного инструмента или файла в скомпрометированную среду — с T1105. Обычные загрузки и запуск легитимного JavaScript сами по себе не становятся техниками атакующего. Но запрос имени ещё не доказывает передачу, а наличие кода загрузки показывает возможность, а не обязательно наблюдённое действие на конкретном компьютере. [3][4]&lt;/p&gt;
&lt;p&gt;Аналогично нельзя добавлять в отчёт закрепление или эксфильтрацию только потому, что загрузчик теоретически способен запустить произвольную программу. Для этих выводов нужны отдельные следы.&lt;/p&gt;
&lt;p&gt;Совпадающие адреса, имена файлов и шаблоны кода могут поддерживать гипотезу о связи образцов. Они не устанавливают автора, страну происхождения и принадлежность к известной группе без дополнительных данных.&lt;/p&gt;
&lt;h2 id="задача-уберите-лишнее-из-заключения"&gt;Задача: уберите лишнее из заключения
&lt;/h2&gt;&lt;p&gt;Исследователь пишет: «В архиве найден URL. Компьютер обращался к этому домену. Значит, пароль от облака украден».&lt;/p&gt;
&lt;details class="repo-traps-answer"&gt;
&lt;summary&gt;Показать разбор&lt;/summary&gt;
&lt;p&gt;&lt;strong&gt;Разбор.&lt;/strong&gt; Первая часть описывает содержимое архива. Вторая — сетевое событие, которое ещё нужно связать с нужным процессом. Третья не следует из первых двух. Для неё требуются сведения о доступе к секрету, передаче данных или последующем использовании учётной записи. При этом защитные действия могут быть оправданы до окончательного подтверждения утечки.&lt;/p&gt;
&lt;/details&gt;
&lt;h2 id="завершение-цикла"&gt;Завершение цикла
&lt;/h2&gt;&lt;p&gt;Начинали мы с настройки редактора, а пришли к проверке доказательств. Во всех статьях работал один приём: найти переход, сформулировать гипотезу и выяснить, какие данные её подтверждают.&lt;/p&gt;
&lt;p&gt;Проверить файл — не то же самое, что проверить его расширение. Найти загрузчик — не то же самое, что установить его исполнение. Зафиксировать соединение — не то же самое, что доказать утечку. Умение удерживать эти различия делает технический разбор полезным и для решения CTF-задачи, и для реального расследования.&lt;/p&gt;
&lt;h3 id="источники"&gt;Источники
&lt;/h3&gt;&lt;p&gt;[1] &lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc8446.html" target="_blank" rel="noopener"
&gt;RFC 8446: TLS 1.3&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[2] &lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc9849.html" target="_blank" rel="noopener"
&gt;RFC 9849: TLS Encrypted Client Hello&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[3] &lt;a class="link" href="https://attack.mitre.org/techniques/T1059/007/" target="_blank" rel="noopener"
&gt;MITRE ATT&amp;amp;CK: T1059.007 — JavaScript&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[4] &lt;a class="link" href="https://attack.mitre.org/techniques/T1105/" target="_blank" rel="noopener"
&gt;MITRE ATT&amp;amp;CK: T1105 — Ingress Tool Transfer&lt;/a&gt;.&lt;/p&gt;
&lt;nav class="repo-traps-nav" aria-label="Навигация по циклу"&gt;
&lt;p&gt;Статья 8 из 8 · &lt;a href="https://ctf.msk.ru/p/repo-traps/"&gt;Оглавление цикла&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel="prev" href="https://ctf.msk.ru/p/repo-traps-hiring/"&gt;← Предыдущая: Тестовое задание на отдельной машине: порядок приёма внешнего кода&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/nav&gt;</description></item><item><title>Тестовое задание на отдельной машине: порядок приёма внешнего кода</title><link>https://ctf.msk.ru/p/repo-traps-hiring/</link><pubDate>Fri, 13 Nov 2026 00:00:00 +0300</pubDate><guid>https://ctf.msk.ru/p/repo-traps-hiring/</guid><description>&lt;p&gt;Тестовое задание создаёт понятное ожидание: кандидат должен показать, что умеет работать с проектом. Этим можно воспользоваться, подменив проверку навыков просьбой запустить неизвестный код в рабочем окружении.&lt;/p&gt;
&lt;p&gt;Такие сценарии описаны исследователями Unit 42 на примере Contagious Interview: злоумышленники выдавали себя за участников процесса найма и предлагали разработчикам заражённое программное обеспечение. Это подтверждает существование вектора, но не позволяет автоматически отнести любой подозрительный репозиторий к той же кампании. [1]&lt;/p&gt;
&lt;p&gt;Разумный порядок приёма задания защищает обе стороны. Кандидату не нужно оправдываться за осторожность, а компания получает воспроизводимый результат вместо случайного запуска на чьём-то ноутбуке.&lt;/p&gt;
&lt;h2 id="проверяем-связь-а-не-впечатление"&gt;Проверяем связь, а не впечатление
&lt;/h2&gt;&lt;p&gt;Название известной компании, логотип и убедительная переписка не подтверждают происхождение репозитория. Уместно проверить вакансию и контакт по независимому каналу: через официальный сайт или известного представителя, а не только по ссылкам из того же сообщения.&lt;/p&gt;
&lt;p&gt;Это не техническая экспертиза кода. Подтверждённый сотрудник тоже может прислать ошибочный или скомпрометированный материал. Поэтому проверка личности отправителя и проверка артефакта решают разные задачи.&lt;/p&gt;
&lt;p&gt;Небольшой или новый репозиторий сам по себе не является уликой. Для обычного тестового задания это может быть нормальным устройством. Более содержательный сигнал — настойчивое требование отключить защиту, разрешить непонятные автоматические действия или использовать личные ключи без объяснимой необходимости.&lt;/p&gt;
&lt;h2 id="до-запуска-нужно-определить-условия"&gt;До запуска нужно определить условия
&lt;/h2&gt;&lt;p&gt;Для нормального задания достаточно заранее договориться о результате, используемых версиях инструментов, ожидаемых командах и допустимых внешних сервисах. Полезно получить фиксированный снимок проекта: иначе две стороны могут обсуждать разные состояния ветки.&lt;/p&gt;
&lt;p&gt;Если проект требует доступа к API, компания предоставляет ограниченную тестовую учётную запись или замену сервиса. Для оценки навыков не должны понадобиться реальные seed-фразы, личные ключи от облака или доступ к рабочей инфраструктуре кандидата.&lt;/p&gt;
&lt;p&gt;Со стороны компании стоит стремиться к минимальному явному сценарию запуска. Автоматические задачи при открытии, непрозрачные установочные действия и скачивание исполняемого кода без проверяемого происхождения создают ненужный риск и мешают воспроизводимости задания. Это предлагаемая политика приёма, а не утверждение, что любой установочный хук вредоносен.&lt;/p&gt;
&lt;h2 id="изоляция-определяется-доступами"&gt;Изоляция определяется доступами
&lt;/h2&gt;&lt;p&gt;Для заведомо подозрительного проекта разумнее отдельная одноразовая виртуальная машина с чистым состоянием. В ней не должно быть рабочих секретов, подключённого домашнего каталога, перенаправленного SSH-agent, профиля основного браузера или свободного доступа во внутреннюю сеть. Обмен папками и буфером обмена включают только при обоснованной необходимости. Для первоначального разбора сеть отключают. Если динамическое исследование требует сетевого обмена, отдельно готовят контролируемую лабораторную сеть с журналированием и подменой внешних сервисов; соединения с адресами из образца не разрешают автоматически.&lt;/p&gt;
&lt;p&gt;Контейнер может быть частью такой среды, но слово Docker не даёт гарантии. Проброшенный каталог, доступ к Docker socket и избыточные полномочия способны разрушить ожидаемую изоляцию. В Linux контейнеры обычно используют ядро хоста, поэтому их граница отличается от границы отдельной виртуальной машины. Docker Desktop на macOS запускает контейнеры в виртуальной машине, но разрешённые общие каталоги и доступ к демону остаются существенны. [4] Документация Docker отдельно разбирает последствия этих доступов. [2]&lt;/p&gt;
&lt;p&gt;Готовый контейнер, предоставленный отправителем задания, тоже содержит недоверенный код. Проверять нужно и способ его запуска: образ, параметры, тома, сеть, переменные среды. Он не становится доверенным только потому, что его удобно запускать одной командой.&lt;/p&gt;
&lt;h2 id="когда-остановиться"&gt;Когда остановиться
&lt;/h2&gt;&lt;p&gt;Если найден путь от внешнего ответа к исполнению, замаскированный загрузчик или необъяснимый автозапуск, для оценки задания не нужно добиваться заражения. Можно зафиксировать находку, приостановить запуск и предложить безопасную альтернативу: статический разбор, очищенный вариант или заранее подготовленное демо.&lt;/p&gt;
&lt;p&gt;Не следует превращать проверку в исследование чужой инфраструктуры без разрешения. Адрес, найденный в коде, — материал для анализа, а не приглашение отправлять запросы на действующий сервер.&lt;/p&gt;
&lt;h2 id="короткий-ответ-отправителю"&gt;Короткий ответ отправителю
&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;Спасибо, проект получил. Сначала проверю его без запуска. Внешние проекты запускаю в отдельной среде без личных и рабочих доступов. Пришлите, пожалуйста, ожидаемый результат задания, версии инструментов и полный сценарий запуска. Если нужны внешние сервисы, потребуется тестовая учётная запись с ограниченными правами.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Формулировка не обвиняет собеседника и не перечисляет случайные «страшные слова». Она устанавливает понятный порядок работы.&lt;/p&gt;
&lt;h2 id="как-сообщить-о-подозрительном-репозитории"&gt;Как сообщить о подозрительном репозитории
&lt;/h2&gt;&lt;p&gt;В сообщении площадке полезны точный адрес объекта, идентификатор исследованного состояния, путь к файлу, минимальный фрагмент, объясняющий поведение, и способ получения доказательств. Отдельно отмечают, был ли это статический анализ и известен ли фактический запуск.&lt;/p&gt;
&lt;p&gt;Atlassian принимает сообщения о вредоносном содержимом Bitbucket через поддержку и адрес &lt;code&gt;abuse@atlassian.com&lt;/code&gt;. В инструкции предлагается указать ссылку на материал и описание проблемы; отрицательный или неопределённый результат сканирования не препятствует сообщению. [3]&lt;/p&gt;
&lt;p&gt;Не нужно публиковать секреты, персональные данные или весь архив только ради убедительности. Наличие &lt;code&gt;eval&lt;/code&gt; без контекста — слабый отчёт. Небольшой фрагмент с доказанной цепочкой и аккуратным описанием значительно полезнее.&lt;/p&gt;
&lt;h2 id="задача-что-спросить-у-рекрутера"&gt;Задача: что спросить у «рекрутера»?
&lt;/h2&gt;&lt;p&gt;Вам пишут: «Сначала выполните все команды из README на своём основном компьютере. Виртуальную машину не используйте. Если редактор покажет предупреждение, разрешите всё — иначе тест не работает».&lt;/p&gt;
&lt;p&gt;Составьте ответ отправителю. Задайте три вопроса о необходимых разрешениях, причине запрета виртуальной машины и критерии прохождения. Предложите один способ выполнить ту же задачу с меньшим доступом.&lt;/p&gt;
&lt;details class="repo-traps-answer"&gt;
&lt;summary&gt;Показать разбор&lt;/summary&gt;
&lt;p&gt;&lt;strong&gt;Возможный ответ.&lt;/strong&gt; «Какие конкретно разрешения нужны и каким шагам? Почему задание не работает в виртуальной машине? По какому результату вы будете оценивать выполнение? Я могу выполнить задание в одноразовой среде без личных данных и предоставить результат и журнал запуска».&lt;/p&gt;
&lt;p&gt;По сообщению можно установить, что отправитель требует расширить доступ до объяснения технической необходимости. Для атрибуции или вывода о компрометации нужны отдельные свидетельства.&lt;/p&gt;
&lt;/details&gt;
&lt;h2 id="что-стоит-запомнить"&gt;Что стоит запомнить
&lt;/h2&gt;&lt;p&gt;Доверие к собеседнику не должно незаметно превращаться в неограниченные полномочия для присланного кода. Хорошо организованное тестовое задание проверяет навыки и допускает понятные границы безопасности.&lt;/p&gt;
&lt;h3 id="источники"&gt;Источники
&lt;/h3&gt;&lt;p&gt;[1] &lt;a class="link" href="https://unit42.paloaltonetworks.com/two-campaigns-by-north-korea-bad-actors-target-job-hunters/" target="_blank" rel="noopener"
&gt;Unit 42: Hacking Employers and Seeking Employment — исследование Contagious Interview&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[2] &lt;a class="link" href="https://docs.docker.com/engine/security/" target="_blank" rel="noopener"
&gt;Docker: Engine security&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[3] &lt;a class="link" href="https://support.atlassian.com/bitbucket-cloud/kb/report-malware-hosted-on-bitbucket-cloud/" target="_blank" rel="noopener"
&gt;Atlassian: Report malware hosted on Bitbucket Cloud&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[4] &lt;a class="link" href="https://docs.docker.com/desktop/setup/install/mac-permission-requirements/" target="_blank" rel="noopener"
&gt;Docker Desktop for Mac: разрешения и контейнеры внутри Linux VM&lt;/a&gt;.&lt;/p&gt;
&lt;nav class="repo-traps-nav" aria-label="Навигация по циклу"&gt;
&lt;p&gt;Статья 7 из 8 · &lt;a href="https://ctf.msk.ru/p/repo-traps/"&gt;Оглавление цикла&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel="prev" href="https://ctf.msk.ru/p/repo-traps-static-control/"&gt;← Предыдущая: Проверка репозитория до запуска: как устроить безопасный статический контроль&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel="next" href="https://ctf.msk.ru/p/repo-traps-network/"&gt;Следующая: Следы в сети: как связать процесс, запрос и файл — и не додумать лишнего →&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/nav&gt;</description></item><item><title>Что выполняется при npm install: хуки, зависимости и границы ignore-scripts</title><link>https://ctf.msk.ru/p/repo-traps-npm/</link><pubDate>Fri, 13 Nov 2026 00:00:00 +0300</pubDate><guid>https://ctf.msk.ru/p/repo-traps-npm/</guid><description>&lt;p&gt;«Я только поставил зависимости, приложение не запускал». Для JavaScript-проекта это важное уточнение, но ещё не ответ на вопрос об исполнении кода.&lt;/p&gt;
&lt;p&gt;npm поддерживает скрипты жизненного цикла. В зависимости от команды, версии и типа пакета установка может вызвать &lt;code&gt;preinstall&lt;/code&gt;, &lt;code&gt;install&lt;/code&gt;, &lt;code&gt;postinstall&lt;/code&gt;, &lt;code&gt;prepare&lt;/code&gt; и связанные этапы. Такие скрипты встречаются в обычной разработке: например, для подготовки сборки. Их наличие требует проверки назначения, а не автоматического обвинения в распространении вредоносного кода. [1]&lt;/p&gt;
&lt;h2 id="где-заканчивается-скачивание-файлов"&gt;Где заканчивается «скачивание файлов»
&lt;/h2&gt;&lt;p&gt;Рассмотрим учебный фрагмент корневого &lt;code&gt;package.json&lt;/code&gt;. Для этой цепочки и задачи в конце статьи используем npm 11 с разрешёнными скриптами жизненного цикла:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;scripts&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;start&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;node server/app.js&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;postinstall&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;npm start&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;prepare&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;node tools/build.js&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Здесь &lt;code&gt;postinstall&lt;/code&gt; вызывает команду запуска приложения. Чтобы понять риск, нужно прочитать &lt;code&gt;server/app.js&lt;/code&gt;, затем проследить его импорты. Название &lt;code&gt;tools/build.js&lt;/code&gt; тоже не гарантирует, что файл только собирает проект.&lt;/p&gt;
&lt;p&gt;Важен весь путь:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;установка с разрешёнными lifecycle scripts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → postinstall
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → npm start
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → server/app.js
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → импорт модуля
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → действие на верхнем уровне этого модуля
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Любое звено может изменить результат: пакет не установился, команда завершилась с ошибкой, импорт не достигнут, сетевая ветвь не сработала. Статический разбор показывает возможную цепочку; журнал конкретного запуска помогает установить состоявшуюся.&lt;/p&gt;
&lt;h2 id="почему-недостаточно-корневого-packagejson"&gt;Почему недостаточно корневого package.json
&lt;/h2&gt;&lt;p&gt;Проверка начинается с корневого файла, но продолжается в рабочих пакетах монорепозитория и зависимостях. Отдельного внимания требуют Git-зависимости и операции подготовки пакета: условия вызова &lt;code&gt;prepare&lt;/code&gt; отличаются от простого запуска произвольного скрипта. Точную последовательность нужно сверять с документацией используемой версии npm. [1]&lt;/p&gt;
&lt;p&gt;Также стоит прочитать &lt;code&gt;.npmrc&lt;/code&gt;. Это конфигурация менеджера пакетов, а значит часть условий выполнения: откуда берутся зависимости и какие параметры применяются. Не копируйте в чужую среду свой пользовательский конфиг вместе с токенами доступа. [2]&lt;/p&gt;
&lt;p&gt;Само получение проекта из репозитория ещё не доказывает компрометацию публичного реестра пакетов. В нашем учебном сценарии достаточно вредоносной логики в корневом проекте. Поэтому точнее говорить о запуске недоверенного проекта через установочные хуки, а не объявлять любой такой случай атакой на npm registry.&lt;/p&gt;
&lt;h2 id="что-даёт---ignore-scripts"&gt;Что даёт &lt;code&gt;--ignore-scripts&lt;/code&gt;
&lt;/h2&gt;&lt;p&gt;При установке этот параметр отключает выполнение скриптов из &lt;code&gt;package.json&lt;/code&gt;. Но явная команда вроде &lt;code&gt;npm start&lt;/code&gt;, &lt;code&gt;npm test&lt;/code&gt; или &lt;code&gt;npm run build&lt;/code&gt; запускает выбранный скрипт даже при действующем &lt;code&gt;ignore-scripts&lt;/code&gt;; подавляются только сопутствующие pre/post-этапы. Это прямо описано в документации npm. [2]&lt;/p&gt;
&lt;p&gt;Флаг одного вызова не становится постоянной настройкой: после &lt;code&gt;npm ci --ignore-scripts&lt;/code&gt; отдельный &lt;code&gt;npm start&lt;/code&gt; не наследует его. Если запрет не задан в конфигурации или окружении, вместе со &lt;code&gt;start&lt;/code&gt; могут сработать и &lt;code&gt;prestart&lt;/code&gt;/&lt;code&gt;poststart&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Следовательно, сочетание «установил с &lt;code&gt;--ignore-scripts&lt;/code&gt;, затем сразу запустил &lt;code&gt;npm start&lt;/code&gt;» не сохраняет запрет на выполнение кода. Оно только переносит момент исполнения.&lt;/p&gt;
&lt;p&gt;Для анализа известного снимка с согласованным lockfile в отдельной подготовленной среде возможна команда:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm ci --ignore-scripts
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Это &lt;strong&gt;не первый шаг проверки неизвестного архива&lt;/strong&gt;. Сначала читают конфигурацию и оценивают происхождение зависимостей. Даже такая установка получает и обрабатывает внешние данные, поэтому её не следует представлять как безопасное действие на машине с рабочими секретами.&lt;/p&gt;
&lt;p&gt;OWASP рекомендует ограничивать установочные скрипты и проверять чужой проект до их разрешения. [3]&lt;/p&gt;
&lt;h2 id="почему-lockfile-не-выдаёт-сертификат-безопасности"&gt;Почему lockfile не выдаёт сертификат безопасности
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;npm ci&lt;/code&gt; требует lockfile, проверяет согласованность с &lt;code&gt;package.json&lt;/code&gt; и не обновляет его по ходу установки. Но команда не запрещает lifecycle scripts сама по себе. [4]&lt;/p&gt;
&lt;p&gt;Фиксация версии и контроль целостности помогают установить, какие именно байты получены. Они не доказывают, что эти байты доброкачественны. Зафиксированная вредоносная версия воспроизводится не хуже обычной.&lt;/p&gt;
&lt;p&gt;То же относится к результату проверки известных уязвимостей. &lt;code&gt;npm audit&lt;/code&gt; запрашивает сведения об известных проблемах зависимостей; отсутствие отчёта не является экспертизой произвольного JavaScript, скрытого в файлах проекта. [5]&lt;/p&gt;
&lt;h2 id="разрешение-скриптов-как-отдельное-решение"&gt;Разрешение скриптов как отдельное решение
&lt;/h2&gt;&lt;p&gt;После первичного анализа может выясниться, что конкретной зависимости действительно требуется подготовительный этап. Тогда разрешение привязывают к известному пакету, версии и понятной команде, а не ко всему содержимому репозитория.&lt;/p&gt;
&lt;p&gt;При редакции в ноябре 2026 года документация npm 12 уже описывает политику &lt;code&gt;allowScripts&lt;/code&gt;: установочные скрипты зависимостей без соответствующего разрешения пропускаются. Это важное отличие от прежних выпусков, но оно не отменяет проверку корневого проекта. Его собственные хуки при обычном локальном &lt;code&gt;npm install&lt;/code&gt; обрабатываются отдельно и не становятся безопасными из-за запрета скриптов зависимостей. [6][7]&lt;/p&gt;
&lt;p&gt;В этой статье мы намеренно ограничиваемся npm. У Yarn и pnpm есть собственные настройки и меняющееся между версиями поведение. Переносить один флаг и считать результат одинаковым без проверки документации не стоит.&lt;/p&gt;
&lt;h2 id="задача-что-изменится-в-трёх-запусках"&gt;Задача: что изменится в трёх запусках?
&lt;/h2&gt;&lt;p&gt;Файлы для самостоятельного разбора доступны в &lt;a class="link" href="https://ctf.msk.ru/p/repo-traps/#%d0%bf%d1%80%d0%b0%d0%ba%d1%82%d0%b8%d0%ba%d1%83%d0%bc" &gt;синтетическом практикуме&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Для фрагмента выше рассматриваются действия:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;А. npm ci
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Б. npm ci --ignore-scripts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;В. npm ci --ignore-scripts, затем npm start
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;В упражнении предполагаются npm 11, согласованный lockfile, разрешённые политикой окружения lifecycle scripts и отсутствие дополнительных ограничений.&lt;/p&gt;
&lt;details class="repo-traps-answer"&gt;
&lt;summary&gt;Показать разбор&lt;/summary&gt;
&lt;p&gt;&lt;strong&gt;Разбор.&lt;/strong&gt; В А разрешена цепочка lifecycle scripts, поэтому &lt;code&gt;postinstall&lt;/code&gt; может запустить приложение. В Б эта автоматическая цепочка подавлена. В В отдельная команда &lt;code&gt;npm start&lt;/code&gt; всё же вызывает приложение. Ни один ответ не устанавливает, что именно приложение сделало: для этого нужно продолжить чтение и сопоставить его с журналами.&lt;/p&gt;
&lt;/details&gt;
&lt;h2 id="что-стоит-запомнить"&gt;Что стоит запомнить
&lt;/h2&gt;&lt;p&gt;Установка, сборка и тестирование — разные команды, но каждая может стать точкой исполнения. Проверять нужно не название действия, а программу, до которой оно доводит управление.&lt;/p&gt;
&lt;h3 id="источники"&gt;Источники
&lt;/h3&gt;&lt;p&gt;[1] &lt;a class="link" href="https://docs.npmjs.com/cli/v11/using-npm/scripts/" target="_blank" rel="noopener"
&gt;npm: Scripts — lifecycle events и условия prepare&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[2] &lt;a class="link" href="https://docs.npmjs.com/cli/v11/using-npm/config/" target="_blank" rel="noopener"
&gt;npm: Config — ignore-scripts и другие параметры&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[3] &lt;a class="link" href="https://cheatsheetseries.owasp.org/cheatsheets/NPM_Security_Cheat_Sheet.html" target="_blank" rel="noopener"
&gt;OWASP: NPM Security Cheat Sheet&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[4] &lt;a class="link" href="https://docs.npmjs.com/cli/v11/commands/npm-ci/" target="_blank" rel="noopener"
&gt;npm: npm ci&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[5] &lt;a class="link" href="https://docs.npmjs.com/cli/v11/commands/npm-audit/" target="_blank" rel="noopener"
&gt;npm: npm audit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[6] &lt;a class="link" href="https://docs.npmjs.com/cli/v12/commands/npm-install-scripts/" target="_blank" rel="noopener"
&gt;npm 12: управление разрешениями установочных скриптов зависимостей&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[7] &lt;a class="link" href="https://github.com/npm/cli/blob/latest/lib/commands/install.js" target="_blank" rel="noopener"
&gt;npm CLI: реализация install и отдельный вызов root lifecycle scripts&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Установочное поведение следует проверять для фактической версии npm. Статья не переносит возможности нынешних выпусков на событие января 2026 года.&lt;/p&gt;
&lt;nav class="repo-traps-nav" aria-label="Навигация по циклу"&gt;
&lt;p&gt;Статья 4 из 8 · &lt;a href="https://ctf.msk.ru/p/repo-traps/"&gt;Оглавление цикла&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel="prev" href="https://ctf.msk.ru/p/repo-traps-data-to-code/"&gt;← Предыдущая: От HTTP-ответа к выполнению кода: читаем опасную цепочку в JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel="next" href="https://ctf.msk.ru/p/repo-traps-response/"&gt;Следующая: После запуска подозрительного проекта: как собрать факты и ограничить ущерб →&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/nav&gt;</description></item><item><title>Шрифт, который оказался скриптом: проверяем формат и назначение файла</title><link>https://ctf.msk.ru/p/repo-traps-assets/</link><pubDate>Fri, 13 Nov 2026 00:00:00 +0300</pubDate><guid>https://ctf.msk.ru/p/repo-traps-assets/</guid><description>&lt;p&gt;В конфигурации проекта обнаружена команда, передающая Node.js файл &lt;code&gt;webfonts/preview.woff2&lt;/code&gt;. Название похоже на веб-шрифт. Но имя файла и его содержимое — разные источники сведений.&lt;/p&gt;
&lt;p&gt;В этой статье разберём, как проверить предполагаемый шрифт и почему обнаруженный JavaScript ещё не отвечает на все вопросы расследования. Примеры учебные: для них не нужен настоящий вредоносный архив.&lt;/p&gt;
&lt;h2 id="расширение--подсказка-а-не-доказательство"&gt;Расширение — подсказка, а не доказательство
&lt;/h2&gt;&lt;p&gt;У формата WOFF2 есть обязательная сигнатура: первые четыре байта должны быть &lt;code&gt;77 4f 46 32&lt;/code&gt;, то есть &lt;code&gt;wOF2&lt;/code&gt;. Это требование спецификации. Если сигнатуры нет, файл не соответствует WOFF2. Обратное заключение неверно: наличие четырёх правильных байтов ещё не доказывает корректность всего шрифта и тем более его безопасность. [1]&lt;/p&gt;
&lt;p&gt;Для первичного просмотра в подготовленной среде достаточно:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;file -- webfonts/preview.woff2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;xxd -g &lt;span class="m"&gt;1&lt;/span&gt; -l &lt;span class="m"&gt;32&lt;/span&gt; webfonts/preview.woff2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;file&lt;/code&gt; помогает определить предполагаемый тип. &lt;code&gt;xxd&lt;/code&gt; показывает сами байты, на которых можно проверить гипотезу. Не нужно передавать файл Node.js, открывать в просмотрщике шрифтов или запускать приложение.&lt;/p&gt;
&lt;p&gt;Предположим, начало выглядит так:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;00000000: 63 6f 6e 73 74 20 6d 61 72 6b 65 72 20 3d 20 22 const marker = &amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Сигнатуры WOFF2 нет. Мы видим начало текстовой конструкции, похожей на JavaScript. Корректная промежуточная запись: «Файл с расширением &lt;code&gt;.woff2&lt;/code&gt; не соответствует обязательному заголовку WOFF2 и содержит текст, похожий на исходный код».&lt;/p&gt;
&lt;p&gt;Здесь важно не перескочить сразу к слову «заражение». Можно столкнуться с ошибочно названным файлом, учебным примером или намеренной маскировкой. Назначение проясняется вместе с окружающим кодом.&lt;/p&gt;
&lt;h2 id="добавляем-контекст-запуска"&gt;Добавляем контекст запуска
&lt;/h2&gt;&lt;p&gt;Вернёмся к задаче редактора. В ней &lt;code&gt;node&lt;/code&gt; получает путь к этому же файлу. Теперь у нас два связанных наблюдения: содержимое не является заявленным шрифтом, а конфигурация пытается использовать его как программу.&lt;/p&gt;
&lt;p&gt;Расширение &lt;code&gt;.woff2&lt;/code&gt; само по себе не запрещает такой запуск. Загрузчик CommonJS в Node.js может читать файл с неизвестным расширением как JavaScript. Но это не универсальное правило: стандартный загрузчик ES modules отвергает неизвестные расширения, а выбор загрузчика зависит, в частности, от &lt;code&gt;type&lt;/code&gt; в ближайшем &lt;code&gt;package.json&lt;/code&gt; и флагов запуска. Для конкретной команды нужно проверить режим и версию Node.js. Здесь важна попытка передать файл интерпретатору; успешность исполнения ещё предстоит установить. [3][4][5]&lt;/p&gt;
&lt;p&gt;Следующий вопрос — что делает программа. Ищем обращения к сети, запись файлов, создание дочерних процессов и превращение полученных строк в код. Само слово &lt;code&gt;require&lt;/code&gt; ничего не доказывает; интерес представляет связная последовательность действий.&lt;/p&gt;
&lt;p&gt;В исходных рабочих записях этого цикла аналогичную роль играл файл &lt;code&gt;webfonts/fa-brands-regular.woff2&lt;/code&gt;. Это описание маскировки JavaScript расширением шрифта, а не доказательство эксплуатации уязвимости в обработчике настоящих шрифтов. Различать эти механизмы необходимо: у них разные точки исполнения и способы проверки.&lt;/p&gt;
&lt;p&gt;Возможен и другой вариант: файл хранит закодированные данные, а отдельный скрипт извлекает их и передаёт на исполнение. Тогда перед нами контейнер, и в доказательную цепочку обязательно входит этот обработчик. Обычный шрифт, который приложение просто отображает, тоже может соседствовать с вредоносным кодом. Одного имени файла недостаточно, чтобы приписать ему любую из этих ролей.&lt;/p&gt;
&lt;h2 id="а-что-со-словарём"&gt;А что со «словарём»?
&lt;/h2&gt;&lt;p&gt;Файл &lt;code&gt;.vscode/spellright.dict&lt;/code&gt; выглядит как вспомогательный словарь редактора. Но расширение &lt;code&gt;.dict&lt;/code&gt; без указания конкретного формата не даёт такой же универсальной проверки, как заголовок WOFF2.&lt;/p&gt;
&lt;p&gt;Здесь полезнее сопоставить предполагаемое назначение и содержимое. Список слов согласуется с ролью словаря. Сложный JavaScript, который устанавливает пакеты и запускает другой файл, — нет. Особенно значимо, если в задаче редактора прямо указан этот путь.&lt;/p&gt;
&lt;p&gt;Не следует делать отдельным правилом «любой &lt;code&gt;.dict&lt;/code&gt; подозрителен». Иначе проверка будет наказывать нормальную конфигурацию, не объясняя, где именно обнаружена опасность.&lt;/p&gt;
&lt;h2 id="почему-похожая-история-не-всегда-подтверждает-нашу"&gt;Почему похожая история не всегда подтверждает нашу
&lt;/h2&gt;&lt;p&gt;В одном из исследований Huntress WOFF2 использовался для подмены отображаемых символов: специально подобранные глифы превращали нечитаемую последовательность в правдоподобное имя файла на странице. Это другой приём. Он не доказывает, что в рассматриваемом здесь «шрифте» исполнялся JavaScript, и не связывает два случая с одной группой. [2]&lt;/p&gt;
&lt;p&gt;Ссылка на исследование полезна лишь тогда, когда подтверждает именно сформулированный тезис. Общего слова WOFF2 недостаточно.&lt;/p&gt;
&lt;h2 id="ограничиваем-просмотр-неизвестных-данных"&gt;Ограничиваем просмотр неизвестных данных
&lt;/h2&gt;&lt;p&gt;Неизвестный файл может содержать огромную строку или управляющие символы. Для небольшого текстового фрагмента удобно получить экранированное представление ограниченного числа байтов:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 -I -S -c &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;&amp;#39;import sys; f=open(sys.argv[1], &amp;#34;rb&amp;#34;); print(repr(f.read(256))); f.close()&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;webfonts/preview.woff2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Команда читает данные и печатает их представление; она не интерпретирует содержимое как JavaScript. Здесь предполагаются заранее подготовленные доверенные инструменты. Даже статический анализ неизвестных файлов разумно выполнять в отдельной среде с ограниченными ресурсами.&lt;/p&gt;
&lt;h2 id="задача-четыре-байта-и-две-гипотезы"&gt;Задача: четыре байта и две гипотезы
&lt;/h2&gt;&lt;p&gt;Файлы для самостоятельного разбора доступны в &lt;a class="link" href="https://ctf.msk.ru/p/repo-traps/#%d0%bf%d1%80%d0%b0%d0%ba%d1%82%d0%b8%d0%ba%d1%83%d0%bc" &gt;синтетическом практикуме&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Даны два начала файлов:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;A: 77 4f 46 32 00 01 00 00
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;B: 63 6f 6e 73 74 20 78 20
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Для файла B дополнительно найдена задача, передающая его Node.js. Для файла A сведений об использовании нет.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Вопросы:&lt;/strong&gt; какой файл прошёл проверку обязательной сигнатуры? Какой требует проверки назначения? Можно ли по этим данным объявить A безопасным, а B — уже исполненным вредоносом?&lt;/p&gt;
&lt;details class="repo-traps-answer"&gt;
&lt;summary&gt;Показать разбор&lt;/summary&gt;
&lt;p&gt;&lt;strong&gt;Разбор.&lt;/strong&gt; A прошёл только проверку первых четырёх байтов. B её не прошёл; начало читается как &lt;code&gt;const x &lt;/code&gt;, а задача указывает предполагаемый путь исполнения. B требует дальнейшего анализа кода. О состоявшемся запуске эти данные не сообщают. Безопасность A также не установлена.&lt;/p&gt;
&lt;/details&gt;
&lt;h2 id="что-стоит-запомнить"&gt;Что стоит запомнить
&lt;/h2&gt;&lt;p&gt;Сильный вывод складывается из нескольких наблюдений: несоответствие формата, содержимое программы и способ её вызова. Проверка расширений или поиск ключевого слова дают зацепку; доказательство появляется, когда удаётся связать её с поведением.&lt;/p&gt;
&lt;h3 id="источники"&gt;Источники
&lt;/h3&gt;&lt;p&gt;[1] &lt;a class="link" href="https://www.w3.org/TR/WOFF2/#woff20Header" target="_blank" rel="noopener"
&gt;W3C: WOFF File Format 2.0, раздел 3.2 — заголовок и сигнатура&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[2] &lt;a class="link" href="https://www.huntress.com/blog/gootloader-threat-detection-woff2-obfuscation" target="_blank" rel="noopener"
&gt;Huntress: Gootloader и обфускация имён с помощью WOFF2&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[3] &lt;a class="link" href="https://nodejs.org/api/modules.html#file-modules" target="_blank" rel="noopener"
&gt;Node.js: CommonJS — обработка расширений файлов&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[4] &lt;a class="link" href="https://nodejs.org/api/cli.html#program-entry-point" target="_blank" rel="noopener"
&gt;Node.js: выбор загрузчика точки входа&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[5] &lt;a class="link" href="https://nodejs.org/api/esm.html#resolution-and-loading-algorithm" target="_blank" rel="noopener"
&gt;Node.js: ES modules — разрешение и загрузка&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Название файла из исходного случая приведено по рабочим записям от 12 января 2026 года, без повторного исследования оригинального архива. Проверки и задача в статье самостоятельны и не требуют этого образца.&lt;/p&gt;
&lt;nav class="repo-traps-nav" aria-label="Навигация по циклу"&gt;
&lt;p&gt;Статья 2 из 8 · &lt;a href="https://ctf.msk.ru/p/repo-traps/"&gt;Оглавление цикла&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel="prev" href="https://ctf.msk.ru/p/repo-traps-vscode/"&gt;← Предыдущая: Репозиторий как ловушка: от настроек VS Code к запуску кода&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel="next" href="https://ctf.msk.ru/p/repo-traps-data-to-code/"&gt;Следующая: От HTTP-ответа к выполнению кода: читаем опасную цепочку в JavaScript →&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/nav&gt;</description></item></channel></rss>