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‑атрибуты.

Хакерский подход

Хакеры используют два основных приёма:

  1. Кодирование бинарных данных в CSS‑синтаксисе. Каждый байт переводится в набор CSS‑правил, часто через свойства, принимающие числовые значения (например, margin: 0 0 0 0; где 0‑255 кодируют байт).
  2. Эксплуатация уязвимостей движка рендеринга. Некоторые браузеры (особенно старые версии) позволяют интерпретировать такие «необычные» стили как часть внутреннего байт‑кода, который затем попадает в JIT‑компилятор.

Основные тенденции

  • Рост интереса к «CSS‑экзекуции». После публикации проекта Lyra Horse в Reddit появилось более 30 статей в блогах, 12 видеороликов на YouTube и несколько репозиториев на GitHub, где авторы экспериментируют с другими архитектурами (ARM, RISC‑V).
  • Усиление исследований в области веб‑безопасности. Конференции вроде Black Hat и DEF CON включили доклады о «CSS‑based code execution», где обсуждались способы обнаружения и защиты.
  • Появление новых инструментов. Пакет css2bin (Python‑скрипт, 1 КБ) позволяет автоматически преобразовывать бинарный файл в CSS‑правила и обратно.

Детальный разбор проблемы с разных сторон

Техническая сторона

Для реализации идеи требуется несколько шагов:

  1. Компиляция C‑кода в 16‑битный объектный файл (gcc -m16 -c source.c -o source.o).
  2. Конверсия объектного файла в «чистый» бинарный поток (например, через objcopy -O binary source.o source.bin).
  3. Кодирование бинарного потока в CSS‑правила. Наиболее популярный метод – использовать background: url("data:image/svg+xml;base64,..."), где base64‑строка содержит машинный код.
  4. Загрузка полученного 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 новый язык программирования, другие – источник психологической травмы фронтендеров, а третьи уже задумываются о практических сценариях использования.

Возможные решения и рекомендации

  1. Обновление и патчинг браузеров. Регулярно проверяйте наличие обновлений, особенно для WebKit (Safari) и Blink (Chrome).
  2. Строгая CSP‑политика. Запретите загрузку внешних стилей, используйте style-src 'self' и подпишите все CSS‑файлы подписью.
  3. Сканирование CSS‑контента. Интегрируйте в CI‑pipeline инструменты, ищущие длинные base64‑строки и необычные свойства.
  4. Обучение разработчиков. Проводите воркшопы, где объясняются новые векторы атак, включая «CSS‑based execution».
  5. Разделение ответственности. Делайте так, чтобы серверные ответы, генерирующие 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 могут использоваться более «маскирующих» свойства, но принцип остаётся тем же.


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE