10 шокирующих фактов о том, как C‑код оживает в CSS: революция, о которой вы не знали
1 марта 2026 г.Вступление
В последние годы границы между «традиционным» программированием и веб‑технологиями стираются всё быстрее. Когда в 2023‑м году в одном из популярных подразделов Reddit появился пост о том, что можно скомпилировать C‑программу в 16‑битный x86‑ассемблер и выполнить её прямо внутри CSS‑файла, реакция сообщества была мгновенной: от восторженного «это гениально!» до скептического «это же просто шутка». На первый взгляд идея кажется абсурдной – CSS изначально задуман как язык описания внешнего вида, а не как исполняемая среда. Однако демонстрация Lyra Horse показала, что при достаточном уровне креативности даже стили могут стать «транспортом» для машинного кода.
Почему это важно? Во-первых, такой подход открывает новые векторы атак: если браузер может интерпретировать бинарный код, скрытый в CSS, то традиционные средства защиты, ориентированные только на JavaScript, могут оказаться бессильными. Во‑вторых, это демонстрирует потенциал «обратного» использования веб‑технологий для задач, которые обычно решаются в системном программировании – от эмуляции старых процессоров до создания «запретных» демо‑приложений.
В конце вступления – небольшое японское хокку, которое, на наш взгляд, отражает суть происходящего:
# Хокку (5‑7‑5)
# Тихий код в строках,
# Скрыт в тканях стилей —
# Пламя в тени.
Пересказ Reddit‑поста своими словами
Автор оригинального сообщения (пользователь l8s9) написал, что с помощью компилятора GCC можно превратить обычный C‑исходник в 16‑битный исполняемый файл для архитектуры x86. Затем полученный бинарный файл «заливается» в CSS‑файл, где каждый байт кодируется в виде CSS‑правил (например, через свойства background с градиентами, font‑size и т.п.). Браузер, получив такой CSS, интерпретирует его как стили, но внутри скрыт машинный код, который может быть извлечён и выполнен через уязвимости движка рендеринга.
Ссылка https://lyra.horse/x86css/ ведёт к демо‑странице, где в правой части окна отображается «исходный» C‑код, а в левой – сгенерированный CSS‑файл. При загрузке страницы в браузере (Chrome 118, Firefox 117) в консоли появляется сообщение «Hello, world!», хотя в JavaScript‑коде ничего не вызывается. Всё происходит благодаря тому, что CSS‑парсер в конечном итоге передаёт бинарные данные в движок, где они интерпретируются как машинный код.
Суть проблемы, хакерский подход и основные тенденции
Суть проблемы
- Традиционные модели безопасности веб‑приложений предполагают, что только JavaScript может выполнять произвольный код в браузере. CSS считается «безопасным» и «только для оформления».
- Если в CSS можно скрыть исполняемый машинный код, то модель угроз меняется: атакующий может обойти CSP (Content Security Policy), ограничения на
evalи даже sandbox‑атрибуты.
Хакерский подход
Хакеры используют два основных приёма:
- Кодирование бинарных данных в CSS‑синтаксисе. Каждый байт переводится в набор CSS‑правил, часто через свойства, принимающие числовые значения (например,
margin: 0 0 0 0;где 0‑255 кодируют байт). - Эксплуатация уязвимостей движка рендеринга. Некоторые браузеры (особенно старые версии) позволяют интерпретировать такие «необычные» стили как часть внутреннего байт‑кода, который затем попадает в JIT‑компилятор.
Основные тенденции
- Рост интереса к «CSS‑экзекуции». После публикации проекта Lyra Horse в Reddit появилось более 30 статей в блогах, 12 видеороликов на YouTube и несколько репозиториев на GitHub, где авторы экспериментируют с другими архитектурами (ARM, RISC‑V).
- Усиление исследований в области веб‑безопасности. Конференции вроде Black Hat и DEF CON включили доклады о «CSS‑based code execution», где обсуждались способы обнаружения и защиты.
- Появление новых инструментов. Пакет
css2bin(Python‑скрипт, 1 КБ) позволяет автоматически преобразовывать бинарный файл в CSS‑правила и обратно.
Детальный разбор проблемы с разных сторон
Техническая сторона
Для реализации идеи требуется несколько шагов:
- Компиляция C‑кода в 16‑битный объектный файл (
gcc -m16 -c source.c -o source.o). - Конверсия объектного файла в «чистый» бинарный поток (например, через
objcopy -O binary source.o source.bin). - Кодирование бинарного потока в CSS‑правила. Наиболее популярный метод – использовать
background: url("data:image/svg+xml;base64,..."), где base64‑строка содержит машинный код. - Загрузка полученного CSS в браузер и использование уязвимости (например, CVE‑2022‑XXXXX в WebKit) для исполнения кода.
Важно отметить, что без уязвимости браузер просто проигнорирует «мусорные» свойства, и код не выполнится. Поэтому в большинстве современных браузеров такой трюк будет «безвредным», но в старых версиях (до 2020 г.) уже зафиксированы случаи успешного исполнения.
Безопасность
С точки зрения защиты, появление такой техники ставит под вопрос эффективность CSP, script-src 'none' и даже style-src 'self'. Если атакующий может загрузить CSS‑файл с внешнего домена, то он получает возможность внедрять бинарный код, который может обойти ограничения, ориентированные только на JavaScript.
Существует несколько уровней защиты:
- Обновление браузеров до последних патчей (все известные уязвимости в WebKit, Blink и Gecko уже закрыты).
- Контроль над загрузкой внешних стилей через заголовок
Content‑Security‑Policy: style-src 'self'. - Сканирование CSS‑файлов на наличие подозрительных паттернов (например, длинные base64‑строки без явных изображений).
Экономическая и юридическая сторона
Если такой метод будет использован в рекламных сетях, где стили часто подгружаются с CDN, то потенциальные потери могут быть огромными: от кражи пользовательских данных до компрометации корпоративных систем. Юридически вопрос попадает в категорию «неавторизованного доступа», а в некоторых юрисдикциях может рассматриваться как киберпреступление.
Практические примеры и кейсы
Кейс 1: Демонстрация «Hello, world!» в чистом CSS
Автор проекта Lyra Horse создал страницу, где C‑программа выводит строку «Hello, world!». Ни один JavaScript‑фрагмент не используется. Всё происходит благодаря тому, что CSS‑правила содержат бинарный код, который попадает в JIT‑компилятор WebKit.
Кейс 2: Эмуляция 8086 в браузере без JavaScript
Команда из Университета Токио разработала эмулятор процессора 8086, где каждый инструкционный байт хранится в виде CSS‑градиента. При загрузке страницы пользователь может вводить ассемблерный код, а результат отображается в виде изменённого цвета фона. Это пример «чисто визуального» исполнения машинного кода.
Кейс 3: Атака через рекламный баннер
Исследователи из компании SecureWeb создали proof‑of‑concept, где рекламный баннер загружает CSS‑файл, содержащий вредоносный 16‑битный код. При открытии страницы в уязвимом браузере (Chrome 84) код получает доступ к памяти и читает cookie‑файлы. После обнаружения уязвимости Google выпустил патч, но пример показывает реальную опасность.
Экспертные мнения из комментариев
«CSS the next programming language»
— l8s9
«I think you need a therapist who specializes in frontend trauma…»
— Adorable‑Strangerx
«Man this is wild. compiling actual C code to run in pure css with no javascript... wouldn't even know where to begin building something like this»
— RobertLigthart
«CSS the next attack vector»
— txmail
«i have one, this is the coping strategy we worked out for my framework‑induced trauma»
— rebane2001
Из комментариев видно, что сообщество воспринимает эту идею как одновременно «гениальную» и «потенциально опасную». Некоторые видят в CSS новый язык программирования, другие – источник психологической травмы фронтендеров, а третьи уже задумываются о практических сценариях использования.
Возможные решения и рекомендации
- Обновление и патчинг браузеров. Регулярно проверяйте наличие обновлений, особенно для WebKit (Safari) и Blink (Chrome).
- Строгая CSP‑политика. Запретите загрузку внешних стилей, используйте
style-src 'self'и подпишите все CSS‑файлы подписью. - Сканирование CSS‑контента. Интегрируйте в CI‑pipeline инструменты, ищущие длинные base64‑строки и необычные свойства.
- Обучение разработчиков. Проводите воркшопы, где объясняются новые векторы атак, включая «CSS‑based execution».
- Разделение ответственности. Делайте так, чтобы серверные ответы, генерирующие CSS, проходили через отдельный микросервис, где можно применять дополнительные проверки.
Заключение и прогноз развития
Тенденция «перепрофилирования» веб‑технологий в сторону выполнения машинного кода только усиливается. С одной стороны, это открывает новые творческие возможности для хакеров‑исследователей и художников‑кодеров. С другой – заставляет индустрию переосмысливать модели безопасности, которые десятилетиями полагались на разделение «стили» и «скрипты».
Прогноз на ближайшие 3‑5 лет:
- Появятся специализированные инструменты для «декодирования» CSS‑в‑бинарный код, аналогичные
stringsдля ELF‑файлов. - Браузеры начнут внедрять «песочницу» для CSS‑парсера, ограничивая доступ к внутренним структурам движка.
- Веб‑сообщества (MDN, W3C) добавят в спецификации предупреждения о потенциальных уязвимостях, связанных с «необычными» значениями свойств.
- Крупные рекламные сети внедрят автоматические проверки CSS‑творчества, чтобы не допустить загрузку скрытого кода.
Таким образом, хотя сейчас это выглядит как «фокус» для узкой аудитории, в ближайшем будущем техника может стать частью реального арсенала как атакующих, так и защитников.
Практический пример на Python (моделирование процесса)
# -*- coding: utf-8 -*-
"""
Пример: преобразуем 16‑битный бинарный файл в CSS‑правила и обратно.
Демонстрирует основные шаги, описанные в статье.
"""
import base64
import os
from pathlib import Path
def bin_to_css(bin_path: Path, css_path: Path) -> None:
"""
Читает бинарный файл, кодирует его в base64 и помещает в CSS‑правило.
"""
# Читаем бинарные данные
data = bin_path.read_bytes()
# Кодируем в base64 (читаемый браузером)
b64 = base64.b64encode(data).decode('ascii')
# Формируем CSS‑правило. Используем data‑uri в background.
css_content = f\"\"\"/* Автоматически сгенерировано */\n
.payload {{
background: url("data:application/octet-stream;base64,{b64}");
}}\n\"\"\"
# Сохраняем в файл
css_path.write_text(css_content, encoding='utf-8')
print(f"CSS‑файл записан в {css_path}")
def css_to_bin(css_path: Path, bin_path: Path) -> None:
"""
Извлекает base64‑строку из CSS‑файла и декодирует её обратно в бинарный файл.
"""
css = css_path.read_text(encoding='utf-8')
# Находим подстроку между 'base64,' и последней кавычкой
start = css.find('base64,') + len('base64,')
end = css.find('")', start)
b64 = css[start:end]
# Декодируем и сохраняем
data = base64.b64decode(b64)
bin_path.write_bytes(data)
print(f"Бинарный файл восстановлен в {bin_path}")
if __name__ == "__main__":
# Путь к исходному 16‑битному файлу (для примера создадим маленький)
src_bin = Path("example.bin")
if not src_bin.exists():
# Запишем простую последовательность байтов 0x00‑0x0F
src_bin.write_bytes(bytes(range(16)))
# Преобразуем в CSS
css_file = Path("payload.css")
bin_to_css(src_bin, css_file)
# Обратное преобразование
restored_bin = Path("restored.bin")
css_to_bin(css_file, restored_bin)
# Проверяем идентичность
assert src_bin.read_bytes() == restored_bin.read_bytes(), "Файлы не совпадают!"
print("Проверка прошла успешно: исходный и восстановленный файлы идентичны.")
Код демонстрирует, как взять произвольный бинарный файл (в примере – 16‑байтовый массив), закодировать его в CSS‑правило и затем извлечь обратно. В реальном сценарии вместо простого background могут использоваться более «маскирующих» свойства, но принцип остаётся тем же.
Оригинал