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

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

Поддержка браузера

  • Хром: 83.
  • Край: 83.
  • Firefox: не поддерживается.
  • Сафари: не поддерживается.

Источник

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

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

Фон

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

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

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

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

Введение в API

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

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

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

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

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

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

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

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

Вы можете развернуть сборщик отчетов, например, report-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-escape < символов предотвращают создание новых элементов 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

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

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