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
  • Первая Тактика: Наиболее эффективное решение - двухфакторная аутентификация
  • Вторая Тактика: Предотвращение кражи токена
  • Третья Тактика: Ограничение вызовов API
  • Четвертая Тактика: Ограничение области действия для токенов
  • Заключение
  1. Глава 2 - Защита и Обход для XSS

Третья линия обороны против XSS - сокращение области воздействия

PreviousВторая линия обороны от XSS - CSP (Content Security Policy)NextПоследние методы защиты от XSS - Trusted Types и встроенный Sanitizer API

Last updated 8 months ago

В первой части защиты от XSS, мы научились обрабатывать пользовательский ввод, чтобы убедиться, что он закодирован или санитизирован, предотвращая вставку вредоносного контента. Вторая часть защиты - это знакомство с механизмом работы CSP, который предотвращает выполнение JavaScript и загрузку внешних ресурсов, даже если вредоносный контент вставлен.

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

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

Давайте я приведу пример. Вы, наверное, видели фильмы типа «Миссия невыполнима», где есть сцены с кражей. Место, где хранятся ценные предметы, всегда оборудовано многоуровневыми мерами безопасности. Там есть распознавание сетчатки, распознавание лица, распознавание голоса, и даже распознавание походки. После прохождения этих проверок, вам все еще нужен ключ, чтобы открыть дверь хранилища. За дверью может быть сейф, и вам потребуется другой набор паролей, чтобы открыть сейф и наконец получить предмет

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

Тот же самый принцип применим для кибербезопасности. Даже если мы тщательно проверяем код бэкенда и убеждаемся, что каждая часть должным образом проверяется и кодируется, мы не можем гарантировать, что фронтэнд никогда не будет уязвим для XSS. Новые разработчики могут допустить ошибки и написать небезопасный код, а сторонние библиотеки могут иметь уязвимости 0-day или быть скомпрометированы вредоносным кодом. Все это риски.

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

После принятия всех возможных мер защиты от XSS, третий слой - это подумать в обратном направлении: "Если XSS неизбежно произойдет, что мы можем сделать, чтобы свести ущерб к минимуму?" Это добавляет дополнительный слой безопасности, так что даже если XSS и произойдет, воздействие не будет таким серьезным.

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

Хотя добавление дополнительного слоя некоторым образом увеличивает безопасность, это также добавляет затраты и усложняет процесс. Не каждый продукт требует такой строгой защиты. Например, в случае с каким-нибудь простым блогом, даже если он подвержен XSS, воздействие минимально. Поэтому даже CSP не особо нужен или рассмотрение того, как смягчить ущерб, вызванный XSS.

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

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

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

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

Чтобы уменьшить воздействие XSS, нам нужно найти способы ограничить то, что злоумышленники могут сделать.

Первая Тактика: Наиболее эффективное решение - двухфакторная аутентификация

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

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

Например, при выполнении перевода в интернет-банкинге, после ввода суммы и адреса получателя, обычно есть дополнительный шаг, такой как ввод предопределенного пароля для интернет-банкинга или получение кода подтверждения через СМС. Это гарантирует дополнительный уровень безопасности. Например, если у банковского сайта есть уязвимость XSS, и злоумышленник может выполнить произвольный код на банковской странице, без дополнительного шага по обеспечению безопасности, злоумышленник мог бы просто вызвать API /transfer и перевести ваши деньги.

Однако с дополнительным шагом одним из параметров для /transfer будет код подтверждения через СМС. Поскольку злоумышленник не знает код подтверждения, он не может успешно вызвать API, и, следовательно, не может украсть ваши деньги.

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

И помимо XSS, это также гарантирует, что "даже если кто-то физически получает доступ к вашему компьютеру, они не смогут сделать ничего вредоносного." Это показывает значительное улучшение в области безопасности. Обычно безопасность и пользовательский опыт обратно пропорциональны. Чем выше безопасность, тем хуже опыт, потому что вам приходится делать больше, и это неизбежно.

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

Вторая Тактика: Предотвращение кражи токена

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

Если токен украден, пользователь может использовать его для отправки запросов на бэкенд API, не ограничиваясь браузером.

Некоторые могут подумать: "Имеет ли значение, если токен украден?" Например, предположим, что токен хранится в виде куки с параметром HttpOnly, который гарантирует, что JavaScript не может получить доступ к куки. Однако, когда злоумышленник использует fetch('/api/me'), они все же могут получить личные данные, потому что куки автоматически включены в запрос.

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

Первое различие - будут ли они "ограничены сайтом".

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

Например, допустим, есть два веб-сайта, a.example.com и b.example.com, оба используют куки, хранящиеся в example.com для аутентификации.

Предположим, что злоумышленник успешно нашел уязвимость XSS на a.example.com, но пользовательские данные находятся на b.example.com. В этом случае они не могут получить доступ к данным пользователя на домене a, потому что политика единого источника блокирует запрос fetch. Однако, если обе службы используют тот же токен и хранят его в localStorage, злоумышленник может использовать этот токен для доступа к домену b и успешно извлечь данные пользователя.

Второе различие - есть ли "временное ограничение". Если у них есть токен, они могут отправлять запросы от имени пользователя, пока токен не истечет.

Но если они могут использовать только XSS, это означает, что они могут выполнять атаки только тогда, когда пользователь открыл веб-страницу. Как только пользователь закрывает веб-страницу или браузер, они больше не могут выполнить код JavaScript.

Поэтому, если возможно, лучше не позволять злоумышленнику получить токен, так как это ограничивает атаки, которые злоумышленник может провести.

По состоянию на сегодняшний день на фронтенде единственный способ обеспечить недоступность токена для JavaScript - это использовать куки с параметром HttpOnly (исключая уязвимости браузера и API, которые напрямую возвращают токены). Других вариантов нет.

Однако, если ваша задача - "разрешить доступ к токену только определенному JavaScript", есть другое решение. Но обратите внимание, что это решение не хранит токен постоянно. Как только пользователь обновит страницу, токен будет потерян.

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

const API = (function() {
    let token;

    return {
        login(username, password) {
            return fetch('/api/login', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json' 
                },
                body: JSON.stringify({ username, password })
            })
            .then(res => res.json())
            .then(data => {
                token = data.token;
            });
        },
        async getProfile() {
            return fetch('/api/me', {
                headers: {
                    'Authorization': 'Bearer ' + token
                }
            });
        }
    };
})();

// Using API
API.login('yourUsername', 'yourPassword').then(() => {
    API.getProfile().then(profile => {
        console.log(profile);
    });
});

Таким образом, даже если злоумышленник обнаруживает уязвимость XSS, он не может "directly" получить доступ к переменной token из-за области его видимости. Я подчеркнул слово "directly", потому что как только у злоумышленника есть возможность провести XSS-атаку, он может сделать много опасных вещей, например вот так:

window.fetch = function(path, options) {
    console.log(options?.headers?.Authorization);
};

API.getProfile();

Заменяя реализацию window.fetch, они могут перехватывать параметры, переданные функции, и косвенно получить доступ к token.

Поэтому, более безопасный метод - предотвратить вмешательство с помощью XSS в среду выполнения, которая имеет токен, достигая изоляции контекста. В веб-фронтенде это можно сделать с помощью Web Workers. Используя Web Workers, можно создать новую среду выполнения для ее изоляции, как показано на следующей схеме:

Приблизительный код выглядит так (это просто концептуальный пример):

// worker.js
let token;

async function login({ username, password }) {
    return fetch('/api/login', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json' // Добавлено для указания типа контента
        },
        body: JSON.stringify({ username, password })
    })
    .then(res => res.json())
    .then(data => {
        // Do not return token
        const { token: newToken, ...props } = data; // Переименован для избежания конфликта
        token = newToken; // Сохранение токена
        return props;
    });
}

async function getProfile() {
    return fetch('/api/me', {
        headers: {
            'Authorization': 'Bearer ' + token
        }
    });
}

onmessage = async function(e) {
    const { name, params } = e.data;
    let response;

    if (name === 'login') {
        response = await login(params);
    } else if (name === 'getProfile') {
        response = await getProfile();
    }

    postMessage({
        name,
        response
    });
};

И в коде приложения инициализируйте рабочего и вызовите API:

const apiWorker = new Worker("worker.js");

async function login(params) {
    return new Promise(resolve => {
        apiWorker.postMessage({
            name: 'login',
            params: {
                username: params.username, // Используем переданные параметры
                password: params.password
            }
        });

        apiWorker.onmessage = (e) => {
            const { name, response } = e.data;
            if (name === 'login') {
                resolve(response); // Исправлено на response
            }
        };
    });
}

login({
    username: 'test',
    password: 'test'
});

На самом деле, идея заключается в том, чтобы поместить все запросы API в рабочий поток. Благодаря изоляции среды выполнения, если в рабочем потоке нет XSS, основной поток не может вмешаться в рабочего и не может получить доступ к его данным. Это обеспечивает безопасность токена.

Третья Тактика: Ограничение вызовов API

Как отмечалось ранее, даже если токен не украден, злоумышленники могут все еще вызвать API и получить ответ через XSS, что верно, когда вы используете cookies для хранения токена.

Однако, если вы используете упомянутый выше метод, используя Web Workers и переменные для хранения токена, ситуация меняется. Использование этого метода означает, что для злоумышленников бесполезно самим вызывать API с помощью fetch(), потому что в запросе не будет прикреплен токен, поэтому аутентификация на сервере не пройдет.

Как в приведенном выше примере, все запросы API должны проходить через Web Workers, что похоже на создание прокси на уровне фронтенда в рабочем процессе. Поэтому, даже если XSS может получить доступ к apiWorker, он может вызывать только API, реализованные apiWorker, и не может вызывать другие.

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

Четвертая Тактика: Ограничение области действия для токенов

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

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

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

Предположим, что XSS появилась на веб-сайте бронирования ресторана, и целью атаки стал авторизованный внутренний сотрудник. В этом случае, злоумышленник может вызвать /internal/users для получения данных всех пользователей. Идеальным решением было бы разделение внутренней системы и системы бронирования ресторана на уровне бэкенд API, но это может потребовать слишком много времени и затрат.

В этом случае может быть использовано другое решение, называемое Backend For Frontend (BFF). BFF — это бэкенд-сервер, специально предназначенный для фронтенда, и все запросы от фронтенда проходят через BFF, как показано на диаграмме:

Таким образом, токен, полученный фронтендом, является только токеном, используемым для связи с BFF, а не токеном бэкенд-сервера за BFF. Таким образом, на стороне BFF можно ограничить права доступа, прямо блокируя все запросы к /internal, и ограничивая права токена, полученного фронтендом, обеспечивая, что внутренние API не могут быть вызваны.

Заключение

"Предотвращение XSS" - это то, что обязательно нужно сделать, но это только первый способ защиты. Если сделано только это, то защита либо 0, либо 1. Либо все хорошо защищено, либо если в одном аспекте защита плохо настроена, то как будто нет никакой обороны, и система легко поддаётся.

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

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

Изначально у меня не было глубокого понимания этой темы. Просто случайно кто-то поднял эту тему в Facebook-группе "Front-End Developers Taiwan", что позволило мне лучше понять её, и заставило меня включить её в эту серию статей.

Ссылки:

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

Что касается хранения токенов, если вам нужен доступ к токену с JavaScript и не нужно его сохранение, вероятно, это лучший вариант. Auth0, компания, специализирующаяся на проверке подлинности, также написала статью, посвященную хранению токенов, на которую вы можете ознакомиться:

1. 2. 3. 4.

Построение безопасных веб-приложений с помощью Web Workers
Документация Auth0 - Хранение токенов
Facebook Group: Front-End Developers Taiwan Ветка обсуждения
auth0 - Хранение токенов
Построение безопасных веб-приложений с использованием Web Workers
Почему BFF считается безопаснее для SPA-приложений?