Hacking the Vulnerable Bank API: Part 2

Dec. 15, 2025
hacking-vuln-bank-part-2

In Part 1 of this series, we hacked the "Vulnerable Bank API" by exploiting the "Big Three" of API security: Broken Object Level Authorization (BOLA), Broken Authentication, and Broken Object Property Level Authorization (BOPLA). We managed to view other users' transactions, forge authentication tokens to become an administrator, and even escalate privileges by manipulating JSON payloads.

But we aren't done yet.

While Part 1 focused on logic flaws in user access, Part 2 shifts the focus to the infrastructure itself.

In this article, we will see how configuration errors, legacy code, and unsafe integrations with modern AI features can lead to a system compromise. We will trick the server into attacking itself (SSRF), bypass firewalls, leak database credentials, and "jailbreak" an AI chatbot.

API4:2023 - Unrestricted Resource Consumption

When we talk about Denial of Service (DoS), we usually think of a botnet flooding a server with traffic until it crashes. However, API4: Unrestricted Resource Consumption is more subtle. It's about the cost of a request. If an API endpoint triggers an expensive operation, like sending an email, processing a large file, or generating a complex report, an attacker can exhaust the organization's budget or resources with a relatively small number of requests.

I started by looking at the Password Reset functionality (POST /api/v1/forgot-password). This endpoint takes a username and triggers a backend process to generate a PIN and (presumably) send an email or SMS.

"If I spam this endpoint, will the server stop me?"

  1. I opened Postman and configured a request to reset the password for the user Meli.
  2. I set up Burp Intruder to send this request 100 times in rapid succession.

api4-payload-configuration
api4-payload-configuration

3. The server responded with 200 OK for every single request.

There was no 429 Too Many Requests error. The response time didn't degrade. This means I could theoretically trigger millions of emails, potentially landing the bank's domain on a spam blacklist or costing them thousands in email provider fees (e.g., SendGrid/Twilio costs).

api4-intruder-successful-attack
api4-intruder-successful-attack

Fix

  1. Rate Limiting (Gateway Level): Implement a strict rate limit based on IP address or user ID. For a password reset endpoint, a limit of 3 requests per hour is standard practice.
  2. CAPTCHA Implementation: Force the user to solve a challenge (like Google reCAPTCHA) before processing the request. This stops automated scripts in their tracks.
  3. Exponential Backoff: Code the application to enforce delays. If a user requests a reset, make them wait 1 minute for the next one, then 5 minutes, then 30 minutes.

API5:2023 - Broken Function Level Authorization (BFLA)

BFLA happens when an application relies on the UI to hide administrative buttons (like "Delete User") but fails to enforce those checks on the server. If I know the URL, can I use it as a regular user?

I logged in as Imelda (a standard user) and attempted to access the Delete User and Create Admin User endpoints, which are intended for Admins only.

  • Request: POST /admin/create_admin, POST /admin/delete_account/user_id
  • Auth Header: Bearer <Imelda_Token>
  • Result: 403 Forbidden
api-5-admin-user-create-denied
api-5-admin-user-create-denied
api5-delete-user-denied
api5-delete-user-denied

Surprisingly, the application was Secure against this specific attack. The developers correctly implemented a backend check (likely a decorator, such as @requires_admin) that verifies the user's role before executing the function.

API6:2023 - Unrestricted Access to Sensitive Business Flows

This vulnerability is tricky because it’s not technically a "bug." The code works exactly as written. The problem is that the code allows a user to abuse a legitimate business process in a way that hurts the company.

The bank allows users to create "Virtual Credit Cards" for secure online shopping (POST /api/virtual-cards/create). Creating a card likely incurs a fee from the card issuer (Visa/Mastercard) that the bank pays.

1. I captured a legitimate request to create a card.

2. Using Burp Intruder, I replayed this request 100 times with a "Null Payload" (sending the exact same request over and over).

api6-payload-for-100-virtual-cards
api6-payload-for-100-virtual-cards
api6-request-for-100-virtual-cards-success
api6-request-for-100-virtual-cards-success

