Казалось бы, XSS уязвимостям уже 100 лет в обед: написано огромное количество материала на эту тему, браузеры и инструменты которые мы используем тоже развиваются и добавляются новые уровни защиты. Но тема не перестает быть актуальной, ведь в новой версии Top 10 Web Application Security Risks (правда двухлетней давности) XSS уязвимости по‑прежнему входят в ТОП 10 самых опасных и распространенных уязвимостей (хоть и включены теперь в группу injection), и 21% всех уязвимостей, найденных в web‑приложениях были именно XSS.

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

Первая часть: «XSS атакует! Краткий обзор XSS уязвимостей»

Top 10 Web Application Security Risks публикует открытое сообщество OWASP (The Open Worldwide Application Security Project), в которое входят специалисты по безопасности со всего мира, и занимаются разработкой различных методологий, инструментов, чтобы сделать интернет безопаснее. В новой версии топа (вышла 2 года назад, но на тот момент она не обновлялась уже 5 лет, так что это относительно недавно) XSS уязвимости по‑прежнему входят в топ 10 самых опасных и распространенных уязвимостей (хоть и включены теперь в группу injection). Согласно статистике 21% всех уязвимостей, найденных в web‑приложениях, были именно XSS.

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

Так же это может быть и прибыльной историей, потому что крупные компании часто платят хорошие деньги за найденные в их приложениях уязвимости. Но не стоит сразу начинать бегать по всем сайтам и пытаться закинуть пэйлоад во все возможные инпуты, в надежде, что сработает, и вы сможете стрясти с владельцев кучу денег. Для начала выясните, участвует ли эта компания в какой либо программе по поиску уязвимостей. Ведь если нет, то поиск уязвимостей у этой компании будет считаться преступлением, даже если вы действовали в благих целях. Ну а об использовании уязвимостей в личных целях я вообще молчу.

Где искать XSS уязвимости

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

В большинстве случаев источник XSS — это пользовательский ввод в том или ином виде.

Давайте разберем основные из них.

Поля ввода

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

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

какой чудесный пост! <script>alert(document.cookie)</script>

Отправляем его, и всё, что мы ввели, улетает на сервер и сохраняется в БД. Если в приложении нет должной обработки пользовательского ввода, то уже у другого пользователя, открывшего наш комментарий, запустится этот скрипт.

Или при авторизации в каком‑нибудь сервисе, в поле ввода имени, помимо имени, мы может также добавить скрипт и, в дальнейшем, у каждого пользователя, увидевшего наш ник, он запустится. На самом деле, примеров может быть бесконечное множество: везде, где вы видите поле ввода, данные из которого будут отображаться другим пользователям — потенциальная XSS уязвимость.

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

Query-параметры в URL

Часто бывает так, что для генерации страницы мы используем данные из Query‑параметров. Например в каком‑нибудь поисковике у нас есть возможность поделиться ссылкой на что‑то, что мы искали. Обычно поисковый запрос добавляется в Query‑параметр и выглядит примерно так:

https://demo.owasp-juice.shop/#/search?q=hello world

Кстати, это демо-сайтик от OWASP для демонстрации разного рода уязвимостей, можете полазить, потыкать.

То, что находится в Query‑параметрe «q» добавляется на страницу выдачи. Мы можем поэкспериментировать и попробовать вбить в поиск:

<b>hello world</b>

Видим, что текст стал жирным.

Давайте попробуем вбить в поиск уже знакомый нам пэйлоад:

<script>alert(document.cookie)</script>
https://demo.owasp-juice.shop/#/search?q=<script>alert(document.cookie)<%2Fscript>

Не сработало... современные браузеры не настолько тупые и такой явный пэйлоад не отработают.

Давайте сделаем хитрее и напишем в поиск:

<img src="wrongSrc" onerror="alert(document.cookie)"/>
https://demo.owasp-juice.shop/#/search?q=<img src%3D"wrongSrc" onerror%3D"alert(document.cookie)"%2F>

Ура, сработало! А если вы перейдете по сформированой ссылке, то сами увидите работу этой уязвимости в действии.

Как это сработало? Дело в том, что у тега img, как и у некоторых других, есть метод атрибут onerror, который выполнит весь JS-код, что мы в него передали, если произойдет ошибка загрузки изображения, что собственно и произошло, когда мы передали левую ссылку в src.

Это был пример уже другого типа уязвимости — Reflected XSS. Скрипт злоумышленника, эксплуатирующего такую уязвимость, уже не сохраняется на сервере. Бывают ситуации, когда скрипт даже не покидает пределов браузера.

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

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

Загружаемые файлы

Возможность загружать файлы в вашем веб приложении тоже может стать потенциальным источником XSS уязвимостей. Самые опасные форматы — HTML, XML, JavaScript. Думаю тут нет смысла объяснять, почему злоумышленнику даже не придется сильно напрягаться, чтобы запустить скрипты из таких файлов у жертвы.

