-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Description
Summary
A Stored Cross-Site Scripting (XSS) vulnerability exists in SpringBlade's /api/blade-desk/notice/submit endpoint. By leveraging JSON Unicode escape sequences (\uXXXX), an attacker can completely bypass the XssHtmlFilter and inject arbitrary JavaScript into the content field. Since the notice system allows low-privilege users to send messages to high-privilege users (e.g., administrators), this can lead to session hijacking, privilege escalation, and arbitrary API execution.
Affected Versions
- Version: ≤ 4.8.0 (latest version as of disclosure)
- Repository: https://github.com/chillzhuang/SpringBlade
Steps to Reproduce
1. Inject Malicious Payload
Send the following request to the notice submit endpoint. The content field contains an XSS payload encoded with JSON Unicode escapes to bypass XssHtmlFilter \u003cimg src=x onerror=alert(1)\u003e:
2. Trigger XSS
When the victim (e.g., an administrator) views the notice:
The response contains the unescaped HTML, and when rendered in the browser, the JavaScript alert(1) executes automatically without any user interaction (no click required).
Root Cause Analysis
The vulnerability stems from an architectural flaw in how XssHttpServletRequestWrapper applies HTML-based filtering to JSON request bodies.
Processing Flow
Client sends JSON body with \u003c (JSON Unicode for '<')
│
▼
┌─────────────────────────────────┐
│ XssHttpServletRequestWrapper │
│ getInputStream() │
│ ┌───────────────────────────┐ │
│ │ Reads raw body as String │ │
│ │ Passes to XssHtmlFilter │──┼──► XssHtmlFilter.filter() sees \u003c
│ │ │ │ as literal characters '\', 'u', '0'...
│ │ │ │ No '<' or '>' found.
│ │ │ │ All regex patterns fail to match.
│ │ │ │ ──► INPUT PASSES THROUGH UNMODIFIED
│ └───────────────────────────┘ │
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ Jackson JSON Deserializer │
│ Decodes \u003c → '<' │
│ Decodes \u003e → '>' │
│ Result: <img src=x onerror=..> │
└─────────────────────────────────┘
│
▼
Stored in Database as raw HTML
│
▼
Rendered in victim's browser → XSS triggered
Key Code - XssHttpServletRequestWrapper.java
@Override
public ServletInputStream getInputStream() throws IOException {
// ...
// Reads the raw JSON body
String body = WebUtil.getRequestBody(super.getInputStream());
// Applies HTML-based filtering to the entire JSON string
body = xssEncode(body);
// ...
}
private String xssEncode(String input) {
return HTML_FILTER.filter(input); // XssHtmlFilter
}Key Code - XssHtmlFilter.java
The filter relies entirely on regex patterns to detect HTML tags:
private static final Pattern P_TAGS = Pattern.compile("<(.*?)>", 32);
private static final Pattern P_END_TAG = Pattern.compile("^/([a-z0-9]+)", 34);
private static final Pattern P_START_TAG = Pattern.compile("^([a-z0-9]+)(.*?)(/?)$", 34);These patterns only match literal < and > characters. JSON Unicode escapes (\u003c, \u003e) are not < / > at the byte level, so every regex check passes without detection. The filter is fundamentally unaware of JSON encoding.
Why This Works
| Stage | Sees | Action |
|---|---|---|
| XssHtmlFilter | \u003cscript\u003e (literal backslash + u + hex digits) |
No < or > found → passes through |
| Jackson | \u003c |
Decodes to < per JSON spec (RFC 8259) |
| Database | <script>alert(1)</script> |
Stored as raw HTML |
| Browser | <script>alert(1)</script> |
Executes JavaScript |
Remediation
- Do not filter raw JSON bodies as HTML. The
XssHttpServletRequestWrappershould not applyXssHtmlFilterto the raw request body. Instead, sanitize individual field values after JSON deserialization (e.g., via a Jackson deserializer or a Spring@ControllerAdvice). - Use a proven HTML sanitizer. Replace the custom regex-based
XssHtmlFilterwith a well-tested library such as OWASP Java HTML Sanitizer or jsoup Safelist. - Apply output encoding. Ensure all user-supplied content is HTML-encoded at render time on the frontend, regardless of backend filtering.