TGViewer
Искусство. Код... ИИ? Искусство. Код... ИИ? @art_code_ai · 706 subscribers
Post #104 1.29K
🐞 CVE-2026-27136 — должен ли санитайзер следовать спекам?

Что тут у нас сегодня? Снова мой любимый класс уязвимостей: атаки на парсер! 🦄 Встречаем: CVE-2026-27136 — уязвимость класса мутационной XSS (mXSS) в HTML-парсере пакета `golang.org/x/net/html`(версии до v0.55.0).

Суть уязвимости проста: токенизатор не отбрасывал дублирующиеся атрибуты при парсинге HTML вопреки спецификации WHATWG (раздел «13.2.5.33 Attribute name state»), которая требует учитывать только первое вхождение.

⌨️ Сценарий атаки

Допустим, в приложении есть вот такой код:
func sanitizeHTML(input string) string {
doc, _ := html.Parse(strings.NewReader(input))
// Обходим дерево, удаляем опасные атрибуты
removeDangerousAttrs(doc) // удаляет onerror, onclick и т.д.
var buf strings.Builder
html.Render(&buf, doc)
return buf.String()
}


Злоумышленник отправляет следующий HTML:
<img src=x onerror= onerror=alert(1)>


И дальше происходит следующее:

1️⃣ Парсинг: токенизатор сохраняет оба атрибута onerror:

• onerror=""
• onerror="alert(1)"

2️⃣ Санитизация: функция removeDangerousAttrs находит первый onerror и удаляет его. Но дубликат onerror="alert(1)" остаётся в дереве — санитайзер, ожидающий, что дерево нормализовано и соответствует спеке, обрабатывает только первое вхождение.

3️⃣ Рендеринг: html.Render() сериализует дерево и выводит:
<img src="x" onerror="alert(1)"/>


🩹 Уязвимость и исправление

Уязвимость находилась в файле html/token.go, функция readTag() и заключалась в том, что проверка if saveAttr && key_non_empty выполнялась без проверки на уникальность имени атрибута. Любой дублирующийся атрибут попадал в срез z.attr.

Исправление уязвимости можно посмотреть в коммите a452f3c. Ключевое изменение — в логике сохранения атрибута:

-    // Было: сохраняем любой непустой атрибут
- if saveAttr && z.pendingAttr[0].start != z.pendingAttr[0].end {
- z.attr = append(z.attr, z.pendingAttr)
- }

+ // Стало: извлекаем ключ, проверяем на дубликат
+ key := strings.ToLower(string(z.buf[z.pendingAttr[0].start:z.pendingAttr[0].end]))
+ if saveAttr && z.pendingAttr[0].start != z.pendingAttr[0].end && !z.attrNames[key] {
+ z.attr = append(z.attr, z.pendingAttr)
+ z.attrNames[key] = true // регистрируем имя
+ }


😻 Пара мыслей на тему

1️⃣ Должен ли санитизатор, работающий с нормализованными данными, имеющими спецификацию, допускать, что они не будут ей соответствовать? Разработчики скажут, конечно нет, ведь нормализация — это жесткий контракт, и какой в нём смысл, если на него не полагаться? Аппсеки возразят, что тогда подобные «правильные» санитизаторы и впредь будут отваливаться пачками при подобных уязвимостях, и, в целом, ничего не мешает, предусмотреть в них подобные ситуации.

Я придерживаюсь мнения, что если контракт есть, то санитизатор должен на него полагаться в части своей основной функциональности. Но только в том случае, если выполнение контракта подтверждено, т.е. ранее была осуществлена валидация соответствия данных спецификации. В противном случае, санитизатор должен брать на себя ответственность за эту валидацию и возвращать ошибку (с записью в лог), если валидация провалилась.

2️⃣ Те, кто внимательно изучил код патча, могли заметить strings.ToLower при формирования ключа. Этим разработчики, с одной стороны — выполнили также требования того же раздела спеки о регистронезависимости имен атрибутов, а с другой — избежали байпаса их патча кейсами типа:
<img src="x" onError="alert(1)" onerror="alert(1)"/>


3️⃣ Ну, а те, кто изучил код патча СОВСЕМ внимательно, могли задаться вопросом, а не вносит ли он уязвимость к DoS через колизию хэшей во вновь добавленной мапе attrNames?

И да, и нет. Нет — потому что в Go мапы используют рандомизацию хэшей (пруф) и этой атаке не подвержены. Да — потому что в другом месте net/html, имена атрибутов различных тегов сравнивались по алгоритму со сложностью O(k × m²) от числа тегов и атрибутов. Эта проблема была исправлена в том же релизе, в 08be507

Всё! 🤗

#уязвимости #аппсек
  • ❤ 2
  • 🔥 2
  • 💯 2
More from @art_code_ai
  1. Sep 29, 2026Объяснили с Раддой Юрьевой на Хабре, почему смешивать формальные и ИИшные подходы в задача…
  2. Sep 27, 2026🔗 Старый добрый аппсек, часть 2 Продолжаем вспоминать ключевые вехи аппсека и смежных обл…
  3. Sep 26, 2026Post #155
  4. Sep 24, 2026🔗 Старый добрый аппсек, часть 1 ...совершенно незаслуженно задвинутый на второй план ИИ-х…
  5. Sep 23, 2026Post #153
  6. Sep 21, 2026👨‍💻 Продуктивные выходные Как-то незаметно (и неожиданно) для себя с удовольствием провё…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →