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

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

Browser Support

  • Chrome: 83.
  • Край: 83.
  • Firefox Technology Preview: поддерживается.
  • Сафари: 26.

Source

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

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

Фон

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

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

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

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

Введение в API

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

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

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

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

Делать
anElement.innerHTML = aTrustedHTML;
  
При включенной функции Trusted Types браузер принимает объект 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, — это код в одной из ваших политик, и вы можете еще больше ограничить его, ограничив создание политик .

Дополнительная информация