Чуть менее очевидные — SVG-файлы. SVG-изображения основаны на базе XML синтакса и добавление, например, тега script:

<svg><script>alert(‘XSS’)</script></svg>

Так же SVG вполне поддерживают многие HTML-атрибуты (точнее XML) и можно написать что-то вроде:

<svg onload="alert('XSS')"> <!-- запустит скрипт после загрузки SVG -->

Помимо onloadатрибута, есть ещё куча других атрибутов, которые могут запустить JS-код, например: onerror (с которым мы уже встречались ранее), onclick, onmousedown, onmouseup и тд.

PDF файлы тоже могут представлять опасность, так как ридеры PDF файлов могут запускать JS. Импакт от такой атаки, конечно, будет не очень велик, потому что файл будет запущен не в окружении веб-приложения, из которого можно было бы стащить какие-то чувствительные данные, но злоумышленник может редиректнуть пользователя на вредоносный ресурс. Правда в Adobe Acrobat Reader вероятность успеха такого трюка ниже, так как он не даст автоматически редиректнуть пользователя, а сначала спросит его мнения на этот счет. Но как в данной ситуации поступят другие ридеры, я не знаю.

Так же для нас могут представлять опасность не только скрипты, вшитые непосредственно в тело файла, но и их метаданные. Если нам в приложении нужно отобразить, например, название загруженного файла, то это тоже может стать проблемой, так как в название файла тоже можно добавить скрипт.

Сторонние библиотеки и фреймворки

Ещё одним источником XSS уязвимостей может стать код, написанный даже не вами.

Сейчас сложно себе представить веб-приложение (да и вообще какое-либо приложение), полностью написанное командой разработки этого приложения. Мы постоянно используем сторонние библиотеки с уже готовыми решениями. Было бы глупо писать свою реализацию шифрования данных (что кстати ещё и очень опасно, так как вряд ли вы сможете написать  достаточно секьюрную систему шифрования, если только вам не дадут на это несколько лет, что вряд ли), писать личный фреймворк, когда есть React, Vue, Angular и другие. В общем, вы меня поняли :)

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

И что же теперь удалять все либы и писать свои костыли? Не обязательно. Просто пользуйтесь проверенными библиотеками, с большим количеством пользователей и регулярно обновляемыми. Ну и, конечно, старайтесь использовать максимально актуальные версии библиотек, так как если она нормально поддерживается и в ней найдут дыру, то скорее всего в скором времени выйдет и баг фикс.

К примеру, Google тоже советует использовать как можно более свежую версию их фреймворка Angular.js. На странице Developer Guide: Security они объясняют, почему и какие уязвимости были в некоторых предыдущих версиях фреймворка (в том числе и XSS).

Как искать XSS уязвимости

По большому счету, у нас есть 2 варианта поиска уязвимостей: ручной и автоматический. Я хочу разобрать только ручной способ, так как инструменты автоматического поиска уязвимостей работают по тем же принципам. Если вы поймете, как искать вручную, то разберетесь с автоматическим способом и без моей помощи.

Глобально, при поиске XSS уязвимостей, наша основная цель — внедрить и запустить скрипт в чужое ПО, он же эксплойт.

Эксплойт — это программа, применяемая для атаки на ПО жертвы, может содержать команды, которые будут выполнены после его внедрения.

На самом деле, с парочкой эксплойтов мы уже успели познакомиться:

  • Самый простой, эксплуатирующий пользовательский ввод и, наверное, самый неэффективный: <script>alert(document.cookie)</script>

  • Чуть хитрее, но от которого всё еще очень легко защититься: <img src="wrongSrc" onerror="alert(document.cookie)"/>

  • Ну и всевозможные программы, вшитые в загружаемые файлы — SVG, PDF, HTML и т.д.

К сожалению (или к счастью), на таких эксплойтах далеко не уедешь и они хорошо подходят разве что для знакомства с темой XSS.

Но к счастью (или к сожалению), в общем доступе есть куча примеров самых различных эксплойтов. Вот, например, огромная памятка для тестировщиков — XSS Filter Evasion Cheat Sheet.

Так же есть XSS полиглоты, выглядят они примерно так:

jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0D%0A//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e

или так:

">><marquee><img src=x onerror=confirm(1)></marquee>" ></plaintext\></|\><plaintext/onmouseover=prompt(1) ><script>prompt(1)</script>@gmail.com<isindex formaction=javascript:alert(/XSS/) type=submit>'-->" ></script><script>alert(1)</script>"><img/id="confirm&lpar; 1)"/alt="/"src="/"onerror=eval(id&%23x29;>'"><img src="http: //i.imgur.com/P8mL8.jpg">

Примеры взяты с GitHub PayloadsAllTheThings/XSS Injection. В целом, очень советую полазить по этому репозиторию, там очень много интересного, связанного с XSS и не только (за полезную ссылку спасибо @Darth_Anjan, оставившего её в комментариях к предыдущей статье).

На этом можно было бы и закончить, сказав, мол, берите эти или любые другие полиглоты, вставляйте их во все возможные поля и всё — вы хакеры.

