(CSTI) Инъекция шаблонов на стороне клиента

В этом разделе мы рассмотрим уязвимости инъекции клиентских шаблонов (client-side template injection) и то, как вы можете эксплуатировать их для XSS-атак. Эта техника атаки была впервые описана нашей исследовательской командой — подробнее: XSS without HTML: Client-Side Template Injection with AngularJS. Хотя инъекция клиентских шаблонов — проблема общего характера, мы сосредоточимся на примерах из фреймворка AngularJS, поскольку он наиболее распространён. Мы опишем, как создавать эксплойты, которые выходят из песочницы AngularJS (AngularJS sandbox), и как потенциально использовать возможности AngularJS для обхода Content Security Policy (CSP).

Что такое инъекция клиентских шаблонов?

Уязвимости инъекции клиентских шаблонов возникают, когда приложения, использующие клиентский шаблонный фреймворк, динамически встраивают пользовательский ввод в веб-страницы. При рендеринге страницы фреймворк сканирует её на наличие шаблонных выражений и выполняет все найденные. Злоумышленник может воспользоваться этим, передав вредоносное шаблонное выражение, которое запускает межсайтовый скриптинг (XSS).

Что такое песочница AngularJS?

Песочница AngularJS — это механизм, который предотвращает доступ к потенциально опасным объектам, таким как window или document, в шаблонных выражениях AngularJS. Она также блокирует доступ к потенциально опасным свойствам, таким как __proto__. Несмотря на то, что команда AngularJS не рассматривала песочницу как полноценную границу безопасности, более широкое сообщество разработчиков часто считало иначе. Хотя изначально обойти песочницу было сложно, исследователи безопасности обнаружили множество способов сделать это. В результате песочница была удалена из AngularJS в версии 1.6. Тем не менее многие легаси‑приложения всё ещё используют более старые версии AngularJS и могут быть уязвимы.

Как работает песочница AngularJS?

Песочница работает, разбирая выражение, переписывая JavaScript, а затем применяя различные функции для проверки того, содержит ли переписанный код опасные объекты. Например, функция ensureSafeObject() проверяет, ссылается ли объект сам на себя. Это один из способов детектировать объект window. Конструктор Function определяется примерно так же — путём проверки, ссылается ли свойство constructor само на себя. Функция ensureSafeMemberName() проверяет каждый доступ к свойству объекта и, если в нём содержатся опасные свойства, такие как __proto__ или __lookupGetter__, объект будет заблокирован. Функция ensureSafeFunction() предотвращает вызовы call(), apply(), bind() или constructor().

Вы можете увидеть работу песочницы на практике, посетив этот fiddle и установив точку останова на строке 13275 файла angular.js. Переменная fnString содержит ваш переписанный код, поэтому вы можете посмотреть, как AngularJS его трансформирует.

Как работает выход из песочницы AngularJS?

Выход из песочницы (sandbox escape) заключается в том, чтобы заставить песочницу считать вредоносное выражение безобидным. Самый известный обход использует модифицированную функцию charAt() глобально внутри выражения:

'a'.constructor.prototype.charAt=[].join

Когда этот способ был изначально обнаружен, AngularJS не препятствовал такой модификации. Атака переопределяет функцию с помощью метода [].join, из‑за чего charAt() начинает возвращать все переданные ей символы, а не конкретный символ. Из‑за логики функции isIdent() в AngularJS она сравнивает то, что считает одиночным символом, с несколькими символами. Поскольку одиночные символы всегда «меньше» нескольких символов, функция isIdent() всегда возвращает true, как показано в примере:

isIdent = function(ch) {
    return ('a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || '_' === ch || ch === '$');
}
isIdent('x9=9a9l9e9r9t9(919)')

После того как isIdent() введена в заблуждение, вы можете инжектировать вредоносный JavaScript. Например, выражение вроде $eval('x=alert(1)') будет допущено, потому что AngularJS трактует каждый символ как идентификатор. Обратите внимание, что нам нужно использовать функцию $eval() AngularJS, поскольку переопределение charAt() вступит в силу только после выполнения кода в песочнице. Эта техника обходит песочницу и позволяет выполнять произвольный JavaScript. PortSwigger Research многократно и всесторонне ломала песочницу AngularJS.

Построение продвинутого выхода из песочницы AngularJS

Предположим, вы освоили базовый обход песочницы, но сталкиваетесь с сайтами, которые жёстче ограничивают допустимые символы. Например, сайт может запрещать использование двойных или одиночных кавычек. В этой ситуации вам нужно использовать такие функции, как String.fromCharCode(), чтобы генерировать символы. Хотя AngularJS запрещает доступ к конструктору Stringвнутри выражения, это можно обойти, используя свойство constructor у строкового значения. Очевидно, для этого нужна строка, так что, чтобы построить такую атаку, вам придётся найти способ создать строку без использования одинарных или двойных кавычек.

В стандартном обходе песочницы вы бы использовали $eval() для выполнения JavaScript‑пейлоада, но в лабораторной ниже функция $eval() не определена. К счастью, можно использовать фильтр orderBy. Типичный синтаксис фильтра orderBy:

Обратите внимание, что оператор | имеет иное значение, чем в JavaScript. Обычно это побитовое ИЛИ, но в AngularJS он обозначает применение фильтра. В коде выше мы передаём массив [123] слева в фильтр orderBy справа. Двоеточие указывает аргумент, передаваемый фильтру — в данном случае строку. Обычно orderBy сортирует объект, но он также принимает выражение, что позволяет использовать его для передачи пейлоада.

Теперь у вас есть все инструменты, необходимые для следующей лабораторной.

Как работает обход CSP в AngularJS?

Обходы Content Security Policy (CSP) работают схожим образом с обычными обходами песочницы, но обычно включают некоторую HTML‑инъекцию. Когда в AngularJS активирован CSP‑режим, он по‑другому парсит шаблонные выражения и избегает использования конструктора Function. Это означает, что стандартный обход песочницы, описанный выше, больше не сработает.

В зависимости от конкретной политики CSP будут блокироваться JavaScript‑события. Однако AngularJS определяет собственные события, которые можно использовать вместо них. Внутри события AngularJS определяет специальный объект $event, который просто ссылается на объект браузерного события. Вы можете использовать этот объект, чтобы выполнить обход CSP. В Chrome у объекта $event/event есть специальное свойство path. Это свойство содержит массив объектов, «приведших» к выполнению события. Последний элемент в этом массиве — всегда объект window, который мы можем использовать для выхода из песочницы. Передав этот массив в фильтр orderBy, мы можем проитерироваться по массиву и использовать последний элемент (объект window), чтобы выполнить глобальную функцию, такую как alert().

Пример:

Обратите внимание на использование функции from(), которая позволяет конвертировать объект в массив и вызвать заданную функцию (указанную вторым аргументом) для каждого элемента массива. В данном случае мы вызываем функцию alert(). Мы не можем вызвать функцию напрямую, потому что песочница AngularJS распарсит код и обнаружит, что используется объект window для вызова функции. Использование from() фактически «прячет» windowот песочницы, позволяя инжектировать вредоносный код. PortSwigger Research создала обход CSP с использованием AngularJS в 56 символов, применяя эту технику.

Обход CSP с выходом из песочницы AngularJS

В следующей лабораторной работе есть ограничение на длину, поэтому вектор выше не подойдёт. Чтобы эксплуатировать лабораторную, вам нужно подумать о различных способах «спрятать» объект window от песочницы AngularJS. Один из способов — использовать функцию array.map() следующим образом:

map() принимает функцию в качестве аргумента и вызывает её для каждого элемента массива. Это обходит песочницу, поскольку ссылка на функцию alert() используется без явного упоминания window. Чтобы решить лабораторную, попробуйте различные способы выполнить alert() без срабатывания обнаружения window в AngularJS.

Как предотвратить уязвимости инъекции клиентских шаблонов

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

Обратите внимание, что HTML‑кодирование недостаточно для предотвращения атак инъекции клиентских шаблонов, потому что фреймворки выполняют HTML‑декодирование соответствующего контента перед поиском и исполнением шаблонных выражений.

Last updated