[Перевод] Проблемы санации SVG

Страницы:  1

Ответить
 

Professor Seleznov


Рендерер Scratch имеет долгую историю связанных с SVG уязвимостей. Их источником становится то, что Scratch парсит сгенерированный пользователем (то есть контролируемый нападающими) контент в элемент  и добавляет его в основной документ для выполнения различных операций (например, для измерения ограничивающего прямоугольника SVG более надёжным образом, чем viewbox или width/height).
Даже если SVG остаётся в основном документе очень недолго, это небезопасная по своей природе операция. Для обеспечения защиты Scratch реализовывал всё более сложную инфраструктуру парсинга SVG и находящейся внутри разметки, чтобы устранить опасные части.
Я считаю, что подход Scratch к санации SVG обречён на провал. Чтобы объяснить это, нам нужно совершить путешествие по истории санации SVG в Scratch и посмотреть, насколько хорошо он с этим справлялся.
2019 год: XSS при помощи тэга
В 2019 году, спустя несколько месяцев после выпуска Scratch 3, разработчики Scratch обнаружили, что SVG могут содержать тэги  , исполнение которых при загрузке SVG обеспечивает Scratch. Такая атака называется XSS.
В Scratch атака XSS позволяет нападающему выполнять действия от лица того, кто загрузит его проект. Например, нападающий может публиковать комментарии, удалять проекты или пытаться захватить аккаунт жертвы иными способами. В Scratch Desktop XSS переходит в исполнение произвольного кода, потому что Scratch Desktop включает опасную фичу интеграции Node.js Electron. (В TurboWarp Desktop эта фича не включена с v0.2.0 от марта 2021 года).
Пример из набора тестов Scratch:
  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">



Проблема была устранена при помощи регулярного выражения, удаляющего тэги script.
Что ж, теперь благодаря этому изменению SVG наверняка полностью безопасны и больше не потребуют исправлений.
2020 год: XSS из-за ошибок в предыдущем исправлении (CVE-2020-7750)
В 2020 году apple502j обнаружил, что XSS всё ещё возможен. Оказалось, что предыдущее исправление абсолютно поломанное и его можно обойти, написав  заглавными буквами, потому что регулярное выражение учитывало регистр; было и множество других способов обхода. Даже если бы регулярное выражение реализовали корректно, это всё равно бы не сработало, потому что существуют и другие способы встраивания JavaScript в SVG. Например, можно использовать встроенный обработчик событий:


xmlns="http://www.w3.org/1999/xhtml"
src="data:any invalid URL"
onerror="alert(1)"
/>

Проблема была устранена при помощи DOMPurify, удаляющего скрипты из SVG перед тем, как scratch-svg-renderer добавляет их в документ.
Что ж, теперь благодаря этому изменению SVG наверняка полностью безопасны и больше не потребуют исправлений.
2022 год: HTTP-утечка через href
В 2022 году обнаружилось, что при помощи свойства href элемента  нападающий может создать SVG, который при загрузке вызывает внешний запрос. Оказалось, что хоть DOMPurify и удаляет исполняемый код, он не защищает от HTTP-утечек, потому что «существует слишком много способов её реализации и наши тесты показали, что неё нельзя защититься надёжным образом».
Для Scratch HTTP-утечка означает, что пользователь Scratch может записывать IP-адрес любого, кто загружает его проект, потенциально раскрывая такую информацию, как местоположение или школьный округ. Жертве не нужно нажимать ни на какие ссылки; логгинг IP-адреса происходит просто при загрузке проекта. Похоже, разработчики Scratch посчитали это багом безопасности, и я согласен с ними.
Пример:


Проблема была решена добавлением хуков DOMPurify для удаления свойств href из всех элементов, если URL ссылается на удалённый сайт.
Что ж, теперь благодаря этому изменению SVG наверняка полностью безопасны и больше не потребуют исправлений.
2023 год: HTTP-утечка через @import CSS
В 2023 году обнаружилось, что при помощи правила @import CSS внутри элемента  нападающий может создать проект, создающий внешние запросы при загрузке проекта. Пример:


Проблема была решена интеграцией написанного на JavaScript парсера CSS, который удаляет опасные части CSS. Он парсит все содержащиеся в SVG таблицы стилей, удаляет все правила @import, и в случае внесения изменений преобразует CSS обратно в строку.
Что ж, теперь благодаря этому изменению SVG наверняка полностью безопасны и больше не потребуют исправлений.
2024 год: XSS через Paper.js
В 2024 году я обнаружил XSS в Paper.js — библиотеке, которую Scratch использует в редакторе костюмов. Оказалось, что хотя Scratch санировал SVG перед работой с ними в scratch-svg-renderer, Paper.js передавались несанированные SVG. В основном эта уязвимость представляла такую же угрозу, как XSS scratch-svg-renderer, обнаруженное в 2020 году, но возникала при использовании редактора костюмов, а не при открытии проекта. Пример:


xmlns="http://www.w3.org/1999/xhtml"
src="data:any invalid URL"
onerror="alert(1)"
/>

Проблема была частично решена за очень долгий период времени благодаря расширению кода санации SVG: теперь он запускался при загрузке SVG, а не только при его обработке в scratch-svg-renderer. С этого момента Paper.js получает только уже санированные SVG.
Я написал «частично решена», потому что не знаю, выполняется ли вообще санация для скачиваемых сервером SVG. В поддержке Scratch мне сказали, что у них «есть меры защиты против того, что обрабатывается на стороне сервера», из-за чего такая санация была бы избыточной. При разработке proof-of-concept я ни разу не видел признаков такой защиты, но, возможно, она реальна.
Что ж, теперь благодаря этому изменению SVG наверняка полностью безопасны и больше не потребуют исправлений.
2025 год: HTTP-утечка через url() CSS
В 2025 году выяснилось, что при использовании url() внутри некоторых правил CSS нападающий может создать SVG, при загрузке создающий внешний запрос. Примеры:


https://example.com/ping)" />



Проблема была решена существенным расширением кода санации SVG: теперь он искал любые вхождения url() и удалял все стили или атрибуты, ссылающиеся на внешние URL.
Что ж, теперь благодаря этому изменению SVG наверняка полностью безопасны и больше не потребуют исправлений.
2026 год: HTTP-утечка через множество багов в старом коде
В 2026 году обнаружилось, что при использовании url() внутри некоторых правил CSS нападающий по-прежнему может создать SVG, при загрузке совершающий внешний запрос. Оказалось, что эта HTTP-утечка стала возможной благодаря как минимум трём уникальным багам:
  • Не учтено то, что CSS позволяет записывать url(...) при помощи управляющих последовательностей.
  • Не обрабатывалась ситуация, при которой атрибут style содержал несколько url(...), где первый безопасен, а второй нет.
  • Не обрабатывался url(), определённый в переменной CSS, на который ссылаются через var(—name).
Пример:

https://example.com/ping)" />
https://example.com/ping)" />


Проблема была решена добавлением большого объёма дополнительной сложности вокруг кода, который и так уже был слишком сложным.
Что ж, теперь благодаря этому изменению SVG наверняка полностью безопасны и больше не потребуют исправлений.
2026 год: полная смена стилей страницы при помощи долгих переходов
В 2026 году выяснилось, что хитро использовав очень долгие переходы и заставив браузер изменить стили всех элементов, нападающий может применять ко всей странице Scratch произвольные стили, сохраняющиеся до обновления страницы. Чаще всего эта уязвимость использовалась для развлечений, но её можно применять и для более зловещих действий:
  • Прятать кнопку «Пожаловаться».
  • Сделать кнопки лайков/добавления в избранное размером со всю страницу, чтобы пользователи вынуждены были их нажимать.
  • Отображать текст, сообщающий пользователю, что ему нужно открыть веб-сайт в новой вкладке, чтобы «верифицировать» свой аккаунт (на какой-нибудь фишинговой странице). Пользователи, скорее всего, поверят инструкциям, потому что сообщение поступает от реального scratch.mit.edu.
