$ cat writeup.md…
$ cat writeup.md…
hackthebox
Task: Fix a GraphQL batching vulnerability that allows PIN brute-force bypass of rate limiting. Solution: Use the online code editor API to read index.js, change allowBatchedHttpRequests from true to false in Apollo Server config, save the patched file, restart the server, and verify the fix to receive the flag.
"With reservoirs sealed and cities teetering on thirst, our heroes storm the HydroAdmin control room to reopen valves and restore the flow of water."
Target: 83.136.249.164:45091
Discovered an online code editor called "HTB Editor" at the target with the following API endpoints:
/api/directory - list files/api/file?path=X - read file content/api/create-file - create new file/api/rename - rename files/api/restart - restart the server/api/verify - verify if vulnerability is patchedKey files discovered:
index.js - Main server with Apollo GraphQL server configured with vulnerable batching:
const server = new ApolloServer({ ...armor.protect(), introspection: false, typeDefs, allowBatchedHttpRequests: true, // VULNERABLE! resolvers });
models/ControlPin.js - PIN generation (4-digit, 1000-9999) and verification logic
schema/resolvers.js - GraphQL resolvers including PIN verification
exploit/solver.py - A script showing how to exploit GraphQL batching to brute-force PINs
The GraphQL server had allowBatchedHttpRequests: true which allows:
This challenge required PATCHING the vulnerability, not exploiting it!
The /api/verify endpoint checked if the vulnerability was fixed.
allowBatchedHttpRequests: true to allowBatchedHttpRequests: false#!/usr/bin/env python3 """ Hydroadmin - GraphQL Batching Patch Challenge HackTheBox Challenge """ import requests BASE_URL = "http://83.136.249.164:45091" # Step 1: Rename original file to backup print("[*] Renaming index.js to index.js.bak...") resp = requests.post(f"{BASE_URL}/api/rename", json={ "oldPath": "index.js", "newPath": "index.js.bak" }) print(f" Response: {resp.json()}") # Step 2: Read the backup file print("[*] Reading index.js.bak...") resp = requests.get(f"{BASE_URL}/api/file?path=index.js.bak") content = resp.json()['content'] print(f" Got {len(content)} bytes") # Step 3: Patch the vulnerability print("[*] Patching allowBatchedHttpRequests...") patched = content.replace( 'allowBatchedHttpRequests: true', 'allowBatchedHttpRequests: false' ) # Step 4: Create patched file print("[*] Creating patched index.js...") resp = requests.post(f"{BASE_URL}/api/create-file", json={ "path": "index.js", "content": patched }) print(f" Response: {resp.json()}") # Step 5: Restart server print("[*] Restarting server...") resp = requests.post(f"{BASE_URL}/api/restart") print(f" Response: {resp.json()}") # Step 6: Wait and verify import time print("[*] Waiting for server restart...") time.sleep(2) print("[*] Verifying patch...") resp = requests.get(f"{BASE_URL}/api/verify") result = resp.json() print(f" Result: {result}") if 'flag' in result: print(f"\n[+] FLAG: {result['flag']}")
Vulnerable (original):
const server = new ApolloServer({ ...armor.protect(), introspection: false, typeDefs, allowBatchedHttpRequests: true, // VULNERABLE resolvers });
Fixed (patched):
const server = new ApolloServer({ ...armor.protect(), introspection: false, typeDefs, allowBatchedHttpRequests: false, // FIXED resolvers });
GraphQL Batching Bypass: GraphQL batching can bypass rate limiting - always set allowBatchedHttpRequests: false in production Apollo servers
Defense in Depth: Even with @escape.tech/graphql-armor library, batching was still enabled manually - security libraries don't protect against misconfigurations
Patch Challenges: Sometimes CTF challenges require fixing vulnerabilities, not just exploiting them - read the challenge description carefully
Code Editor Attack Surface: Online code editors with file manipulation APIs (create, rename, restart) can be powerful attack vectors
PIN Bruteforce Math:
$ cat /etc/motd
Liked this one?
Pro unlocks every complete writeup and expanded API access. $9/mo.
$ cat pricing.md$ grep --similar