Client-Side Fundamental
  • Добро пожаловать
  • Глава 1 - Начало работы с XSS
    • Браузерная модель безопасности
    • Знакомимся с уязвимостью XSS
    • Более глубокое понимание XSS
    • Опасный псевдопротокол javascript
  • Глава 2 - Защита и Обход для XSS
    • Первая линия обороны от XSS - Sanitization
    • Вторая линия обороны от XSS - CSP (Content Security Policy)
    • Третья линия обороны против XSS - сокращение области воздействия
    • Последние методы защиты от XSS - Trusted Types и встроенный Sanitizer API
    • Обход защитных мер - Обычные способы обхода CSP
    • Обход защитных мер - Mutation XSS
    • Самая опасная XSS - Universal XSS
  • Глава 3 - Атаки без JavaScript
    • Кто сказал, что для атаки обязательно выполнять JavaScript?
    • Prototype Pollution - Эксплуатация цепочки прототипов
    • Может ли HTML влиять на JavaScript - Введение в DOM clobbering
    • Template Injection in Frontend - CSTI
    • CSS Injection - Атака с использованием только CSS (Часть 1)
    • CSS Injection - Атака с использованием только CSS (Часть 2)
    • Можно ли атаковать, используя только HTML
  • Глава 4 - Межсайтовые атаки
    • Same-origin Policy и Same-Site
    • Введение в Cross-Origin Resource Sharing (CORS)
    • Проблемы Cross-Origin безопасности
    • Cross-Site Request Forgery (CSRF)
    • Спаситель от CSRF - Same-site cookie
    • От same-site до главного site
    • Интересная и практичная Cookie Bomb
  • Глава 5 - Другие интересные темы
    • То, что вы видите, это не то, что вы получаете - Clickjacking
    • Эксплуатация MIME Sniffing
    • Атаки на цепочку поставок во фронтенде - Attacking Downstream from Upstream
    • Атаки на веб-фронтенд в Web3
    • Самая интересная атака на побочные каналы фронтенда - XSLeaks (Часть 1)
    • Самая интересная атака на побочные каналы фронтенда - XSLeaks (Часть 2)
Powered by GitBook
On this page
  • Server Side Template Injection
  • Примеры SSTI в реальности
  • Client Side Template Injection
  • Кстати, знаете ли вы разницу между AngularJS и Angular?
  • Практический пример CSTI
  • AngularJS и CSP Bypass
  • Заключение
  1. Глава 3 - Атаки без JavaScript

Template Injection in Frontend - CSTI

CSTI, то есть Client Side Template Injection, относится к внедрению шаблона во фронтенде. Поскольку есть версия для фронтенда, существует и соответствующая версия для бэкенда, называемая SSTI, что означает "Внедрение шаблона на стороне сервера".

Прежде чем рассматривать версию для фронтенда, давайте посмотрим на версию для бэкенда.

Server Side Template Injection

При написании бэкенд-кода, который должен выводить HTML, вы можете выбрать его прямой вывод, как в чистом PHP:

<?php  echo '<h1>hello</h1>';?>

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

Например, на странице некоего блога часть шаблона выглядит так:

<article class="article content gallery" itemscope itemprop="blogPost">
    <h1 class="article-title is-size-3 is-size-4-mobile" itemprop="name">
        <%= post.title %>
    </h1>
    <div class="article-meta columns is-variable is-1 is-multiline is-mobile is-size-7-mobile">
        <span class="column is-narrow">
            <time datetime="<%= date_xml(post.date) %>" itemprop="datePublished">
                <%= format_date_full(post.date) %>
            </time>
        </span>
        <% if (post.categories && post.categories.length) { %>
        <span class="column is-narrow article-category">
            <i class="far fa-folder"></i>
            <%- (post._categories || post.categories).map(category =>
                `<a class="article-category-link" href="${url_for(category.path)}">${category.name}</a>`
            ).join('<span>></span>') %>
        </span>
        <% } %>
    </div>
    <div class="article-entry is-size-6-mobile" itemprop="articleBody">
        <%- post.content %>
    </div>
</article>

При рендеринге мне нужно просто передать объект post и объединить его с шаблоном, чтобы создать полноценную страницу статьи.

Внедрение шаблона не означает, что "злоумышленники могут манипулировать такими данными, как post," а скорее "злоумышленники могут манипулировать самим шаблоном".

Например, предположим, что у нас есть служба маркетинговых электронных писем. Обычно компании загружают в нее данные пользователей и устанавливают собственные шаблоны, такие как:

Hi, {{name}}Do you find our product fits for your needs?If not, feel free to schedule a brief 10-minute online meeting with me at your convenience.You can make a reservation <a href="{{link}}q={{email}}">here</a>

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

from jinja2 import Template

data = {
    "name": "Peter",
    "link": "https://example.com",
    "email": "test@example.com"
}

template_str = """Hi, {{ name }}
Do you find our product fits for your needs?
If not, feel free to schedule a brief 10-minute online meeting with me at your convenience.
You can make a reservation <a href="{{ link }}?q={{ email }}">here</a> wr3d"""

template = Template(template_str)
rendered_template = template.render(
    name=data['name'],
    link=data['link'],
    email=data['email']
)

print(rendered_template)

Финальный вывод:

Hi, PeterDo you find our product fits for your needs?If not, feel free to schedule a brief 10-minute online meeting with me at your convenience.You can make a reservation <a href="https://example.com?q=test@example.com">here</a>

Выглядит нормально, но что, если мы модифицируем шаблон? Например так:

from jinja2 import Template

data = {
    "name": "Peter",
    "link": "https://example.com",
    "email": "test@example.com"
}

template_str = """Output: {{ self.__init__.__globals__.__builtins__.__import__('os').popen('uname').read() }}"""

template = Template(template_str)
rendered_template = template.render(
    name=data['name'],
    link=data['link'],
    email=data['email']
)

print(rendered_template)

Вывод станет: Output: Darwin, а Darwin - это результат выполнения команды uname.

Простыми словами, вы можете думать о содержимом в {{}} как о коде, который движок шаблонов выполнит за вас.

Хотя мы обычно писали просто {{name}}, на самом деле можно выполнить больше операций, например, {{ name + email }}. В вышеуказанном примере он начинается с self и использует Python, чтобы прочитать __import__, позволяя импортировать другие модули и достигать выполнения команды.

Уязвимости, которые позволяют злоумышленникам контролировать шаблон, называются внедрением шаблона. Когда это происходит на бэкенде, это называется SSTI, а когда это происходит на фронтенде, это называется CSTI.

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

Примеры SSTI в реальности

Первый пример - это уязвимость, обнаруженная Orange в Uber в 2016 году. Однажды Orange заметил 2 в письме, отправленном Uber, и вспомнил, что он ввел {{ 1+1 }} в поле имени. Это обычная техника при поиске уязвимостей SSTI, когда в поля для ввода вводятся множество полезных нагрузок, чтобы проверить, есть ли какие-либо проблемы SSTI на основе результатов.

Затем они использовали упомянутую выше технику, чтобы найти, какие переменные можно использовать и объединить. Так как Uber также использует Jinja2, окончательный полезная нагрузка очень похожа на то, что мы только что написали, и они успешно достигли RCE с помощью SSTI.

Второй пример - это внедрение шаблонов Handlebars SSTI в Shopify, о котором сообщил Mahmoud Gamal в 2019 году.

Бэкенд продавцов Shopify имеет функцию, которая позволяет продавцам настраивать письма, отправляемые пользователям (аналогично приведенному ранее примеру). Они могут использовать синтаксис типа {{ order.number }} для настройки содержимого. Бэкенд использует Node.js с движком шаблонов Handlebars.

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

В конце концов, наличие SSTI - это одно, а вот не каждый шаблонный движок можно эксплуатировать для RCE.

Окончательная полезная нагрузка, которую они придумали, была очень длинной:

