OlivettaBANK

The Olivetta Bank Security Demo

This site is a sandbox. It contains seven intentional weaknesses, built to demonstrate how Cloudflare's WAF and API Shield protect a modern banking application without a single line of code changing on the bank's side.

Read each story below, try the attack against the unprotected site, then watch your Cloudflare Solutions Engineer flip on the matching control. The attack stops at the edge.

The mobile-app API signs every login token with an ES256 (P-256 ECDSA) key. The matching public key is published at /api/v1/.well-known/jwks.json so Cloudflare API Shield can verify every request at the edge.

Try it with these demo accounts

  • alice / Password123! — owns accounts GL00000001 and GL00000002
  • bob / Bob12345! — owns accounts GL00000003 and GL00000004 (BOLA target)
  • admin / admin — deliberately trivial password for the brute-force demo
Web App

Cross-Site Scripting (XSS)

A criminal posts a link that, when clicked, runs hidden code in your browser and steals your session.

The bank's homepage has a 'Find a branch' search box. The text you type is shown back to you on the same page - but the bank's developers forgot to escape it. An attacker can craft a URL containing JavaScript that runs in any victim's browser the moment they open the link.

How to try it
What stops it
Cloudflare WAF Managed Rules

Cloudflare's Managed Ruleset includes XSS signatures. With the Cloudflare Managed Ruleset enabled, the request is blocked at the edge before it ever reaches Olivetta's server, and the script never executes.

Without protection: the script runs, an alert pops up, and a real attacker could steal cookies, log keystrokes, or impersonate the user. With Cloudflare WAF: 403 Forbidden at the edge.

API

SQL Injection (SQLi)

An attacker tricks the database into returning data they shouldn't be able to see - or worse, dropping the whole table.

