Актуальны ли ещё угрозы XSS? Прошло около 20 лет с тех пор, как Cross Site Scripting (XSS) появился как вид атаки. С тех пор мы получили богатый опыт и знания, защита наших сайтов стала намного сложнее, а многочисленные фреймворки были призваны оберегать нас от ошибок. Но последние данные показывают совсем другую картину: в первых кварталах 2017 года количество сообщений об XSS-атаках и количество найденных уязвимостей выросло в несколько раз.
В этом хабропосте мы расскажем, как страшно жить, почему ваши приложения в опасности, почему фреймворки не спасают, как находить уязвимости и какие инструменты для этого использовать.
Прототипом статьи является доклад на конференции HolyJS 2017 Moscow. Алексей — фронтенд-тимлид/архитектор в компании EPAM Systems и один из лидеров сообщества FrontSpot в Минске. Основные области профессиональных интересов: архитектура и инфраструктура приложений, управление разработкой.
В этом тексте огромное количество картинок со слайдов. Осторожно, трафик!
Я знаю, что многие из вас уже ловили какие-то эксцесс-атаки. Хочу рассказать вам пару интересных историй, случаев, на что нам стоит обращать внимание, и в конце задам вопрос — насколько вы уверены, что ваши приложения неуязвимы?
Начну с описания событий, которые произошли более 12 лет назад и заставили немного по-другому взглянуть на безопасность веб-приложений, фронтенд-приложений, да и в принципе на безопасность в целом. 4 октября 2005 года Сэми Камкар на своей страничке в MySpace оставил комментарий. Комментарий с вирусом, который всего лишь за 20 часов заразил более 1 миллиона пользователей. 20 часов — 1 миллион пользователей. К счастью для этих самых пользователей, вирус не делал ничего плохого. Он добавлял Сэми в друзья и в разделе Heroes писал: «But most of all, Samy is my hero». Сэми использовал одну из известных в то время уязвимостей Internet Explorer и оставил комментарий, содержащий кусок html. Там был div с кастомным атрибутом, в котором был спрятан скрипт.
И, с помощью inline-стиля, он этот самый скрипт запускал. Таким образом, когда кто-то заходил на страничку к Сэми, скрипт выполнялся, добавлял Сэми в друзья, заражал страничку, и так по цепной реакции. Когда вы посещали зараженную страничку, вы сами заражались и могли заражать других. 20 часов — 1 миллион пользователей. Этот вирус официально был признан самым быстро распространяемым червем в истории. Сэми сам говорил, что это была всего лишь шутка. «Я хотел пошутить». Так вот, за свою шутку Сэми получил 3 года.
Прошло 12 лет, что за это время изменилось? Кто из нас пользуется MySpace? MySpace мертв. Появились новые браузеры, новые фреймворки, новые библиотеки, технологии, да и сам мир, в принципе, изменился.
Я работаю в EPAM в минском офисе, в центре компетенции. И к нам периодически приходят клиенты, заказчики, аккаунт-менеджеры, project-менеджеры для того, чтобы мы провели аудит их проектов. У нас есть большой чек-лист, на что мы проверяем, и один из пунктов называется security. Так вот, эта самая security, за время моей работы, не прошла с первого раза ни один проект. Когда мы общаемся с клиентами, мы им рассказываем, что мы нашли уязвимость. И клиент отвечает: «Ну, это же всего лишь одна». И нам каждый раз приходится рассказывать одно и то же. Всего лишь одна дырка, одно открытое окно, и там обязательно появится чья-то рыжая морда.
В вашем приложении не должно быть security-багов, ноль.
Итак, security — это очень обширная тема. Настолько большая, что если мы будем каждый день собираться и говорить о ней, нам не хватит и месяца.
Я бы хотел остановиться именно на том примере, который я привел в самом начале, а именно на уязвимости так называемой Cross-Site Scripting (XSS). Как баг он появился целых 20 лет назад. За это время он сильно мутировал, породил огромное количество других видов уязвимостей. И о нем мы будем говорить сегодня. Чтобы подогреть интерес именно к этому виду уязвимостей, я хотел бы предоставить вам немного статистики. Есть такая компания Akamai. Если кто-то не знает, это один из крупнейших дистрибьютеров контента в мире, который, по разным данным, контролирует 15-30% интернет-трафика в разных регионах. Это очень много. Каждый квартал они выпускают отчет, и один из отчетов называется Security Report. Что интересно в этом отчете? Что процент XSS-атак от общего количества атак уже составил более 20%.
Это только за первый квартал 2017 года. В среднем, каждый сайт был атакован более 30 раз, именно с помощью XSS. Довольно тревожно, правда?
Мы говорим про XSS, но что это такое? Нужны еще примеры. Давайте представим, что ваш друг присылает какую-то ссылку. На почту, в Skype, в любой мессенджер или чат. Все в этой ссылке ничего, кроме вот этого параметра.
В данном случае очень очевидно виден скрипт. Так случилось, что мы взяли и нажали на эту ссылку. Что произойдет дальше? Вы можете перейти по ссылке и увидеть там сайт, можете увидеть там «Page not found» или «По заданным вами критериям ничего не было найдено». Но фишка в том, что скрипт уже на вашей страничке. Скрипт уже выполнился и украл ваши данные. Преступление совершено.
Этот тип атаки называется «активная XSS», то есть злоумышленник работает непосредственно с пользователями. Он рассылает спам, сообщения, пытается как-то до вас донести эту самую ссылочку. И, будем говорить откровенно, если вы затупили, нажали на эту ссылку и перешли — всё. Считайте, всё потеряно. И ведь все знают, что не стоит садиться в машину к незнакомым, не стоит брать конфеты от незнакомых, не стоит нажимать ссылки от незнакомых людей. Все знают, но по-прежнему нажимают. Почему?
Виной этому Google, может быть. Нажали бы на такую ссылку?
А почему нет? Коротенькая ссылочка, очень удобно, нажимаем. А там спрятан мой скрипт, мой активный XSS. Или вот еще вам пример, QR-код.
А что там? Вот я сфоткал. Думаете, я читаю эту ссылку? Я взял, перешел. Все, скрипт выполнился, мои данные были украдены.
Следующий вид — это пассивный XSS. Давайте представим, что я злоумышленник, я зашел на какой-то сайт и оставил комментарий. Комментарий содержит обычный скрипт, который редиректит на какую-то другую страничку, прихватив ваши cookie. Почему этот тип атаки называется именно пассивным? От меня уже требуются какие-то более глубокие знания, мне нужно понимать, как рендерится страничка, как выполняется скрипт. Это уже более глубокий, серьезный уровень. То есть я работаю непосредственно с сайтом и ищу там уязвимости. Как только я их нахожу, я внедряю этот самый скрипт, и всё, от вас уже больше ничего не требуется. Вы, как обычный пользователь, опять заходите на эту страничку — всё, данные были уже украдены. И вы даже не заметите, что произошло.
Чтобы привести пример, расскажу историю. Совсем недавно на сайте питерского ЕГЭ, на главной страничке, красовалась следующая надпись: «После сдачи ЕГЭ у меня не хватает баллов для поступления на факультет компьютерной безопасности. Придется обеспечить работой тех, кто поступил». На самом деле этот взлом не был связан с XSS. Это быстро пофиксили и через полчаса сайт работал как ни в чем не бывало. Но другой школьник, коллега, еще через полчаса снова взломал сайт. «Сочувствую парню из Питера, которому не хватило баллов, чтобы поступить на факультет компьютерной безопасности. Надеюсь, мне хватит». Этот парень использовал именно ту уязвимость, о которой я рассказал. Он оставил комментарий с редиректом. Как только вы заходили на сайт питерского ЕГЭ, вы сразу же редиректились на эту страничку и читали эту надпись. Шалость? Да. Просто? Очень просто. Можно ли сломать сайт? Легко. Как видите, в основе этих уязвимостей лежит уязвимость именно DOM-дерева. Как мы с ним работаем, что мы туда вводим, может привести к очень интересным последствиям.
Давайте посмотрим на обычный input.
Мы туда можем вводить числа, строки, и ничего не произойдет, всё будет хорошо. Но а если наш текст содержит вот такой вот скрипт:
Мы перезагрузили страничку, перерисовали этот input с параметром. Всё, наш input закрылся, скрипт выполнился. Ладно, кто сейчас вставляет скрипт? Это слишком очевидно, слишком просто. Допустим, я могу вставить iframe.
Произойдет всё то же самое. Input закрылся, iframe загрузился, выполнил какой-то скрипт, который я там написал. Уязвимость.
Еще один пример с картинкой. Я вставляю картинку, в ней — невалидный url и onerror-хук.
Как только картинка пытается загрузиться, конечно, такого url нет, сработает onerror-хук, выполнится мой скрипт, вуаля. Вы скажете, очевидно, что там скрипт. А если я спрячу ту же самую картинку и заинклужу таким образом. Понятно, что там скрипт? Или вот так.
Или вот наши друзья с js-pack.
Alert? Кто его знает, что там происходит в этом коде. Вот серьезно, что он делает? Это обычный alert, который представлен в виде открывающихся и закрывающихся скобок. Что это? Как вообще понять, что это работает?
Это пример одного инпута. Давайте к жизненным примерам. eBay (одна из компаний, которая никак не связана с IT) пару месяцев назад решила продать книжку. Вот такую книжку. Самое интересное, что название у книжки вот такое:
Книжка стоит недорого, 25$. Как только вы заходите на эту страничку на eBay, вы видите alert. Поверьте эта страничка еще актуальна, но баг пофиксили. Сообществу так понравился этот прикол с книжкой, что ее начали продавать в разных странах. Вот вам самый популярный сайт в Швеции по продаже книг. Работает. Вот Германия. Работает.
И много-много разных других примеров. До сих пор не пофиксены все. И мы сейчас говорили про уязвимости одного лишь инпута. Есть примерный список всех уязвимостей атрибутов, тегов. Их много. И по каждой из этих ссылок еще больший список, как можно варьировать ваши данные, как можно обойти ту или иную защиту. Страшно.
Но кто эти злоумышленники? Что они хотят от нашего сайта? Если мы знаем врага в лицо, мы можем что-то с этим делать. Вспомним детство, ведь дети всегда делились на 2 типа. Дети, которые с удовольствием строили замки, и дети, которые с таким же удовольствием эти замки ломали. Ничего не изменилось и во взрослом мире. Мы пишем сайты, кто-то их ломает, просто по приколу. Поверьте, они получают такое же удовольствие от неработающего и сломанного кода, как мы от работающего. Есть люди, которые этим занимаются по фану. А есть специальные сайты, на которых мы можем зарегистрироваться и ломать сайты безнаказанно. Знал бы об этом Сэми. За то, что мы найдем баг, мы можем даже получить какое-то вознаграждение. На больших серьезных сайтах — серьезные деньги. Только третья часть — это именно злоумышленники, которые хотят у нас что-то украсть. А что у наших сайтов можно украсть? Если мы знаем, что это хотят украсть, мы можем это подальше спрятать, правильно? Cookie и то, что воруется в первую очередь, то, что лежит в наших хранилищах. Это могут быть session storage, local storage. С index UDP, поверьте, данные с удовольствием утянут, лишь бы до них добраться. Ладно, cookie сейчас уже мало кто пользуется.
Что еще можно воровать? Пароли. Есть много способов украсть пароль. С обычным скриптом мы можем просто повесить какой-нибудь keyup, keydown на документ и логировать всё, что мы нажимаем. Потом отправляем это всё на какой-то сайт, и там уже машинным образом подбираем. Или, допустим, фишинг, если мы более умные ребята — ставим фейковую форму поверх существующей формы и просто забираем себе submit. Основная цель, которая появилась совсем недавно и, в принципе, и является причиной всплеска этих XSS-атак — это майнинг. Мощности наших компьютеров, мощности наших телефонов растут. Почему бы нам не воровать эти мощности?
Приведу пример из Беларуси. Завод «Луч», один из крупнейших заводов на постсоветском пространстве по производству часов. На их главной страничке самым наглым образом майнились. Насколько наглым, что даже спрашивали: «Вы не против, если мы тут немножко помайним?»
Был скандал, уволили пару человек, сайт вернули в прежний вид. Но сколько вот такого майнинга происходит без нашего ведома? Нас могут ведь не спрашивать об этом. Довольно крупная компания AdGuard, которая занимается исследованиями в области рекламы, блокировщики рекламы, изучала в течение трех недель топ сайтов из списка Alexa. Изучала, изучала и обнаружила, что 220 из первых 100 тысяч майнят. 220 из 100 000 — это 0,22%, капля, согласитесь. Но оказалось, что за 3 недели на этих 220 сайтах было 500 000 000 пользователей. Представьте, что можно было намайнить за это время. В эти 220 сайтов попали самые популярные сайты для взрослых, стриминг и пиратский контент.
Как жить дальше? Вроде и будущего больше нет, всё страшно, всё можно украсть. Даже то, что мы не ожидаем. Кто-то должен нас спасти и защитить. И, в принципе, логичным ответом должны быть фреймворки. Ведь другие разработчики уже подумали об этом. И действительно, читая документацию популярных библиотек и фреймворков, мы можем увидеть, как они развивались в этом направлении, что они делали для этого. Но если б они действительно нас спасали, то следующая моя тема не прозвучала бы как… Уязвимости фреймворка! Как сломать фреймворк? Мы можем вводить данные, как это делалось в инпуты, но поверьте, там у них действительно всё хорошо. Но нам в нашей работе очень часто необходимо вставлять динамический контент, который пришел с сервера, который ввел сам пользователь. Вот этот динамический контент действительно представляет угрозу. Разные фреймворки делают это по-разному. Это могут быть какие-то директивы, могут быть какие-то property… Начнем с AngularJS.
Итак, вот директива ng-bind html на самом деле лежит в библиотеке sanitize.js. Мы вставляем туда стремный кусок html, и эта библиотека по своему листу выпиливает всё стремное: атрибуты, теги — всё, что ей не понравилось.
Оставляет чистый html, который мы можем безопасно вставить. Вроде бы всё должно работать неплохо. А что, если мы забудем в нашем списке какой-нибудь атрибут? Так и случилось. Кто из вас пользовался атрибутом usemap
? Я о нем не знал, пока не прочитал репорты, я им никогда не пользовался. Это атрибут для динамической подгрузки контента по нажатию на картинку. Разработчики о нем тоже забыли в Angular. И до версии 1.5 существовала эта уязвимость. То есть вот этот код с удовольствием вставится и выполнится. Уязвимость.
И не только с этим атрибутом. Вот краткий список уязвимостей до версии 1.5:
Многие из них уже, конечно, пофиксены в более поздних версиях, но всё же это большой список. Я не буду на них останавливаться подробнее, но они есть. Казалось бы, AngularJS, уже legaсy-фреймворк.
Надо посмотреть что-то более новое, стильное, молодежное. Разработчики ведь должны были учесть прошлый опыт. Vue.js. Я начал читать документацию, как они работают в директиве v-html. Они пишут, что вставлять динамический контент как-то стремно, так что вы сами виноваты. А что они сами делают? Да ничего.
Я скачал последнюю версию Vue.js:
И попробовал вставить динамический контент в заданную область. Получилось.
Vue.js никак нам в этом не помогает. Это должен делать сам разработчик. И вот сейчас, если вы пишете на Vue.js и впервые об этом слышите — у вас уязвимое приложение. Поздравляю.
Хорошо, а что насчет React и Angular? За последние 2 года не было открыто ни одной проблемы, связанной с security. Казалось бы, вот оно. Используем React и Angular, и живем себе счастливо. Но вот вам еще статистика. 77% сайтов с этими фреймворками имеют хотя бы одну уязвимость. Откуда берется вся эта дрянь? А вот откуда. Уязвимости в node_modules. Мы сами их скачали, сами выбрали версию, сами добавили свой проект. Добро пожаловать.
Для примера давайте разберем уязвимость в Redux. Redux и React подарили нам отличную фичу, серверный рендеринг. Это была киллер-фича своего времени, работает она примерно следующим образом: нам нужно state, которое мы хотим использовать как initial, засунуть в переменную __PRELOADED_STATE__ в глобальном скоупе, в window. Но перед этим надо ее застрингифаить.
И особое внимание вот к этой строчке.
Что, если мне удалось где-то зарегистрироваться вот с таким именем?
Данные сохранились где-то там на сервере, вроде бы ничего страшного не произошло, но вот тут серверный пререндеринг…
и всё, скрипт снова закрывается, и выполняется мой скрипт. Уязвимость. Да, это уязвимость не в самом Redux, а в подходе Redux, но мы используем этот Redux из node modules. Мы используем то, что было написано в их документации. Вторая проблема node modules — в среднем node_modules отстают от последнего релиза на 1177 дней — это более трех лет! Вдумайтесь: всё, что появилось нового за три года, все эти уязвимости — они все, скорее всего, есть в вашем приложении. Давайте подумаем, что за три года произошло. jQuery снизил свою популярность всего лишь на 35%. Это при том, что появился Angular, AngularJS, React, Vue. И если посмотреть на версии, 79% сайтов используют первую версию jQuery. А в первой версии — вот вам, пожалуйста, открытые уязвимости.
Поэтому во всех сайтах, которые используют старую версию, скорее всего, есть уязвимости. И это не только проблема jQuery, это проблема всех старых клиентских библиотек: Handlebars, Bootstep, D3, JsTree, почти любая библиотека старой версии имеет хотя бы одну уязвимость. Если вы их не обновляли, если у вас сайт довольно legacy, советую вам посмотреть на это.
И давайте обратим внимание еще вот на такие пакеты: babelcli, jquery.js, mangose, gruntcli, D3.js. Думаю, каждый из вас хотя бы раз работал с этими пакетами. А вот так на самом деле называются эти пакеты: babel-cli, jquery, mangoose, grunt-cli, D3. И это огромная проблема, которая появилась последнее время в npm, — зараженные пакеты. Мы, как злоумышленники, работаем непосредственно с npm. Мы ищем какие-то популярные пакеты, делаем какую-то опечатку, заливаем снова в npm, и человек, положившись на свою память, качает mangoose с одним «о», забирает к себе в проект, и, поверьте, вы сразу даже ничего не заметите. Они работают абсолютно одинаково, но в postinstall-куках будут украдены ваши переменные среды. NPM очень сильно обеспокоены этой проблемой. Он ищет эти пакеты, понижает версию, обзывает как «0.0.1-security», поэтому нельзя удалить полностью пакет сейчас, и остается только readme и package.json. Если вы обновляете свои пакеты, вы увидите эту проблему, если нет — ваши переменные среды будут по-прежнему воровать.
Если вы думаете, ну кто так может затупить, то вот вам пример. Я недавно зашел на npm-репозиторий, ввел неправильного mangoose и узнал, что в тот день еще 21 человек затупило. 464 человека за месяц, так что это работает.
Получается, все наши друзья — они же наши враги. Мы все больше данных храним на клиенте, ведь для поддержания оффлайн-режима нам нужно все больше данных. Часто это могут быть какие-то security-данные, их-то и хочется украсть, какую-то аналитику. И данные играют против нас. NPM со своими уязвимостями в каких-то пакетах — угроза нашим приложениям. Серверный рендеринг, пример с Redux, неправильно используем — вот вам уязвимость.
Прогресс тоже не стоит на месте, мощности растут, и потенциально это интересно для наших злоумышленников.
Single page applications — отличный подход в написании приложений, а теперь подумаем, как люди этим пользуются. С утра пришли, загрузили это приложение, и до вечера его не перезагружаем — не за чем. И если где-то была уязвимость, целый день она работает, вы целый день подарили скрипту в SPA. Когда вы разрабатываете SPA, вы должны быть еще более внимательными.
Кэш тоже играет против нас. Мы совершили ошибку, нам отрепортили ее, мы ее пофиксили, но мы настроили неправильно service worker, неправильно настроили работу с кэшем. И в кэше наших пользователей этот скрипт по-прежнему работает, эта уязвимость еще остается какое-то время — тоже проблема.
Кто из вас использовал Service Worker? Всего полтора года назад я рассказывал об этом, как о чем-то новом. Сейчас, если в вашем приложении нет Service Worker, всё, оно не реактивное, не прогрессивное. Теперь он должен быть в каждом приложении.
Расскажу интересную историю. Я начал гуглить и нагуглил своего друга, котика-программиста. Он отлично программирует, мне очень понравилась эта гифка, и я ее по ссылочке вставил в свое приложение. Оно красивенько отрисовалось, и я о нем забыл, пошел дальше работать. Через какое-то время я замечаю, что в приложении, которое я разрабатывал, в котором нет Service Worker, начинает крутиться на этом домене какой-то Worker, причем он крутится постоянно, не завершается. Откуда он взялся? Если мы проанализируем header и response этой картинки, мы можем увидеть две интересные строчки: «Original-Trial» и «Link».
Original-Trial
— это, в принципе, понятно, ключ от Google для использования каких-то экспериментальных технологий. Более того, вы их можете использовать без ведома своих пользователей. Этот proposal называется Foreign Fetch для Service Worker, для оптимизации загрузки шрифтов и картинок. И вот этот Service Worker может установиться из link. Устанавливается и может без вашего ведома делать cross origin request. Что? Я никого не приглашал в свое приложение!
Как это можно использовать: Service Worker так устроен, что если 60 секунд к нему никто не обращается, он затухает. Не полностью, он там в подпроцессе где-то висит, чтобы слушать push-нотификации, но в принципе он не работает, а если мы в этом Service Worker будем слать сами себе сообщение каждые 50 секунд, мы будем работать все время. Или вот еще один подход: мы можем пойти в сеть и вытянуть оттуда точно такую же копию Service Worker, она тоже будет работать, и каждую минуту мы будем что-то новое загружать. Для чего мы можем это использовать? Все правильно, майнинг. Без нашего ведома, в background висит Service Worker, который постоянно работает. Эта уязвимость просуществовала целых 3 версии Chrome. К чему я веду. Технологии развиваются так быстро и так быстро попадают в продакшн без должных проверок, что страшно их использовать.
Как с этим бороться? Как бороться. например, с майнингом? Мы можем поставить какие-то экстеншены, которые будут отслеживать все эти непонятные манипуляции и блокировать их. Но если бы они действительно нас спасали и помогали, следующая глава не звучала бы как «Экстеншены — тоже наши враги». Многие из нас используют блокировщики рекламы. AdBlocker, uBlock, AdGuard, что угодно. А давайте подумаем, как они на самом деле работают. Вы загружаете свой контент, и эти блокировщики лезут по всему вашему HTML и там прячут блоки, которые содержат рекламу, то есть от них в DOM не спрятаться, они могут лазить везде. Дальше они блокируют загрузку с каких-то ресурсов, которые находятся у них в блэклисте, то есть они имеют доступ в response к content security policy-объекту. Дальше они получают доступ к самому response. Они могут прямо из reponse вырвать кусок, содержащий рекламу. То есть, в принципе, экстеншенам доступно всё. А что если эти экстеншены попадут в чьи-то плохие руки?
Вот вам реальная история. Сергей, автор одного из очень популярных XML Viewer-экстеншенов для Chrome (100 000 пользователей):
Он разрешил поделиться этой историей и рассказать ее вам. Однажды к нему пришли люди и предложили неплохие деньги: нужно было модифицировались ссылки, чтобы в них добавлялась специальная метаинформация и можно было узнать, откуда пользователь пришел. Неплохие деньги, в принципе, почему нет, безобидная же модификация… И как только он отдал этим людям код, сразу же начались жалобы, что в наших реквестах появились какие-то сниппеты, которые отслеживают вообще все наши запросы в экстеншенах. Этот пример хорошо иллюстрирует, насколько вы доверяете этим экстеншенам. Ведь они могут украсть всё, что угодно, могут модифицировать всё, что угодно. Конечно, Сергей очень быстро пофиксил эту проблему, откатился обратно, но это очень показательная история. Доверяете ли вы своим экстеншенам, которые есть у вас в вашем приложении, в вашем браузере?
Как же нам бороться с этими уязвимостями? Если экстеншены вас немного напугали, давайте поможем пользователям. Мы можем из своего приложения определить, какие экстеншены стоят в браузере. И если мы точно знаем, что эти экстеншены несут какой-то вред именно для вашего приложения, мы можем их идентифицировать. У каждого экстеншена есть какой-то уникальный id, который мы можем посмотреть прямо на сайте. Далее, мы можем изучить манифест этого экстеншена и увидеть, что есть какие-то открытые ресурсы, которые можно загрузить прямо из вашего приложения.
Допустим, в примере с AdBlock это какой-то кастомный CSS. Мы сформировали url и обычным способом пробуем загрузить, и если этот ресурс грузится, значит, экстеншн стоит. Показываем pop-up. Если у вас там что-то страшное стоит, удаляйте быстрее, а то у вас всё украдут. Помогайте своим пользователям, это вам ничего не стоит, всего три строчки кода.
Немного капитанский совет: если нет необходимости, не вставляйте никогда скрипты от пользователей, не вставляйте даже комментарии, не вставляйте никакие теги или атрибуты, стили, ничего не вставляйте. Работайте с чистым кодом. Если все-таки есть такая необходимость, обезопасьте себя в своих формах, достаточно всего лишь заэскейпить вот эти пять символов, и на вашем клиенте уже, как минимум, не выполнится ни один скрипт. Пять символов — это довольно просто.
Еще нужно санитайзить. Если вы думаете, что, в принципе, я сам могу написать какой-то код, вот примерный список опасных тегов, атрибутов, которые вам нужно засанитайзить.
Их намного больше, о многих мы уже забыли, не знаем, но их больше. Используйте готовые решения, хорошо протестированные. Я бы посоветовал: js-xss, наиболее популярное для санитайза HTML, есть еще DOMPurify, тоже довольно популярная библиотека. Они очень легковесные, у них хорошие тесты, они постоянно обновляются, сообщество поддерживает. И есть serialize-javascript для того, чтобы пройтись по вашему объекту и найти вот эти вкрапления, поэтому c помощью этой библиотеки вы очень легко решите проблему с Redux.
Используйте Content-Security-Policy — специальный хедер в респонсе, его можно использовать как метатег, описывающий правила, по которым будет происходить загрузка ресурсов на вашей страничке. Мы говорим, что хотим грузить с этого домена. Мы грузим скрипты, они грузятся, и если злоумышленник пытается подтянуть что-то чужое со своего домена, это не сработает, появится сообщение об ошибке, мы заблокировали. Я не буду вдаваться в подробности документации, вы сами можете всё прочитать, скажу лишь, что мы можем описывать правила для картинок, для стилей, для работы с iframe, со скриптами — много разных настроек. Я хотел бы остановиться подробнее на новых фишках, которые появились во второй версии. Это nonce
— строка, которая генерируется на стороне сервера и добавляется ко всем вашим скриптам. То есть злоумышленник, вставив вам какой-то скрипт, этого nonce не будет знать, потому что он каждый раз новый, таким образом скрипт тоже не будет выполняться.
Или же второй способ — вы можете просто посчитать хэш-сумму всех ваших inline-скриптов. Зная эту хэш-сумму, если вдруг изменится какой-то скрипт, добавится хоть один пробел или порядок скриптов изменится, всё, скрипты не будут выполняться, вы будете в безопасности.
Получаем примерно такие сообщения об ошибках, development mode. На продакшне это не очень используется, потому что хотелось бы собирать статистику. Content-Security-Policy тоже позволяет собирать репорты. Специальный атрибут report-uri
позволяет указать нам эндпоинт, куда мы будем слать наши отчеты. Если вы сразу боитесь внедрять Content-Security-Policy, то это разумно, потому что вы можете заблокировать какие-то важные скрипты, в продакшне это страшно. Вы можете подключить их в режиме Report-Only, правила будут отрабатывать точно так же, но отчеты будут отправляться примерно в таком виде: что было заблокировано, почему было заблокировано, каким правилом.
Content-Security-Policy спасает вас сверху, то есть не полагайтесь на свой код, обезопасьте себя чуть выше, на уровне браузера. В старых браузерах Content-Security-Policy не поддерживается, поэтому нужно знать другие хедеры, их много, намного больше, чем в этом списке:
То есть, вы не должны хранить cookie, чтобы они не были доступны из документа, передаваться только в реквестах. Для поддержки различных защит в старых браузерах, почитаете потом, помните о них обязательно. Это мы пытаемся обезопасить себя со стороны девелопмента, но что делать в продакшне? Нам нужны какие-то тулы, которые автоматизируют эту защиту.
Есть такой сайт и организация OWASP (Open Web Application Security Project). Если начнете что-то гуглить, вы обязательно туда попадете, именно там собрана вся актуальная информация, статистика обо всех уязвимостях. И казалось бы, где, как не там, искать советы по использованию тулов. И вот, если мы зайдем на этот сайт, мы увидим ссылку на загрузку 2013 года, релиз был 4 года назад. Использую я это? Наверно, нет. Вот еще совет 2006 года. Это, наверно, когда Сэми Камкар взламывал, тогда появился этот тул. То есть там ничего толкового нет. Большинство тулов даже только под виндой работает. Но самое интересное, что просто так тулы нам тоже не нужны. Мы хотим их встроить в CI. Один из самых популярных CI для энтерпрайза — это Jenkins, я буду приводить пример именно к нему. Как бы парадоксально ни звучало, один из действительно стоящих продуктов — это продукт от самой OWASP, они его поддерживают, хотя почему-то не так сильно пиарят, как другие. ZAP Application Proxy работает следующим образом. Мы в своем Jenkins должны сослаться на этот Application Proxy, через который будет работать наше приложение. В режиме теста, этот Proxy может протестировать наше приложение. Как он это сделает? Он просто берет наше приложение и начинает насиловать во все инпуты, вставляет всё подряд, потом собирает отчеты о том, что вставилось, что перезагрузилось, как произошло, и отправляет нам отчеты в разных форматах. Дополнительно нам понадобятся ZAP Plugin и какой-то плагин для создания отчётов, для того, чтобы собирать аналитику в долгосрочном периоде. Строить какие-то тренды и т.д. Из минусов, мы можем понимать, что просто так, особенно фронтенд-разработчику, поставить proxy server в Jenkins CI довольно сложно, и тем более, чтобы через него работало приложение. По перформансу может ударить, решение не самое простое. Но ради интереса вы можете установить его локально и протестировать ваш сайт. Я вот поигрался с holyjs-moscow.ru, сайт, на котором, в принципе, нет ни одного инпута, там нечему ломаться. Но вот есть несколько рекомендаций, что пропущены некоторые хедеры для старых браузеров. То есть если я как-то встроюсь между доменом и этим сайтом, я могу этим воспользоваться для майнинга, почему бы нет.
Следующий тул — Arachni, работает он по схожему принципу. Из Jenkins мы должны сослаться и отдать URL нашей задеплоенной тестовой среды этому Arachni. У Arachni отличный CLI, и он занимается тем же самым, начинает играться с нашим приложением и формирует нам отчеты. Использовать удобнее, весит меньше и внедрять довольно просто. Дополнительно нам понадобятся: Text Finder Plugin для того, чтобы проанализировать результат отчета, пометить build как failed или success, и еще один плагин для анализа в долгосрочном периоде.
Но это всё динамическая проверка. Мы написали код, задеплоили, там проверили, но нам надо как-то понимать, что мы пишем уже что-то не то, то есть мы должны включить какой-то тул для статического анализа. Если мы начнем гуглить, в первых рядах появятся Checkmarx, Veracode, SonarQube в каком-то режиме — дорогие тулы, для энтерпрайза даже дорого, по-моему. Мы живем уже почти в 2018 году, на JavaScript уже мало кто пишет, мы пишем на ECMAScript, мы пишем на TypeScript, мы пишем на JSX, кто-то еще на чем-то пишет. Умеют ли это всё анализировать эти анализаторы? Нет. Стоят дорого, не умеют ничего. А есть ESLint – бесплатное решение для вашего кода. Подключаем Security плагин, добавляем ваш код, работаем. Он нисколько не стоит и в CI встраивается элементарно.
Хорошо, что еще можно проверить? Мы проверили динамически наши приложения, мы проверили статически наш код во время написания, а что с node-модулями? Один из популярных тулов — это Node Security Platform, устанавливается из npm очень просто (npm install nsp
) с помощью команды npm check
– запускается. Есть там уязвимости нет, пробегает по зависимостям, формирует вам отчёт. Работает классно, нисколько не стоит. Как вы понимаете, в CI это встроить очень просто. Работает он интересным образом — анализирует пакеты, которые есть в package.json, и проверяет по базе данных, есть ли уязвимость в этой версии и так далее, ведет зависимости, формирует вам отчет. Работает классно, опенсорс проект, тоже нисколько не стоит, но я бы вам еще посоветовал посмотреть на Snyk, работает абсолютно по такому же принципу. Устанавливаете с npm, запускаете, он пробегает по package.json, ищет уязвимости и на удивление находит их больше. Не знаю, может быть у них разные базы данных. Snyk появился позже, но маркетологи работают намного лучше. Сейчас он тестируется в Lighthouse, вы можете зайти в Canary, он там уже встроен. Вы можете посмотреть, какие уязвимости есть в ваших приложениях, прямо в dev tool, но мы используем очень часто код, который обфусцирован, разобраться, какие там есть зависимости, практически невозможно, только в старых приложениях, поэтому стоит относиться с долей иронии к этой ставке. В Snyk уже встроен GitHub, прямо сейчас вы можете зайти в Dependency graph любого приложения и увидеть зависимости, уязвимости и посмотреть, стоит ли вообще это использовать. Это работает уже прямо сейчас на всех репозиториях.
Итак, что у нас получается в итоге. Не полагайтесь на фреймворки. Фреймворки пишут такие же люди, как и мы. Поверьте, они не всегда умнее нас. И они тоже могут совершать ошибки, как то забытый атрибут в блэклисте или действительно какая-то серьезная ошибка.
Заботьтесь о своих пользователях. Если вы знаете, что какие-то экстеншены могут нанести вам непоправимый вред, найдите их и предупредите пользователя.
Смотрите за своими node_modules, вы сами приглашаете их в свое приложение, поэтому будьте разборчивы в этих гостях. Обязательно используйте Content-Security-Policy, а также тулы для динамического и статического анализа (ESLint).
Минутка рекламы. Как вы, наверное, знаете, мы делаем конференции. Ближайшая конференция про JavaScript — HolyJS 2018 Piter, которая пройдет 17-18 мая 2018 года. Можно туда прийти, послушать доклады (какие доклады там бывают — вы уже увидели в этой статье), вживую пообщаться с практикующими экспертами-джаваскриптерами, практиками и изобретателями разных моднейших технологий. Короче, заходите, мы вас ждём. Билеты можно достать на официальном сайте.
Комментарии (17)
Samouvazhektra
21.02.2018 17:48+3best-practice — не доверять ничему что приходит от пользователя. Куки, заголовки, данные, файлы. Валидировать и санитизить серверной стороной!!! (Валидация на клиенте — только декоративная, чтобы быстро юзеру подсказать что не так, потому что я могу скриптик ручками поправить и отправить в ваше апи любую каку минуя валидации)
VulvarisMagistralis
22.02.2018 07:51best-practice — не доверять ничему что приходит от пользователя
Не только от пользователя.
От коллег, библиотеки/пакеты которых вы используете в своих продуктах
Zibx
22.02.2018 02:59+1Тэги и их аттрибуты надо фильтровать только по белому списку. После чего дофильтровывать возможные значения.
lany
22.02.2018 06:07+2Так вот, за свою шутку Сэми получил 3 года.
Английская википедия говорит
but paying a fine of $20,000 USD, serving three years of probation, working 720 hours of community service
Не находите, что "получил 3 года" — это совсем не то же самое, что "три года условно и 720 часов общественных работ"?
olegchir
22.02.2018 21:32три года условно в России — тоже срок :) Ты не можешь выбрать Путина, не можешь работать на некоторых работах, обязан отмечаться у всяких мерзких товарищей-участковых, итп. Тебя обязательно заставят найти работу (т.е. просто жить фрилансером не получится). А при приёме на работу, пока срок не закончился, тебе нужно писать судимость — и кто тебя таким возьмёт? И ты даже не можешь выехать из России!
Dionisys
22.02.2018 12:36+1По поводу
Vue.js никак нам в этом не помогает. Это должен делать сам разработчик. И вот сейчас, если вы пишете на Vue.js и впервые об этом слышите — у вас уязвимое приложение. Поздравляю.
То v-html применяется специально для вывода сырого html и подразумевается, что это не безопасно давать контроль пользователю над выводимым таким образом значением, вообще v-html используется в крайне редких случаях. Для вывода контента нужно использовать "{{ }}"
ru.vuejs.org/v2/guide/syntax.html#%D0%A1%D1%8B%D1%80%D0%BE%D0%B9-HTML
Nest_aka_Swan
22.02.2018 13:33Dynamically rendering arbitrary HTML on your website can be very dangerous because it can easily lead to XSS vulnerabilities. Only use HTML interpolation on trusted content and never on user-provided content.
andreyverbin
Спасибо за статью. Есть ли где-то готовый набор практик и приемов, которые позволяют исключить или уменьшить вероятность получить XSS уязвимость? Это настолько уже обширная тема, что знать все методы XSS атак прикладному программисту просто невозможно. Нужны best practices, которые позволят избежать уязвимостей не вдаваясь в детали их реализации.
PS: К сожалению, судя по количеству комментариев, сообщество не очень интересуется этой темой :(
xsash
Для сообщества тема скорее избита уже с XSS
Проверять на валидность/Парсить/Очищать все входящие и исходящие данные.
Остальное тут.
mayorovp
Использовать подходы которые исключают вставку "грязных" данных напрямую в разметку.
Например, шаблонизатор Razor (он же ASP.NET WebPages) полностью исключает XSS если не использовать инлайн-скрипты и конструкцию
Html.Raw
.Клиентские фреймворки, которые работают с DOM а не с HTML — тоже безопасны (если, опять-таки, не использовать html-биндинги и инлайн-скрипты).
VulvarisMagistralis
При том что автор привел обратные примеры.
mayorovp
VulvarisMagistralis
Если делать все корректно — у вас все будет хорошо.
Но всегда есть соблазн сэкономить свое время.