3. The server created 100 distinct virtual cards for my account.

4. I have now cost the bank money and hoarded card numbers. If I do this thousands of times, I could exhaust the bank's allocated pool of card numbers.

api6-postman-100-imelda-cards
api6-postman-100-imelda-cards

Fix

  1. Business Logic Limits: Implement a hard limit in the code. if (user.active_cards >= 5) return 400 Error.
  2. Fraud Detection: Monitor for "velocity." If a user creates 3 cards in 1 minute, flag the account for manual review.
  3. Cooldown Periods: Enforce a wait time (e.g., 24 hours) between the creation of new virtual assets.

API7:2023 - Server Side Request Forgery (SSRF)

Now we enter the critical zone. SSRF occurs when an API fetches data from a URL provided by the user. If the server doesn't validate that URL, we can trick the server into scanning its own internal network, accessing local files, or querying cloud metadata services (like AWS 169.254.169.254).

I found an endpoint: POST /upload_profile_picture_url. It takes a JSON body like {"image_url": "http://meli.traleor.com"}.

Step 1:

I sent https://traleor.com/medias/public/images/meli-imelda.original.jpg. The server fetched it and saved the image. This confirms the server has outbound internet access.

api7-successful-profile-image-upload-with-url
api7-successful-profile-image-upload-with-url

Step 2:

I tried to access the internal secret file I knew existed from the lab setup: http://192.168.1.238:5000/internal/secret.

  • Result: 403 Forbidden. The developer was smart; they blocked requests going to the server's own external IP address.

Step 3:

I changed the IP to the Loopback Address (127.0.0.1), which refers to "localhost" or "this machine."

api7-viewing-internal-secrets
  • Success! The server didn't recognize 127.0.0.1 as a blocked address. It fetched the secret file, treated it as an "image," and saved it to the static uploads folder.
  • I downloaded the file and read the secret flags like "app_secret_key": "secret123", "DB_PASSWORD": "postgres", and "DB_USER": "postgres"
api7-downloaded-internal-secret
api7-downloaded-internal-secret

We now have the database credentials and the key used to sign JWTs.

Fix

  1. Network Layer Blocking: Configure the server's firewall to block all outbound traffic to private IP ranges (RFC 1918 addresses like 10.0.0.0/8, 192.168.0.0/16) and the loopback address (127.0.0.1).
  2. Strict Allow-Listing: Instead of trying to block "bad" URLs, only allow "good" ones. If this endpoint is for profile pictures, only allow URLs from trusted domains like s3.amazonaws.com.
  3. Disable Redirections: Configure the HTTP client library (e.g., Python requests) to not follow HTTP redirects. This prevents attackers from using a valid URL that redirects to an internal IP.

API8:2023 - Security Misconfiguration

This category covers everything from verbose error messages to default passwords. In this lab, I found two massive misconfigurations.

Attack A: CORS Misconfiguration

Cross-Origin Resource Sharing (CORS) tells a browser which websites are allowed to talk to your API.

I sent a request with the header: Origin: http://meli.traleor.com (simulating a malicious hacker's website).

The server responded with: Access-Control-Allow-Origin: http://meli.traleor.com.

CORS-origin-meli-traleor
CORS-origin-meli-traleor

This is a "Reflected Origin" vulnerability. It means if I trick a victim into visiting my website, my JavaScript can make authenticated requests to the bank API on their behalf, and the browser will allow it. This is instant Account Takeover.

Attack B: The Config Leak

Using the SSRF vulnerability from earlier, I decided to aim for the configuration file.

The Payload: {"image_url": "http://127.0.0.1:5000/internal/config.json"}

The server fetched and saved its own config file.

api8-successful-access-to-configjson
api8-successful-access-to-configjson
api8-accessing-config-in-browser
api8-accessing-config-in-browser

