Безопасность в вебе, или TrustedTypes как новый способ защиты от XSS
В этой статье я постараюсь вас убедить, что опасность XSS, несмотря на все современные фреймворки, по-прежнему существует. Также мы рассмотрим главные способы защиты. Основное внимание уделим новому революционному подходу — DOM TrustedTypes, который, несмотря на то что еще находится в разработке, обещает поднять безопасность браузеров на новый уровень.
Безопасность в вебе
В далеком 2008 году я окончил университет магистром в области информационной безопасности. К сожалению, ничего интересного по специальности на тот момент не нашлось, и я пошел работать в мир веб-разработки. Имея серьезную теоретическую базу, я всегда представлял защиту информации на уровне криптографических алгоритмов, технических устройств и процессов. Но когда я углубился в веб-разработку, понял, что мир веба развивается очень быстро, а вот культура безопасности серьезно отстает. Это было во всем. Никто не уделял внимания анализу уязвимостей. Этим грешили даже крупные компании. Дыры безопасности можно было найти в порталах с миллионами активных пользователей. Простые поисковые запросы в гугле сразу же выдавали десятки сайтов.
Однако после крупных взломов таких компаний, как MySpace (XSS), eBay (XSRF) и многих других, до компаний начало доходить, что нужно что-то делать.
В веб началась эра фреймворков, которые решали большую часть проблем, связанных с фильтрацией и выводом данных. Но сами фреймворки, несмотря на то что закрыли явные проблемы, за несколько лет очень расслабили программистов, которые решили, что с точки зрения безопасности работы для них уже не осталось.
Вернуться к теме безопасности нам пришлось в процессе миграции старой инфраструктуры. И тут я обнаружил на поверхности еще очень много проблем, которым по-прежнему никто не уделяет достаточного внимания.
В своей статье я хочу сфокусироваться на XSS-атаке, а также рассмотреть новый способ защиты от нее — DOM TrustedTypes.
Почему XSS все еще опасен
XSS (англ. Cross-Site Scripting — «межсайтовый скриптинг») — атака не новая, и большая часть программистов с ней знакомы. Хакер использует уязвимость (как правило, это неотфильтрованный пользовательский ввод данных), чтобы разместить вредоносный код.
С каждым годом фронтенд-приложений становится все больше, и сами приложения становятся все объемнее и сложнее. Поэтому логично, что появляется все больше атак XSS непосредственно в DOM.
Давайте рассмотрим пример. Программисты веб-приложения написали вот такой код:
Чем это может быть опасно? Тем, что мы выводим неотфильтрованные данные прямиком в DOM. Хакер может подкинуть для нашего пользователя-жертвы вот такую ссылку:
При этом в DOM мы получим вредоносный код:
Вывод alert сообщения сам по себе не опасен, но обычно с его помощью хакер тестирует сайт на уязвимости.
Многие считают, что основная цель XSS — стянуть пароль из кукисов, ну а если пароль/токен в кукисах не хранится (а например, в localStorage), то XSS сайту не страшен. Это заблуждение. Кроме стягивания кукисов, c XSS можно совершать много различных манипуляций, например:
Как вы, наверное, догадались — этот код предназначен, чтобы красть номера кредитной карточки.
А вот этот будет заниматься майнингом криптовалюты за счет браузера нашего пользователя:
Но это далеко не все. Если вы дадите хакеру доступ к выполнению произвольных скриптов путем внедрения XSS, он сможет сделать практически все с компьютером жертвы, даже такой интересный вариант, как, например, организовать запись с веб-камеры.
Если вы до этого не сталкивались с такой утилитой, как BeEF — очень рекомендую обратить внимание. Она помогает раскрыть потенциал уязвимости и обеспечивает удобный графический интерфейс.
Еще одно заблуждение, что XSS — проблема прошлого, а сейчас нас защищают современные фреймворки и фаерволы на серверах, что у всех серьезных компаний инфраструктура настроена так, что как бы «криворукие» программисты ни старались, они не смогут создать XSS.
Чтобы развенчать этот миф, достаточно привести пример Google: основная функция — поиск — подверглась XSS в конце прошлого года. А привела к этому уязвимость в библиотеке Closure.
Как защититься от XSS
Если я вас уже убедил, что XSS до сих пор является достаточно серьезной угрозой вашим веб-проектам — можем переходить к рассмотрению способов защиты.
Фильтрация/санитизация данных
Пожалуй, самый надежный способ защититься от межсайтового скриптинга — это фильтрация (или санитизация) данных. Не думаю, что я смогу поведать вам что-то новое, но хотел бы подчеркнуть основные моменты:
- Не доверять никому (с вашего API тоже могут прийти опасные с точки зрения HTML данные, на стороне сервера работают такие же программисты, которые могли забыть что-то отфильтровать).
- Не пытайтесь написать санитизатор самостоятельно, вы не умнее десятков тысяч хакеров, которые пытаются обойти все варианты фильтрации. Уже существует множество зрелых решений для любой технологии.
- Общая санитизация не подходит, если мы не знаем тип контента, который собираемся отфильтровать (ведь для URL и для HTML опасными будут разные кодовые комбинации).
Использование CSP-заголовков
Content Security Policies (или просто CPS) — очень хороший способ защиты от XSS, когда у вас огромный проект, и вы не уверены, что знаете все его места. CSP позволяет будто зонтиком «накрыть» сразу весь проект, и даже плохой код с уязвимостями и неотфильтрованными данными.
Как работает CSP
CSP — это специальная дополнительная информация, которая передается браузеру либо в HTTP-заголовках, либо в мета-тегах сайта. В ней задаются правила, как браузер должен себя вести с разными типами ресурсов: разрешать или блокировать.
То есть в теории если мы знаем, что у нас нет инлайн скриптов, мы можем их просто запретить, и если хакер сможет внедрить опасный код из примера выше:
Он просто не выполнится в браузере.
Включение CPS в режиме отчета
Самое прекрасное, что вы можете включить CSP в режиме отчета: то есть ничего у вас не поломается, не заблокируется — просто система будет оповещать, что что-то работает некорректно согласно политикам безопасности, и до того как их включать, необходимо это исправить, иначе на сайте может перестать работать какая-то функция.
CSP в реальной жизни
Мы на своем проекте принципиально отказались от inline-скриптов, и даже код-сниппеты, такие как Google Analytics, перенесли в отдельные файлы. Это, я думаю, первое, что вам необходимо сделать, чтобы предотвратить возможную XSS-атаку.
Далее прошлись по всем сторонним ресурсам, которые используются приложением, и добавили их в исключения. Не со всеми сервисами 3rd Party это получается просто: они подгружают дополнительные файлы динамически, то есть при обновлении библиотеки это уже может быть другой файл.
Проблемным также оказался вопрос с фреймами: некоторые клиенты используют наш портал внутри тега iframe.
Курьезный момент: благодаря CSP мы обнаружили, что одна из наших библиотек использует внутри себя метод eval. Пришлось отписать им, чтобы обновили. Так CSP помогает вам не только в целом защититься от возможных атак, но также выявить и устранить проблемные места в ваших проектах.
Более подробно о CSP можно почитать в MDN-документации.
Использование Trusted Types в DOM
Если мы копнем глубже — из-за чего происходят все проблемы — то окажется, что мы просто доверяем браузеру делать слишком много магии, при этом не задавая контент для выполнения. Самый распространенный случай — innerHTML= ‘.......’ То есть мы передаем строку, в которой может быть все что угодно, в том числе вредоносный скрипт.
При этом вы можете работать с innerHTML не напрямую — это может делать одна из ваших вспомогательных библиотек (так, например, делала jQuery в методе append).
Так, может, нам просто заблокировать innerHTML на глобальном уровне? Нет, увы: innerHTML — не единственное проблемное место в DOM, есть много свойств-методов, которые делают примерно такую же магию с передаваемой строкой:
Но что, если бы существовала возможность передавать значение не как строка, а как объект? Ведь DOM уже давно поддерживает такую возможность:
При этом этот объект знал бы свой скоуп, то есть либо это URL, либо это HTML, либо еще что-то. В зависимости от этого браузер или сам объект мог бы валидировать/фильтровать данные, ничего не ломая.
Веб-платформа либо полифилы предоставляет набор готовых типов:
- TrustedHTML
- TrustedScript
- TrustedURL
- TrustedScriptURL
Но при этом мы можем добавлять свои в случае необходимости.
Как подключить Trusted Types
Для подключения Trusted Types необходимо лишь добавить в CSP дополнительную инструкцию:
Content-Security-Policy: trusted-types *
И теперь, если мы попытаемся сделать что-то такое в коде:
Браузер просто выдаст ошибку:
Uncaught TypeError: Failed to set the ’innerHTML’ property on ’Element’: This document requires `TrustedHTML` assignment.
Но если мы сделаем присвоение через специальный объект:
Все отработает правильно!
Плюс ко всему, мы еще можем лимитировать возможные варианты политик: добавим вместо звездочки имя политики (класса), которую используем:
Content-Security-Policy: trusted-types myPolicy
Несмотря на то что этот проект все еще остается в формате Draft, вы уже можете поиграться в Chrome 73+, используя специальный флаг:
chrome --enable-blink-features=TrustedDOMTypes
TrustedTypes в реальной жизни
Даже при том, что DOM TrustedTypes по-прежнему является экспериментальной функцией, если вы хотите в последующем (с принятием стандарта) активировать его на проекте — изменения нужно начинать готовить уже сейчас.
Мы на своем проекте используем Angular, который уже частично включает концепцию TrustedTypes: если правильно использовать фреймворк, то он будет контролировать всю передачу и отображение данных в шаблоне, при этом Angular четко знает тип передаваемых данных и экранирует их в зависимости от типа. Более того, уже даже есть Pull Request добавить TrustedTypes нативно во фреймворк.
P. S. Я надеюсь, эта статья позволит вам быстрее разобраться с TrustedTypes и поможет сделать вашу систему безопаснее уже сегодня благодаря использованию лишь нескольких дополнительных настроек.