Предотвратите уязвимости межсайтового скриптинга на основе DOM с помощью доверенных типов

Кшиштоф Котович
Krzysztof Kotowicz

Browser Support

  • Хром: 83.
  • Край: 83.
  • Firefox: за флагом.
  • Сафари: 26.

Source

Межсайтовый скриптинг на основе DOM (DOM XSS) происходит, когда данные из источника , контролируемого пользователем (например, имя пользователя или URL-адрес перенаправления, взятый из фрагмента URL-адреса), достигают приемника , который представляет собой функцию, такую ​​как eval() или средство установки свойств, такое как .innerHTML , которое может выполнять произвольный код JavaScript.

DOM XSS — одна из самых распространённых уязвимостей веб-безопасности, и команды разработчиков часто случайно внедряют её в свои приложения. Доверенные типы предоставляют инструменты для разработки, проверки безопасности и защиты приложений от DOM XSS-уязвимостей, делая опасные функции веб-API безопасными по умолчанию. Доверенные типы доступны в виде полифилла для браузеров, которые их пока не поддерживают.

Фон

На протяжении многих лет DOM XSS является одной из самых распространенных и опасных уязвимостей веб-безопасности.

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

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

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

Введение в API

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

Доверенные типы данных требуют обработки данных перед их передачей этим функциям-приемникам. Использование только строки недопустимо, поскольку браузер не знает, являются ли данные достоверными:

Не
anElement.innerHTML  = location.href;
При включении доверенных типов браузер выдает ошибку TypeError и предотвращает использование приемника DOM XSS со строкой.

Чтобы подтвердить безопасную обработку данных, создайте специальный объект — доверенный тип.

Делать
anElement.innerHTML = aTrustedHTML;
  
При включении доверенных типов браузер принимает объект TrustedHTML для приёмников, ожидающих фрагменты HTML. Для других конфиденциальных приёмников также предусмотрены объекты TrustedScript и TrustedScriptURL .

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

Как использовать доверенные типы

Подготовьтесь к отчетам о нарушении Политики безопасности контента

Вы можете развернуть сборщик отчётов, например, reporting-api-processor или go-csp-collector с открытым исходным кодом, или использовать один из коммерческих аналогов. Вы также можете добавить пользовательское ведение журнала и отладку нарушений в браузере с помощью ReportingObserver :

const observer = new ReportingObserver((reports, observer) => {
    for (const report of reports) {
        if (report.type !== 'csp-violation' ||
            report.body.effectiveDirective !== 'require-trusted-types-for') {
            continue;
        }

        const violation = report.body;
        console.log('Trusted Types Violation:', violation);

        // ... (rest of your logging and reporting logic)
    }
}, { buffered: true });

observer.observe();

или добавив прослушиватель событий:

document.addEventListener('securitypolicyviolation',
    console.error.bind(console));

Добавить заголовок CSP только для отчетов

Добавьте следующий заголовок HTTP-ответа к документам, которые вы хотите перенести в доверенные типы:

Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example

Теперь все нарушения отправляются в //my-csp-endpoint.example , но сайт продолжает работать. В следующем разделе объясняется, как работает //my-csp-endpoint.example .

Выявить нарушения доверенных типов

С этого момента каждый раз, когда Trusted Types обнаруживает нарушение, браузер отправляет отчёт на настроенный report-uri . Например, когда ваше приложение передаёт строку в innerHTML , браузер отправляет следующий отчёт:

{
"csp-report": {
    "document-uri": "https://my.url.example",
    "violated-directive": "require-trusted-types-for",
    "disposition": "report",
    "blocked-uri": "trusted-types-sink",
    "line-number": 39,
    "column-number": 12,
    "source-file": "https://my.url.example/script.js",
    "status-code": 0,
    "script-sample": "Element innerHTML <img src=x"
}
}

Это означает, что в https://my.url.example/script.js в строке 39 был вызван innerHTML со строкой, начинающейся с <img src=x . Эта информация поможет вам определить, какие части кода могут содержать DOM XSS и требуют изменений.

Устранить нарушения

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

Перепишите ошибочный код.

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

Делать
el.textContent = '';
const img = document.createElement('img');
img.src = 'xyz.jpg';
el.appendChild(img);
Не
el.innerHTML = '<img src=xyz.jpg>';

Использовать библиотеку

Некоторые библиотеки уже генерируют доверенные типы, которые можно передать функциям приёмника. Например, можно использовать DOMPurify для очистки HTML-фрагмента, удаляя XSS-полезную нагрузку.

import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(html, {RETURN_TRUSTED_TYPE: true});

DOMPurify поддерживает доверенные типы и возвращает очищенный HTML, завернутый в объект TrustedHTML , чтобы браузер не генерировал нарушение.

Создайте политику доверенного типа

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

Сначала создайте политику . Политики — это фабрики доверенных типов, которые применяют определённые правила безопасности к своим входным данным:

if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
  const escapeHTMLPolicy = trustedTypes.createPolicy('myEscapePolicy', {
    createHTML: string => string.replace(/\</g, '&lt;')
  });
}

Этот код создаёт политику myEscapePolicy , которая может создавать объекты TrustedHTML с помощью функции createHTML() . Заданные правила HTML-экранирования символов < предотвращают создание новых HTML-элементов.

Используйте эту политику следующим образом:

const escaped = escapeHTMLPolicy.createHTML('<img src=x onerror=alert(1)>');
console.log(escaped instanceof TrustedHTML);  // true
el.innerHTML = escaped;  // '&lt;img src=x onerror=alert(1)>'

Использовать политику по умолчанию

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

if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
  trustedTypes.createPolicy('default', {
    createHTML: (string, sink) => DOMPurify.sanitize(string, {RETURN_TRUSTED_TYPE: true})
  });
}

Политика с именем default применяется везде, где в приемнике используется строка, которая принимает только доверенный тип.

Перейти к применению политики безопасности контента

Когда ваше приложение больше не будет выдавать нарушений, вы можете начать применять доверенные типы:

Content-Security-Policy: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example

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

Дальнейшее чтение