Пример проекта (не мой): https://scratch.mit.edu/projects/1299571218/
Рано или поздно это, наверно, исправят, но пока пользователь будет видеть такое:
pic
В этом проекте используются два SVG. Первый из них — это «триггер»:



Trigger


Второй содержит стили для отображения:



Styles


Не буду делать вид, что понимаю происходящее здесь, и почему это работает недетерминированно, но в целом представляю это так:
  • Триггерный SVG применяет transform и filter к каждому элементу документа, чтобы вынудить браузер сразу же заново вычислить все стили, применив стили из другого SVG.
  • Триггерный SVG применяет очень долгий transition, чтобы после удаления другого SVG стили сохранялись в течение всего «перехода».
Эта проблема не решена.
Что ж, если её решат, то SVG наверняка будут полностью безопасны и больше не потребуют исправлений.
2026 год: HTTP-утечка через image-set()
Я сообщал о ней разработчикам Scratch в 2025 году. Они её не устранили, поэтому я раскрываю её в этой статье. Все разумные сроки раскрытия прошли уже полгода назад.
Вместо url() нападающий может использовать image-set(), чтобы создать SVG, при загрузке выполняющий внешний запрос. Примеры:









Эта проблема не решена.
Что ж, если её решат, то SVG наверняка будут полностью безопасны и больше не потребуют исправлений.
20XX год: HTTP-утечка через новые фичи CSS
Об этом я тоже сообщал разработчикам Scratch в 2025 году. На самом деле, этот баг пока не работает, но начнёт работать в будущем, если браузеры реализуют все CSS Units Level 4 или CSS Images Level 4. Сегодня Ladybird — единственный реализующий их браузер, но рано или поздно их могут реализовать и самые популярные браузеры.
Вместо url() нападающий может использовать src() или image(), чтобы создать SVG, при загрузке совершающий внешний запрос. Примеры:













Эта проблема не решена.
Что ж, если её решат, то SVG наверняка будут полностью безопасны и больше не потребуют исправлений.
Такая система неустойчива
Засовывание в процесс санации всё больше сложности — это обречённое на провал решение. Мы уже углубились на пять крупных доработок, но до сих пор существуют известные дыры. Люди активно делятся проектами на веб-сайте Scratch, обходя санацию SVG. А в момент, когда в браузерах решат реализовать последние спецификации CSS, откроется ещё больше дыр.
Кроме того, не у всех этих проблем есть чёткие решения. В случае уязвимости с полной стилизацией страницы оба SVG выглядят совершенно невинно: в них нет JavaScript и ссылок на внешние ресурсы. Вероятно, устранить проблему можно было бы, удалив стили transition, потому что в Scratch переходы всё равно никогда не выполняются, но уверены ли мы, что этого достаточно? Вспомним ли мы, что нужно удалить все версии transition с префиксами поставщика? А что насчёт стилей animation?
Вот некоторые другие примеры, которые могут обеспечить возможность обхода защиты в будущем:
  • css-tree (библиотека, используемая Scratch для парсинга CSS) и реальные парсеры CSS браузеров могут совпадать не полностью. В этом случае css-tree может спарсить CSS так, что всё выглядит правильно, а значит, ничего не удалится, но реальный парсер браузера потом распознает внешний контент.
  • Продвинутые новые фичи CSS наподобие @property или native nesting, которые версии css-tree, возможно, не смогут осмысленно парсить без постоянных обновлений.
  • Браузеры всегда могут добавить новые функции, способные ссылаться на внешний контент, как это произошло с image-set() и с тем, что подразумевает спецификация в src() и image(). Как не отставать от постоянных изменений в этих спецификациях и проверять, не ссылается ли каждая новая функция на внешний контент?
