Шрифт, который оказался скриптом: проверяем формат и назначение файла

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

В конфигурации проекта обнаружена команда, передающая Node.js файл webfonts/preview.woff2. Название похоже на веб-шрифт. Но имя файла и его содержимое — разные источники сведений.

В этой статье разберём, как проверить предполагаемый шрифт и почему обнаруженный JavaScript ещё не отвечает на все вопросы расследования. Примеры учебные: для них не нужен настоящий вредоносный архив.

Расширение — подсказка, а не доказательство

У формата WOFF2 есть обязательная сигнатура: первые четыре байта должны быть 77 4f 46 32, то есть wOF2. Это требование спецификации. Если сигнатуры нет, файл не соответствует WOFF2. Обратное заключение неверно: наличие четырёх правильных байтов ещё не доказывает корректность всего шрифта и тем более его безопасность. [1]

Для первичного просмотра в подготовленной среде достаточно:

1
2
file -- webfonts/preview.woff2
xxd -g 1 -l 32 webfonts/preview.woff2

file помогает определить предполагаемый тип. xxd показывает сами байты, на которых можно проверить гипотезу. Не нужно передавать файл Node.js, открывать в просмотрщике шрифтов или запускать приложение.

Предположим, начало выглядит так:

1
00000000: 63 6f 6e 73 74 20 6d 61 72 6b 65 72 20 3d 20 22  const marker = "

Сигнатуры WOFF2 нет. Мы видим начало текстовой конструкции, похожей на JavaScript. Корректная промежуточная запись: «Файл с расширением .woff2 не соответствует обязательному заголовку WOFF2 и содержит текст, похожий на исходный код».

Здесь важно не перескочить сразу к слову «заражение». Можно столкнуться с ошибочно названным файлом, учебным примером или намеренной маскировкой. Назначение проясняется вместе с окружающим кодом.

Добавляем контекст запуска

Вернёмся к задаче редактора. В ней node получает путь к этому же файлу. Теперь у нас два связанных наблюдения: содержимое не является заявленным шрифтом, а конфигурация пытается использовать его как программу.

Расширение .woff2 само по себе не запрещает такой запуск. Загрузчик CommonJS в Node.js может читать файл с неизвестным расширением как JavaScript. Но это не универсальное правило: стандартный загрузчик ES modules отвергает неизвестные расширения, а выбор загрузчика зависит, в частности, от type в ближайшем package.json и флагов запуска. Для конкретной команды нужно проверить режим и версию Node.js. Здесь важна попытка передать файл интерпретатору; успешность исполнения ещё предстоит установить. [3][4][5]

Следующий вопрос — что делает программа. Ищем обращения к сети, запись файлов, создание дочерних процессов и превращение полученных строк в код. Само слово require ничего не доказывает; интерес представляет связная последовательность действий.

В исходных рабочих записях этого цикла аналогичную роль играл файл webfonts/fa-brands-regular.woff2. Это описание маскировки JavaScript расширением шрифта, а не доказательство эксплуатации уязвимости в обработчике настоящих шрифтов. Различать эти механизмы необходимо: у них разные точки исполнения и способы проверки.

Возможен и другой вариант: файл хранит закодированные данные, а отдельный скрипт извлекает их и передаёт на исполнение. Тогда перед нами контейнер, и в доказательную цепочку обязательно входит этот обработчик. Обычный шрифт, который приложение просто отображает, тоже может соседствовать с вредоносным кодом. Одного имени файла недостаточно, чтобы приписать ему любую из этих ролей.

А что со «словарём»?

Файл .vscode/spellright.dict выглядит как вспомогательный словарь редактора. Но расширение .dict без указания конкретного формата не даёт такой же универсальной проверки, как заголовок WOFF2.

Здесь полезнее сопоставить предполагаемое назначение и содержимое. Список слов согласуется с ролью словаря. Сложный JavaScript, который устанавливает пакеты и запускает другой файл, — нет. Особенно значимо, если в задаче редактора прямо указан этот путь.

Не следует делать отдельным правилом «любой .dict подозрителен». Иначе проверка будет наказывать нормальную конфигурацию, не объясняя, где именно обнаружена опасность.

Почему похожая история не всегда подтверждает нашу

В одном из исследований Huntress WOFF2 использовался для подмены отображаемых символов: специально подобранные глифы превращали нечитаемую последовательность в правдоподобное имя файла на странице. Это другой приём. Он не доказывает, что в рассматриваемом здесь «шрифте» исполнялся JavaScript, и не связывает два случая с одной группой. [2]

Ссылка на исследование полезна лишь тогда, когда подтверждает именно сформулированный тезис. Общего слова WOFF2 недостаточно.

Ограничиваем просмотр неизвестных данных

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

1
2
3
python3 -I -S -c \
'import sys; f=open(sys.argv[1], "rb"); print(repr(f.read(256))); f.close()' \
webfonts/preview.woff2

Команда читает данные и печатает их представление; она не интерпретирует содержимое как JavaScript. Здесь предполагаются заранее подготовленные доверенные инструменты. Даже статический анализ неизвестных файлов разумно выполнять в отдельной среде с ограниченными ресурсами.

Задача: четыре байта и две гипотезы

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

Даны два начала файлов:

1
2
A: 77 4f 46 32 00 01 00 00
B: 63 6f 6e 73 74 20 78 20

Для файла B дополнительно найдена задача, передающая его Node.js. Для файла A сведений об использовании нет.

Вопросы: какой файл прошёл проверку обязательной сигнатуры? Какой требует проверки назначения? Можно ли по этим данным объявить A безопасным, а B — уже исполненным вредоносом?

Показать разбор

Разбор. A прошёл только проверку первых четырёх байтов. B её не прошёл; начало читается как const x , а задача указывает предполагаемый путь исполнения. B требует дальнейшего анализа кода. О состоявшемся запуске эти данные не сообщают. Безопасность A также не установлена.

Что стоит запомнить

Сильный вывод складывается из нескольких наблюдений: несоответствие формата, содержимое программы и способ её вызова. Проверка расширений или поиск ключевого слова дают зацепку; доказательство появляется, когда удаётся связать её с поведением.

Источники

[1] W3C: WOFF File Format 2.0, раздел 3.2 — заголовок и сигнатура.

[2] Huntress: Gootloader и обфускация имён с помощью WOFF2.

[3] Node.js: CommonJS — обработка расширений файлов.

[4] Node.js: выбор загрузчика точки входа.

[5] Node.js: ES modules — разрешение и загрузка.

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

Создано при помощи Hugo
Тема Stack, дизайн Jimmy