Reflected XSS in /search via unescaped 'q' query parameter
xss · server.js:56-60
f87c6fc2fa by Macintosh1011 on 2026-05-30Summary
The /search endpoint in server.js reads the user-controlled 'q' query parameter and interpolates it directly into an HTML template literal that is returned with Content-Type: text/html. No HTML escaping or output encoding is performed, so any HTML or JavaScript supplied in 'q' is rendered as live markup in the victim's browser. A request such as /search?q=<script>ROOKPOC</script> is reflected verbatim into the response body, confirming script execution in the application's origin.
Impact. An attacker can craft a link to the application that, when clicked by a logged-in user, runs arbitrary JavaScript in that user's browser under the site's origin. This lets the attacker steal session cookies and authentication tokens, perform actions on behalf of the victim (account changes, data exfiltration), deface the page, or pivot to phishing and credential capture. Because the payload only requires a single visit to a crafted URL, it is easy to deliver via email, chat, or social media.
Vulnerable code — server.js
🔴 if (parsed.pathname === "/search") {🔴 const term = q.q || "";🔴 send(res, 200, `<html><body><div>You searched: ${term}</div></body></html>`, "text/html");🔴 return;🔴 }
Working exploit
curl -s 'http://127.0.0.1:4600/search?q=<script>ROOKPOC</script>'
Exploit transcript
HTTP 200 Content-Type: text/html <html><body><div>You searched: <script>ROOKPOC</script></div></body></html>
Confirmed: reflected XSS confirmed — the injected payload reflected unescaped in a text/html response
CVSS v3.1
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N→ 6.1 (medium)Recommended fix
AI-suggested fix — review before applying (derived from analysis of untrusted repo content):
Escape user input before embedding it in HTML. Example patch:
--- a/server.js
+++ b/server.js
@@
if (parsed.pathname === "/search") {
- const term = q.q || "";
- send(res, 200, `<html><body><div>You searched: ${term}</div></body></html>`, "text/html");
+ const term = q.q || "";
+ const escapeHtml = (s) => String(s)
+ .replace(/&/g, "&")
+ .replace(/</g, "<")
+ .replace(/>/g, ">")
+ .replace(/"/g, """)
+ .replace(/'/g, "'");
+ send(res, 200, `<html><body><div>You searched: ${escapeHtml(term)}</div></body></html>`, "text/html");
return;
}
Prefer a vetted templating library (e.g., Handlebars, Eta) that auto-escapes by default, and add a restrictive Content-Security-Policy (e.g., default-src 'self'; script-src 'self') as defense in depth.Ship the fix
Rook found it and proved it. Hand the finding — with its working exploit as a failing test — to Otis to write the fix PR.