Но, возможно, вам было бы интересно, как эти наборы символов, больше похожие на регулярки, могут запуститься в чужом ПО и ещё сделать что-то нехорошее.

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

HTML контекст

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

<script>alert("XSS")</script> или <img src="wrongSrc" onerror="alert('XSS')"/>

Правда есть исключения, например, если наш пэйлоад попадает между тегами title, textarea и другими подобными элементами, которые допускают только текст внутри себя. Мы можем это обойти, просто предварительно закрыв эти теги:

</title></textarea><script>alert("XSS")</script>

Теперь наш пэйлоад может запускаться как в обычном HTML контексте, так и не боится попадания внутрь элементов title и textarea.

Примерно по такому же принципу мы можем выйти и из JS контекста (не во всех случаях, конечно), и из CSS, и из контекста комментариев, добавив в наш пэйлоад закрывающие теги для них:

--></sсript></style></title></textarea><script>alert("XSS")</script>

Контекст атрибутов

Часто бывает так, что пользовательский ввод попадает в атрибуты элементов:

<input name="input" value="{{someValue}}">

Первое, что нам нужно сделать, это выйти из атрибута, потому что внутри атрибута наш пэйлоад будет просто строкой и не запустится:

<input name="input" value="<script>alert('XSS')</script>">

Так что мы просто добавляем в наш пэйлоад кавычку:

"<script>alert('XSS')</script>
<input name="input" value=""<script>alert('XSS')</script>">

Но так мы выйдем только из атрибута и останемся внутри тэга, нам нужно ещё закрыть тег:

"><script>alert('XSS')</script>
<input name="input" value=""><script>alert('XSS')</script>">

Есть ещё другой вариант, когда мы выходим из атрибута, но не выходим их тега, а добавляем свой атрибут события, в котором выполняем код:

"autofocus onfocus="alert('XSS')
<input name="input" value=""autofocus onfocus="alert('XSS')">

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

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

'"autofocus onfocus="alert('XSS')

Ещё у нас есть атрибут href, который используется в тегах <a>, <link>, <base>, <area>, а также src в <iframe> и action в <form>. Помимо своей основной функции они еще могут выполнять JS с помощью псевдопротокола JavaScript. Таким образом, если мы поняли, что пользовательский ввод попадает в тег href, мы можем сделать такой пэйлоад:

javascript:alert('XSS');
<a href="javascript:alert('XSS');">Click me</a>

Есть ещё много способов выйти из JavaScript контекста под разные ситуации, где не сработает просто закрывающий тег script, да и не только из JavaScript контекста. Но их так много, что можно написать ещё одну отдельную статью просто с перечислением возможных вариантов.

Кстати, возможно вы заметили, что в одном из полиглот пэйлоаде теги, атрибуты и псевдопротокол JavaScript были написаны в разном регистре типа: jaVasCript, oNcliCk, teXtarEa. Это сделано для обхода некоторых сценариев сонации, когда идёт сравнение напрямую с JavaScript, SVG, onclick ну и так далее. Такая сонация не будет эффективной, а вот браузеру всё равно, для него такое написание валидно.

Собрав все примеры воедино, мы уже можем составить вполне себе неплохой пэйлоад, покрывающий не маленькое количество сценариев:

'"--></scRipt></sTylE></tiTle></tExTarEa><iMg sRc="wrongSrc" onerror="alert('XSS')"/>

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

На данный момент, пожалуй это всё, чем я хотел поделиться. В следующей статье на тему XSS хочу рассказать о том, как защищаться от атак.

Если найдете какие-то неточности или если есть что добавить по тем или иным пунктам, буду рад вашему фидбеку.


Подборка статей из блога:

Подписывайтесь на Телеграм-канал Alfa Digital — там мы постим статьи, новости, опросы, видео с митапов, краткие выжимки из статей, иногда шутим.

Комментарии (3)


  1. NetherNN
    25.04.2023 17:24

    "Не сработало… современные браузеры не настолько тупые и такой явный пэйлоад не отработают."

    А расскажите подробнее про пример с"<script>alert(document.cookie)</script>" в JuiceShop


    Что значит "не тупые"? Тег <script>получается браузеры научились детектить, а другие нет?)


    1. y4ppieflu
      25.04.2023 17:24
      +1

      <script> дописывается на страницу через innerHTML. Согласно спеке он не будет исполнятся, однако event-handler в <img> будет. Трудно сказать, в чем причины такого поведения, но не думаю, что это как-то связано с заботой о безопасности.

      Если бы он добавлялся на страницу не динамически во время загрузки, а статически, он бы прекрасно исполнился браузером.


      1. dunai12 Автор
        25.04.2023 17:24

        Ну да, вы правы, это скорее заслуга не разработчиков браузеров, а разработчиков спецификации. Но как я понимаю этот пункт с поведением тега script при добавлении его через innerHTML в спецификации тоже не сразу появился, а только с выпуском HTML5.