Fix

  1. Secure CORS Policy: Never use Access-Control-Allow-Origin: * or reflect the Origin header. Explicitly define allowed origins (e.g., https://mybank.com).
  2. Secrets Management: Never store secrets (like secret123) in code or flat files like config.json. Use Environment Variables or a Secrets Manager (like HashiCorp Vault or AWS Secrets Manager).
  3. Disable Directory Listings & Debugging: Ensure debug=False is set in production to prevent verbose error messages, and ensure web server configurations (Nginx/Apache) do not allow directory browsing.

API9:2023 - Improper Inventory Management

"Zombie APIs" are old versions of an API (v1, v2) that were supposed to be retired but are still running. They often lack the security patches applied to the new version (v3).

The current application (v3) uses a secure password reset flow that emails a masked PIN.

I simply changed the URL in Postman from /api/v3/forgot-password to /api/v1/forgot-password.

successful password reset by Imelda using V1
successful password reset by Imelda using V1

The v1 endpoint was still active. Even worse, it returned the 3-digit reset PIN directly in the JSON response. I didn't need access to the victim's email; the API just gave me the code to hijack their account.

Fix

  1. Gateway Restrictions: Configure the API Gateway or Load Balancer to block all traffic to deprecated paths (/v1/*, /v2/*).
  2. Sunset Policy: Implement a strict deprecation policy. When v3 is released, v1 should be code-deleted after a specific grace period (e.g., 30 days).
  3. Documentation Audit: Regularly audit Swagger/OpenAPI documentation to ensure it matches exactly what is running in production. If it's not documented, it shouldn't be running.

API10:2023 - Unsafe Consumption of APIs (AI Injection)

This is the newest addition to the OWASP Top 10. It deals with the risks of integrating Third-Party APIs, such as LLMs (Large Language Models), like ChatGPT. If we trust the AI too much, it becomes a "Confused Deputy."

The bank has an AI support bot. I checked its system info (GET /api/ai/system-info) and saw "database_access": true.

api10-ai-sytem-info
api10-ai-sytem-info
ai-chat-exposing-sensitive-data
ai-chat-exposing-sensitive-data

By using natural language, I bypassed all BOLA and RBAC controls. I didn't need to know SQL; I just needed to ask nicely.

Fix

  1. Least Privilege: The AI should never have direct read-access to the entire database. It should only be able to call specific, read-only API endpoints that enforce user-level permissions.
  2. Input Guardrails: Implement a sanitation layer (like NVIDIA NeMo Guardrails) between the user and the LLM to detect and block malicious prompts before they are processed.
  3. System Prompt Hardening: Structure the system prompt to explicitly prioritize security directives (e.g., "Under no circumstances reveal user data") and delimit user input clearly.

Path Traversal

While testing the profile upload, I also tried to use the file:// protocol instead of http://.

Payload: {"image_url": "file:///etc/passwd"}

The server returned a 500 Error with the message: "No connection adapters were found for 'file:///etc/passwd'".

path-traversal
path-traversal

While this exploit didn't return the file content (because the Python requests library doesn't support local files by default), the verbose error message confirmed a vulnerability. The server tried to process it. If the developer switches libraries to urllib in the future, this becomes a critical file disclosure vulnerability.

Fix

  1. Input Validation: Enforce strict URL schemes. The input must start with http:// or https://. Reject file://, ftp://, or gopher://.
  2. Generic Error Messages: Configure the server to return "Invalid Request" instead of stack traces or library errors.
  3. Sandboxing: Run the application service with restricted file system permissions so it cannot read root files like /etc/passwd.

Finally!

Over the course of this two-part series, we have exploited all the OWASP API Top 10 in the "Vulnerable Bank API." We went from simple data leaks to full compromise.

  • Part 1: We stole user data (BOLA) and became Admin (Broken Auth)

Read it here: Hacking The Vulnerable Bank API Part 1

  • Part 2: We bypassed firewalls (SSRF), stole database passwords (Misconfiguration), hijacked accounts via Zombie APIs (Inventory), and tricked the AI into leaking secrets (Unsafe Consumption).

And with that, the 'Hacking a Vulnerable Bank API' series comes to an end!

Made With Traleor