On the transactions page, the search box passes your text to the API in a query string. A naive backend would paste that text directly into a SQL query, letting an attacker change what the query does (e.g. dump every customer's transactions). Olivetta's backend happens to use parameterised queries, so the database itself is safe - but the request payload still matches SQLi signatures, which is what Cloudflare blocks.

How to try it
  • Log in, open an account, then try this search
    ' OR 1=1 -- 
  • Or hit the API directly
    curl 'https://banking.homesecurity.rocks/api/v1/accounts/GL00000001/transactions?q=%27%20OR%201%3D1%20--%20' -H 'Authorization: Bearer <token>'
What stops it
Cloudflare WAF Managed Rules

The Managed Ruleset (OWASP + Cloudflare-curated) blocks classic SQL-injection payloads at the edge. Pair it with a custom rate-limit rule on the search parameter to defeat fuzzing.

Without protection: the malicious payload reaches the backend. With Cloudflare WAF: blocked before the application ever sees it.

API

Forged or missing API tokens

An attacker calls the mobile API without logging in - or signs a fake token and pretends to be a customer.

Olivetta's API issues bearer tokens (JWTs) at login. A correctly-built API must verify every token's cryptographic signature on every request - missing tokens, expired tokens, and tokens signed with the wrong key must all be rejected. Cloudflare API Shield can perform this check at the edge using the public key published at /api/v1/.well-known/jwks.json, so forged requests never even reach the bank.

How to try it
  • Call a protected endpoint with no token
    curl -i https://banking.homesecurity.rocks/api/v1/accounts
  • Or with a tampered token (flip the last character)
    TOKEN=$(curl -s .../login ... | jq -r .token)
    curl -i .../api/v1/accounts -H "Authorization: Bearer ${TOKEN%?}X"
  • Inspect the public key the edge uses to verify
    /api/v1/.well-known/jwks.json
    Open: /api/v1/.well-known/jwks.json
What stops it
Cloudflare API Shield - JWT Validation

Upload the public JWK to a Token Configuration in API Shield, then create a JWT validation rule scoped to the API hostname. The edge cryptographically verifies every token's signature, expiry, and issuer before the request reaches origin. Tokens that are missing, expired, tampered, or signed with the wrong key receive a 403.

Without protection: the API only does a best-effort decode and trusts whoever holds a token-shaped string. With API Shield: forged or missing tokens are blocked at the edge with a clear violation reason in Security Events.

API

Broken Object-Level Authorisation (BOLA)

After logging in as yourself, you change a number in the URL and see somebody else's bank account.

Even with a perfectly valid token, GET /api/v1/accounts/{id} returns whatever account you ask for - the API never checks whether the account belongs to you. This is OWASP API Security Top 10 #1, and unlike XSS or SQL injection there is no signature for it: ownership rules live in the application's data model, not in the request payload. Cloudflare's recommended approach is layered: detect the bug, limit the blast radius, and fix the bug.

How to try it
  • Log in as Alice, then ask for one of Bob's accounts
    GET /api/v1/accounts/GL00000003   (Bob's checking)
  • Try the same in the web UI
    /account/GL00000003
    Open: /account/GL00000003
  • Flip the demo "application-side fix" on with a query flag
    GET /api/v1/accounts/GL00000003?fixed=1   →  404 not_found
What stops it
API Shield BOLA Detection + per-session rate limit (JWT sub) + application fix

Three layers: (1) API Shield BOLA Vulnerability Detection labels the endpoint with cf-risk-bola-enumeration when one session requests far more unique IDs than baseline. (2) Volumetric Abuse Detection recommends a per-session rate limit, enforced by counting the JWT 'sub' claim - this scales to millions of accounts because it limits sessions, not IDs. (3) The actual fix is one extra SQL clause in the application that scopes the query to the requesting user. Cloudflare buys the bank time to ship the fix safely.

Without protection: any authenticated user can enumerate every account in the system. With Cloudflare: the endpoint is flagged in Security Overview, attacker sessions are listed with their IPs and JA4 fingerprints, the per-session rate limit cuts the enumeration off after ~20 requests/minute, and the dev team gets a precise pointer to the bug they need to fix.

API

Brute-force login

An attacker hammers the login endpoint with millions of password guesses until one works.

POST /api/v1/login has no in-app rate limit. A botnet can fire thousands of guesses per second from many IPs. The 'admin / admin' demo account exists specifically so you can crack it quickly without a wordlist.

How to try it
  • Run a load tool against /api/v1/login
    hey -n 500 -c 50 -m POST -T application/json \
      -d '{"username":"admin","password":"wrong"}' \
      https://banking.homesecurity.rocks/api/v1/login
What stops it
Cloudflare Rate Limiting + Bot Management

A single rate-limit rule on /api/v1/login (e.g. 10 requests per IP per minute) blocks credential-stuffing tools. Bot Management adds behavioural detection so distributed attacks across many IPs are also caught.

Without protection: thousands of login attempts succeed in seconds. With Rate Limiting: attempts beyond the threshold receive 429 Too Many Requests.

API

Shadow / undocumented endpoints

Internal endpoints that the bank forgot to remove from production. They were never meant to be public.

GET /api/v1/admin/debug and /api/v1/_internal/dbdump exist in the running Worker but are not declared in the public OpenAPI specification. In a real bank this is how customer data ends up in breaches: a developer leaves a debug route in production, an attacker fuzzes the URL space and finds it.

How to try it
  • Hit the undocumented debug route
    curl https://banking.homesecurity.rocks/api/v1/admin/debug
  • Or the internal db dump
    curl https://banking.homesecurity.rocks/api/v1/_internal/dbdump
What stops it
Cloudflare API Shield - Schema Validation

Upload the published OpenAPI schema to API Shield and set the action to 'block'. Any request that does not match a declared endpoint (path, method, parameter types, body shape) is rejected.

Without protection: full user table, including password hashes, returned to anyone who finds the URL. With Schema Validation: the endpoint is unreachable from the public internet.

API

Sensitive data exposure

The API returns full credit-card numbers and CVVs in plain text - a regulatory disaster.

GET /api/v1/cards intentionally returns full Primary Account Numbers (PAN) and CVVs in the response body. A real bank that does this fails PCI-DSS instantly. The point is that data-loss problems are often invisible to a code review: the endpoint looks innocent until you see what it returns.

How to try it
  • Log in and request the cards endpoint
    curl https://banking.homesecurity.rocks/api/v1/cards \
      -H 'Authorization: Bearer <token>'
What stops it
Cloudflare Sensitive Data Detection (SDD)

SDD inspects API responses for patterns such as credit-card numbers, social security numbers, JWTs, and more. It surfaces every endpoint that returns sensitive data, so the security team can flag or block exposure before it becomes a breach.

Without protection: card data leaves the perimeter in plain text. With SDD: the response is flagged in Security Events, and the team can take the endpoint down or mask the data.

Where to find things

💡 Tip for Solutions Engineers

The demo repo ships with a traffic generator (pnpm traffic) that produces a realistic mix of legitimate API calls, WAF/SQLi/XSS attack payloads, shadow-endpoint probes, brute-force logins, BOLA attempts, scanner user-agents, and JWT validation negatives. Run it from your laptop once before flipping protections on (clean baseline), once after (lots of edge blocks), and the Security Events dashboard tells the whole story.

The script prints a filter cheatsheet at the end that maps each Cloudflare Service name to the section of traffic that generated it - perfect for narrating the dashboard tour.