Альтернатива
TurboWarp (форк Scratch, над которым работаю я) не затронули HTTP-утечки 2026 года и проблема полной смены стилей страницы. И не потому, что я нашёл все хитрые способы, которыми SVG могут наносить вред: на самом деле, я полностью удалил код санации CSS, чтобы упакованные проекты стали на 400 КБ меньше.
Я реализовал альтернативное решение для сэндбоксинга SVG внутри iframe. Сначала мы создаём iframe со свойством sandbox, равным allow-same-origin. Это не позволяет исполнять скрипты снаружи iframe, но позволяет при этом взаимодействовать с контентом внутри.
Во-вторых, мы создаём iframe со следующим HTML:







Встроенная Content-Security-Policy настроена так, чтобы блокировать все скрипты и позволять загружать только безопасные ресурсы из URL безопасных данных. Также мы по-прежнему используем DOMPurify для устранения из SVG очевидно зловредных вещей. Затем мы помещаем iframe в какую-нибудь часть документа за пределами экрана, чтобы необходимый Scratch API измерений продолжал работать.
Такое решение обеспечивает нам очень удобные свойства:
  • Браузер использует готовый код, чтобы выполнять за нас самую сложную работу.
    TurboWarp не обязан знать о всех способах, которыми SVG может выполнять запрос. Их уже знает браузер, и он будет проверять их для всех новых добавляемых API.
    Реальные реализации CSP неидеальны и содержат дыры. Однако эти дыры обычно оказываются странными пограничными случаями, требующими от нападающего обеспечить исполнение JavaScript. Такие уязвимости считаются проблемами безопасности браузеров, поэтому за них платят баг-баунти.
  • SVG не может влиять на основной документ.
    Возьмём для примера смену стилей всей страницы. Так как SVG заключён в iframe, он может изменить стили только этого iframe. Стили iframe ни на что не влияют, так что нас это устраивает.
Наш код можно найти здесь: Вероятно, можно делать что-то интересное с shadow DOM или другими веб-API, но нас вполне устраивает решение с iframe.
Ниже я расскажу о проблемах, о которых узнал после публикации статьи.
12 апреля 2026 года: Claude нашёл HTTP-утечку через расслабленный синтаксис вложенности CSS
После публикации статьи мне стало интересно, насколько хорошо современные языковые модели умеют находить подобные баги. Я попросил Claude Opus 4.6 клонировать репозиторий scratch-editor, изучить последние изменения в рендерере SVG и поискать в них дыры. Результаты оказались интересными:
  • Claude самостоятельно обнаружил, что image-set(...) не санируется и может вызывать HTTP-утечки.
  • Claude обнаружил новую проблему, не описанную в этом посте.
Баг связан с вложенностью CSS, которая может проявляться в двух формах. Вложенный стиль может добавлять к селектору префикс & или не добавлять префикс (последнее известно, как «расслабленный» синтаксис). Современные браузеры интерпретируют оба показанных ниже примера одинаково.
g {
& rect {
background-image: url(https://example.com/ping);
}
}
g {
rect {
background-image: url(https://example.com/ping);
}
}
css-tree способен парсить версию с префиксом & в осмысленное дерево синтаксиса, которое способен санировать Scratch. Однако оказалось, что css-tree не знает, как парсить расслабленную версию. Весь блок div { ... } парсится, как узел «сырого текста», который код Scratch не санирует. Вот полный пример SVG:



Ранее в этом посте я говорил, что css-tree и реальные парсеры CSS браузеров могут совпадать не полностью. Вот реальный пример бага, позволяющего обойти санацию CSS. Стоит отметить, что сейчас у css-tree есть 48 открытых issue и множество других неизвестных проблем. Я считаю, что надежда на то, что css-tree будет идеальным парсером — тупиковый путь, который приведёт к ещё большему количеству уязвимостей. Песочница SVG в TurboWarp полностью устранила этот баг, хотя я о нём даже не знал.
Эта проблема не устранена.  Issue css-tree по этому багу открыта с декабря 2023 года.
Что ж, если её решат, то SVG наверняка будут полностью безопасны и больше не потребуют исправлений.-Источник
 
Loading...
Error