{{#with this as |obj|}}
    {{#with (obj.constructor.keys "1") as |arr|}}
        {{#with obj.constructor.name as |str|}}
            {{#blockHelperMissing str.toString}}
                {{#with (arr.constructor (str.toString.bind "return JSON.stringify(process.env);"))}}
                    {{#with (obj.constructor.getOwnPropertyDescriptor this 0)}}
                        {{#with (obj.constructor.defineProperty obj.constructor.prototype "toString" this)}}
                            {{#with (obj.constructor.constructor "test")}}
                                {{this}}
                            {{/with}}
                        {{/with}}
                    {{/with}}
                {{/with}}
            {{/blockHelperMissing}}
        {{/with}}
    {{/with}}
{{/with}}

Client Side Template Injection

Понимание CSTI становится проще после понимания SSTI, поскольку принципы схожи, единственное отличие в том, что этот шаблон - это шаблон на стороне клиента.

Подождите, есть ли шаблоны на фронтенде? Безусловно!

// import required packages
import 'zone.js';
import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';

// describe component
@Component({
  selector: 'add-one-button', // component name used in markup
  standalone: true, // component is self-contained
  template: // the component's markup
    `
      <button (click)="count = count + 1">Add one</button> {{ count }}
    `,
})
// export component
export class AddOneButtonComponent {
  count = 0;
}

bootstrapApplication(AddOneButtonComponent);

Ключевое здесь - параметр под названием template. Если вы измените {{ count }} на {{ constructor.constructor('alert(1)')() }}, вы увидите всплывающее окно с предупреждением.

Необходимо использовать constructor.constructor('alert(1)')() потому что шаблон не может напрямую получить доступ к window, поэтому создается новая функция через конструктор функции.

Шаблоны следует рассматривать как исполняемый код, и пользователю никогда не следует разрешать управлять шаблонами.

Кстати, знаете ли вы разницу между AngularJS и Angular?

Когда он был впервые выпущен в 2010 году, он назывался AngularJS, и номера версий были 0.x.x или 1.x.x. Но после версии 2 его переименовали в Angular, схожее использование, но полностью переписанный дизайн. Мы в основном будем ссылаться на старую версию, AngularJS, потому что она имеет больше проблем из-за своего возраста, и это библиотека, подходящая для помощи в атаках.

Когда AngularJS был впервые выпущен, выполнение произвольного кода также было возможно с использованием {{ constructor.constructor('alert(1)')() }}. Однако, начиная с версии 1.2.0, был добавлен механизм песочницы, чтобы предотвратить доступ к window любым возможным способом. Но когда дело доходит до атаки и защиты, исследователи безопасности не отстают, и они нашли способы обхода песочницы.

В версиях AngularJS 1.x этот процесс был более удобным и простым, требуя только элемент с ng-app:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
</head>
<body>
  <div ng-app>
    {{ 'hello world'.toUpperCase() }}
  </div>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.3/angular.min.js"></script>
</body>
</html>

Идеально было бы, если бы весь фронтенд контролировался AngularJS, с общением на бэкенд через API, а бэкенд не должен был бы заниматься рендерингом представления, однако в то время концепция SPA еще не была популярна, и многие веб-сайты все еще имели бэкенд, отвечающий за рендеринг представления. Поэтому было весьма вероятно написать следующий код:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
</head>
<body>
  <div ng-app>
    Hello, <?php echo htmlspecialchars($_GET['name']); ?>
  </div>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.3/angular.min.js"></script>
</body>
</html>

Хотя в коде выше закодирован ввод, в {{ alert(1) }} нет опасных символов, поэтому это все равно может привести к XSS.

Метод защиты такой же, как у SSTI. Никогда не рассматривайте ввод пользователя как часть содержимого шаблона, или это легко может вызвать проблемы.

Практический пример CSTI

Давайте приведём интересный случай в качестве примера. Масато Кинугава, исследователь в области кибербезопасности из Японии, продемонстрировал уязвимость удаленного выполнения кода (RCE) в программном обеспечении для связи Microsoft Teams во время Pwn2Own 2022. Отправив сообщение целевой стороне, можно выполнить код на их компьютере! Эта уязвимость принесла приз в размере $150,000 на Pwn2Own.

Программное обеспечение Teams для настольных компьютеров создано с использованием Electron, по сути, это веб-страница. Чтобы добиться RCE, обычно первым шагом является поиск XSS, который позволяет выполнить код JavaScript на веб-странице.

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

Например, даже для имен классов разрешены только определенные имена классов. Масато обнаружил, что у санитайзера есть некоторый простор для манипуляций с именами классов. Например, есть правило типа swift-*, поэтому и swift-abc, и swift-; [] ()'% допускается в качестве имен классов.

Но что пользы только от манипуляций с именами классов?

Вот ключ: веб-страница Teams написана на AngularJS, у которого есть много магических функций. Одна из них – атрибут ng-init, используемый для инициализации, вот так:

<!DOCTYPE html>
<html lang="en">
<body>
  <div ng-app>
    <div ng-init="name='test'">
      {{ name }}
    </div>
  </div>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.3/angular.min.js"></script>
</body>
</html>

Это отобразит test на странице, указывая на то, что код внутри ng-init выполнен.

Так что если вы измените его на ng-init = "constructor.constructor ('alert (1)') ()", появится предупреждающее окно.

Какое это имеет отношение к ранее упомянутому названию класса? Оказывается, что этот ng-init также можно использовать внутри имени класса:

<div class="ng-init:constructor.constructor('alert(1)')()"></div>

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

AngularJS и CSP Bypass

На практике AngularJS наиболее часто используется для обхода CSP. Если вы можете найти AngularJS в разрешенных путях CSP, существует большая вероятность его обхода. Например:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta http-equiv="Content-Security-Policy" content="script-src https://cdnjs.cloudflare.com">
</head>
<body>
  <div ng-app ng-csp>
    <input id="x" autofocus ng-focus="$event.composedPath() | orderBy:'(z=alert)(1)'">
  </div>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.3/angular.min.js"></script>
</body>
</html>

Хотя это может показаться простым, при более тщательном рассмотрении это не так просто. Подумайте, CSP не имеет unsafe-eval, поэтому ни одна строка не может быть выполнена как код. Но тогда как все эти строки внутри ng-focus выполняются? Разве это не выполнение строк как кода?

Таким образом, можно считать, что AngularJS реализует свой собственный eval для выполнения строк как кода, не используя эти стандартные функции.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta http-equiv="Content-Security-Policy" content="script-src https://www.google.com/recaptcha/">
</head>
<body>
  <div ng-controller="CarouselController as c" ng-init="c.init()">
    [[c.element.ownerDocument.defaultView.alert(1)]]
    <div carousel>
      <div slides></div>
    </div>
  </div>
  <script src="https://www.google.com/recaptcha/about/js/main.min.js"></script>
</body>
</html>

Заключение

Рассмотренная на этот раз CSTI также является типом атаки "непрямого выполнения JavaScript".

Когда вы кодируете все выводы и думаете, что это безопасно, забывая, что на вашем фронтенде есть AngularJS, злоумышленники могут достичь XSS через казалось бы безопасные {{}}, используя CSTI.

Хотя сейчас сайтов, использующих AngularJS, становится все меньше и меньше, и все меньше людей рассматривают ввод пользователя как часть шаблона. Многие уязвимости попросту еще не были обнаружены.

Если ваш сервис использует AngularJS, убедитесь, что нет проблем с CSTI.

PreviousМожет ли HTML влиять на JavaScript - Введение в DOM clobberingNextCSS Injection - Атака с использованием только CSS (Часть 1)

Last updated 8 months ago

Для более подробного процесса вы можете обратиться к:

Подробности можно найти в оригинальной статье автора:

Например, одним из них является Angular. Вот пример с :

В документации Angular упоминается :

Этот цикл обхода, улучшения песочницы и снова обхода продолжался. Наконец, AngularJS объявил о полном удалении песочницы после версии 1.6. Причина в том, что песочница на самом деле не является функцией безопасности. Если ваш шаблон может быть контролирован, то это проблема которую необходимо решить, а не надеяться на песочницу. Детали можно найти в оригинальной статье объявления: . Больше истории обхода можно найти в .

Оригинальная статья также включает раздел о том, как AngularJS анализирует имена классов и обходит песочницу AngularJS для этой версии. Превращение XSS в RCE потребует дополнительный средств, но поскольку они не связаны с рассмотренной в этой статье CSTI, они пропускаются. Я настоятельно рекомендую прочитать оригинальную презентацию: .

Настройка CSP выглядит строгой и разрешает только , но это позволяет нам подключить AngularJS, что приводит к XSS.

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

Когда мы обсуждали обход CSP ранее, я упоминал, что сделав конфигурацию путей более строгой, можно "снизить риск", а не "полностью устранить опасность". Примером являлось установка его на вместо .

На самом деле, в GoogleCTF 2023 было задание обойти CSP , и решение использовало AngularJS. Вот почему я сказал, что строгий путь может снизить риск, но не может полностью избежать его:

Если вас интересует обход CSP в AngularJS, вы можете обратиться к статье: , где представлен другой метод обхода.

Uber Remote Code Execution via Flask Jinja2 Template Injection
Handlebars template injection and RCE in a Shopify app
официального сайта Angular
Angular's cross-site scripting security model
Обход песочницы выражения AngularJS
DOM-based AngularJS sandbox escapes
How I Hacked Microsoft Teams and got $150,000 in Pwn2Own
https://cdnjs.cloudflare.com
ng-csp
https://www.google.com/recaptcha/
https://www.google.com
https://www.google.com/recaptcha/
Automatically Finding Alternatives to prototype.js in AngularJS CSP Bypass