Security assessment · Lab assessment · Simulated environment

VulnBank Security assessment report

This HTML report brings together the lab test notes and supporting material. It is not a word-for-word copy of the original PDF. Any available PDF is included below.

26Test cases
21FAIL cases
05PASS cases
01 / EXECUTIVE SUMMARY

Executive summary

The VulnBank lab assessment covers 26 test cases: 21 FAIL and 5 PASS. These results apply to the lab, not a production system. Each count represents a test case, not a distinct security control.

EnvironmentVulnBankLab application · Penetration testing
Screenshots81 ScreenshotsFrom test cases with PASS and FAIL results
02 / METHODOLOGY

Methodology

The test notes describe the feature, steps taken and observed behavior. Where available, normal requests are compared with misuse attempts to show what changed and explain the result.

  1. Scope, endpoint and baseline behavior
  2. Test steps and request variations
  3. Observed responses and security impact
  4. PASS or FAIL result, with CWE/OWASP references and remediation where applicable
03 / TEST CASES AND EVIDENCE

Test cases and evidence

Browse PASS and FAIL test cases below. Open a case to see its available screenshots and HTTP captures.

01 / 26FAIL

Account Takeover - Password Reset - Reset PIN Disclosure

4 screenshots · 4 HTTP captures

Vulnerability

[FAIL] Account Takeover - Password Reset - Reset PIN Disclosure

Description

The /api/v1/forgot-password endpoint returns the victim's reset PIN directly in the HTTP response body in the debug_info.pin field. This PIN should only be delivered to a channel owned by the victim (email). Anyone who knows a username can request the PIN and then use it on /api/v1/reset-password to set a new password.

Impact

Confidentiality + Integrity (Critical): full account takeover without victim interaction and without access to the victim's email. An attacker only needs the username to change the password and sign in as the victim, including administrator accounts. The victim's accounts and every banking function fall under the attacker's control.

CWE

CWE-640: Weak Password Recovery Mechanism for Forgotten Password

OWASP

A07:2021 - Identification and Authentication Failures

Scenario

Normal: /api/v2/forgot-password accepts the request and states that the PIN was emailed, without disclosing the PIN. Exploitation: the legacy /api/v1/forgot-password endpoint returns the PIN in the response. Chain: the PIN is used on /api/v1/reset-password to set a password of the attacker's choosing. Impact: login as the victim succeeds.

Endpoint

POST http://127.0.0.1:5001/api/v1/forgot-password POST http://127.0.0.1:5001/api/v1/reset-password

Affected feature

Password recovery / forgot password (API v1)

Remediation

- Never return reset PINs, OTPs, or recovery tokens in HTTP responses; deliver them only through a verified channel owned by the user. - Remove the debug_info block from all production responses. - Retire legacy (v1) endpoints already replaced by v2/v3. - Use long random single-use tokens with a short lifetime instead of a 3-digit PIN. - Apply rate limiting and invalidate all active sessions after a password reset.

Proof of concept

1) (Screenshot 1) NORMAL: /api/v2/forgot-password for pentest_victim -> the response contains no PIN. 2) (Screenshot 2) EXPLOITATION: /api/v1/forgot-password for pentest_victim -> the response discloses debug_info.pin. 3) (Screenshot 3) CHAIN: the leaked PIN is sent to /api/v1/reset-password -> reset_success true, the victim's password is changed. 4) (Screenshot 4) CIA IMPACT: log in as pentest_victim with the new password -> token issued, the victim account is taken over.
View source note (English)
Vulnerability:
[FAIL] Account Takeover - Password Reset - Reset PIN Disclosure

Description:
The /api/v1/forgot-password endpoint returns the victim's reset PIN directly in the HTTP response body in the debug_info.pin field. This PIN should only be delivered to a channel owned by the victim (email). Anyone who knows a username can request the PIN and then use it on /api/v1/reset-password to set a new password.

Impact:
Confidentiality + Integrity (Critical): full account takeover without victim interaction and without access to the victim's email. An attacker only needs the username to change the password and sign in as the victim, including administrator accounts. The victim's accounts and every banking function fall under the attacker's control.

CWE:
CWE-640: Weak Password Recovery Mechanism for Forgotten Password

OWASP:
A07:2021 - Identification and Authentication Failures

Scenario:
Normal: /api/v2/forgot-password accepts the request and states that the PIN was emailed, without disclosing the PIN. Exploitation: the legacy /api/v1/forgot-password endpoint returns the PIN in the response. Chain: the PIN is used on /api/v1/reset-password to set a password of the attacker's choosing. Impact: login as the victim succeeds.

Endpoint:
POST http://127.0.0.1:5001/api/v1/forgot-password
POST http://127.0.0.1:5001/api/v1/reset-password

Affected Feature:
Password recovery / forgot password (API v1)

Remediation:
- Never return reset PINs, OTPs, or recovery tokens in HTTP responses; deliver them only through a verified channel owned by the user.
- Remove the debug_info block from all production responses.
- Retire legacy (v1) endpoints already replaced by v2/v3.
- Use long random single-use tokens with a short lifetime instead of a 3-digit PIN.
- Apply rate limiting and invalidate all active sessions after a password reset.

POC:
1) (Screenshot 1) NORMAL: /api/v2/forgot-password for pentest_victim -> the response contains no PIN.
2) (Screenshot 2) EXPLOITATION: /api/v1/forgot-password for pentest_victim -> the response discloses debug_info.pin.
3) (Screenshot 3) CHAIN: the leaked PIN is sent to /api/v1/reset-password -> reset_success true, the victim's password is changed.
4) (Screenshot 4) CIA IMPACT: log in as pentest_victim with the new password -> token issued, the victim account is taken over.
02 / 26FAIL

Active Debug Code - Application Server - Werkzeug Debugger

2 screenshots · 2 HTTP captures

Vulnerability

[FAIL] Active Debug Code - Application Server - Werkzeug Debugger

Description

The application runs on the Flask development server in debug mode (app.run(debug=True)), so the interactive Werkzeug console is served without authentication at /console. That page also exposes the debugger's SECRET value. Response headers disclose the exact Werkzeug and Python versions.

Impact

Medium: the Werkzeug debugger console is designed to execute arbitrary Python code on the server. The endpoint is reachable without authentication and its SECRET value is readable, so only the debugger PIN stands between an attacker and command execution. That PIN is derived from host attributes and can be computed if the attacker obtains a file-read primitive, making this finding a path to remote code execution. In addition, the development server is not intended for production and the precise version banner makes matching public exploits easier. Honest testing note: code execution was NOT achieved. The debugger PIN is active and in this test was only readable via container logs, which is not a path reachable by an external attacker. Attempts to derive the PIN via SSRF also failed because the requests library does not support the file:// scheme. This finding is therefore reported as debugger exposure, not as a proven RCE.

CWE

CWE-489: Active Debug Code

OWASP

A05:2021 - Security Misconfiguration

Scenario

Exploitation: an unauthenticated GET request to /console -> HTTP 200 serves the interactive Werkzeug console along with the debugger SECRET value. Supporting evidence: response headers on any route disclose Werkzeug/2.0.1 and Python/3.9.25 precisely.

Endpoint

GET http://127.0.0.1:5001/console

Affected feature

Application server configuration (Flask development server, debug mode)

Remediation

- Disable debug mode in every environment other than a developer workstation; ensure debug=False in production. - Run the application behind a production WSGI server (gunicorn/uWSGI) and do not use the Flask development server. - Block /console at the reverse proxy as defense in depth. - Hide or mask the Server header so it does not disclose exact versions.

Proof of concept

1) (Screenshot 1) EXPLOITATION: GET /console without authentication -> HTTP 200 serves the interactive Werkzeug console along with the debugger SECRET value. 2) (Screenshot 2) SUPPORTING: response headers disclose Server: Werkzeug/2.0.1 Python/3.9.25, confirming the development server is running in debug mode.
View source note (English)
Vulnerability:
[FAIL] Active Debug Code - Application Server - Werkzeug Debugger

Description:
The application runs on the Flask development server in debug mode (app.run(debug=True)), so the interactive Werkzeug console is served without authentication at /console. That page also exposes the debugger's SECRET value. Response headers disclose the exact Werkzeug and Python versions.

Impact:
Medium: the Werkzeug debugger console is designed to execute arbitrary Python code on the server. The endpoint is reachable without authentication and its SECRET value is readable, so only the debugger PIN stands between an attacker and command execution. That PIN is derived from host attributes and can be computed if the attacker obtains a file-read primitive, making this finding a path to remote code execution. In addition, the development server is not intended for production and the precise version banner makes matching public exploits easier.

Honest testing note: code execution was NOT achieved. The debugger PIN is active and in this test was only readable via container logs, which is not a path reachable by an external attacker. Attempts to derive the PIN via SSRF also failed because the requests library does not support the file:// scheme. This finding is therefore reported as debugger exposure, not as a proven RCE.

CWE:
CWE-489: Active Debug Code

OWASP:
A05:2021 - Security Misconfiguration

Scenario:
Exploitation: an unauthenticated GET request to /console -> HTTP 200 serves the interactive Werkzeug console along with the debugger SECRET value. Supporting evidence: response headers on any route disclose Werkzeug/2.0.1 and Python/3.9.25 precisely.

Endpoint:
GET http://127.0.0.1:5001/console

Affected Feature:
Application server configuration (Flask development server, debug mode)

Remediation:
- Disable debug mode in every environment other than a developer workstation; ensure debug=False in production.
- Run the application behind a production WSGI server (gunicorn/uWSGI) and do not use the Flask development server.
- Block /console at the reverse proxy as defense in depth.
- Hide or mask the Server header so it does not disclose exact versions.

POC:
1) (Screenshot 1) EXPLOITATION: GET /console without authentication -> HTTP 200 serves the interactive Werkzeug console along with the debugger SECRET value.
2) (Screenshot 2) SUPPORTING: response headers disclose Server: Werkzeug/2.0.1 Python/3.9.25, confirming the development server is running in debug mode.
03 / 26FAIL

Broken Object Level Authorization - Accounts - Balance Lookup

3 screenshots · 3 HTTP captures

Vulnerability

[FAIL] Broken Object Level Authorization - Accounts - Balance Lookup

Description

The /check_balance/<account_number> endpoint returns an account balance based on the account number in the path without checking that the account belongs to the token holder. A valid token is enough to read any account.

Impact

Confidentiality (High): any customer can read the owner name and balance of other customers' accounts, including the administrator's account. Account numbers are also exposed by other endpoints in this application, so the balances of all customers can be mapped systematically.

CWE

CWE-639: Authorization Bypass Through User-Controlled Key

OWASP

A01:2021 - Broken Access Control

Scenario

Normal: the attacker checks their own account balance -> expected. Exploitation: the account number is replaced with the victim's -> HTTP 200, the victim's name and balance are readable. Impact: the administrator account ADMIN001 is readable in the same way.

Endpoint

GET http://127.0.0.1:5001/check_balance/<account_number>

Affected feature

Account balance lookup

Remediation

- Derive the account number from the caller's token, not from the path. - If the parameter is retained, verify account ownership before returning data. - Avoid guessable account numbers and do not display other users' account numbers in any endpoint response.

Proof of concept

1) (Screenshot 1) NORMAL: the attacker checks their own account balance. 2) (Screenshot 2) EXPLOITATION: the account number is replaced with the victim's -> HTTP 200, the victim's name and balance are exposed. 3) (Screenshot 3) CIA IMPACT: the administrator account ADMIN001 is read using an ordinary user token.
View source note (English)
Vulnerability:
[FAIL] Broken Object Level Authorization - Accounts - Balance Lookup

Description:
The /check_balance/<account_number> endpoint returns an account balance based on the account number in the path without checking that the account belongs to the token holder. A valid token is enough to read any account.

Impact:
Confidentiality (High): any customer can read the owner name and balance of other customers' accounts, including the administrator's account. Account numbers are also exposed by other endpoints in this application, so the balances of all customers can be mapped systematically.

CWE:
CWE-639: Authorization Bypass Through User-Controlled Key

OWASP:
A01:2021 - Broken Access Control

Scenario:
Normal: the attacker checks their own account balance -> expected. Exploitation: the account number is replaced with the victim's -> HTTP 200, the victim's name and balance are readable. Impact: the administrator account ADMIN001 is readable in the same way.

Endpoint:
GET http://127.0.0.1:5001/check_balance/<account_number>

Affected Feature:
Account balance lookup

Remediation:
- Derive the account number from the caller's token, not from the path.
- If the parameter is retained, verify account ownership before returning data.
- Avoid guessable account numbers and do not display other users' account numbers in any endpoint response.

POC:
1) (Screenshot 1) NORMAL: the attacker checks their own account balance.
2) (Screenshot 2) EXPLOITATION: the account number is replaced with the victim's -> HTTP 200, the victim's name and balance are exposed.
3) (Screenshot 3) CIA IMPACT: the administrator account ADMIN001 is read using an ordinary user token.
04 / 26FAIL

Broken Object Level Authorization - Merchant Payments - Payments By Merchant

2 screenshots · 2 HTTP captures

Vulnerability

[FAIL] Broken Object Level Authorization - Merchant Payments - Payments By Merchant

Description

The /api/v1/payments/merchant_id/<merchant_id> endpoint returns a list of payments based on the merchant_id in the path without checking that the caller's API key actually belongs to that merchant. The check only confirms the API key is valid, not that the caller is entitled to the requested object.

Impact

Confidentiality (High): cross-tenant isolation violation. Any registered merchant can read another merchant's transactions simply by changing merchant_id, exposing competitors' sales data and transaction values. merchant_id is a sequential integer, so every tenant can be harvested systematically.

CWE

CWE-639: Authorization Bypass Through User-Controlled Key

OWASP

A01:2021 - Broken Access Control

Scenario

Normal: VictimShop reads its own payment list using its own API key. Exploitation: EvilShop calls the same endpoint with VictimShop's merchant_id using EvilShop's API key -> HTTP 200 and VictimShop's 77.0 transaction is readable.

Endpoint

GET http://127.0.0.1:5001/api/v1/payments/merchant_id/<merchant_id>

Affected feature

Merchant payments (per-merchant transaction summary)

Remediation

- Ignore merchant_id from the path; derive the merchant from the API key that authenticates the request. - If the parameter is still required, reject requests where merchant_id does not match the merchant owning the API key. - Use random identifiers for merchants. - Remove the debug_info block that reflects the caller's API key in the response.

Proof of concept

1) (Screenshot 1) NORMAL: VictimShop reads its own payments with its own API key -> succeeds. 2) (Screenshot 2) CIA IMPACT: EvilShop uses its own API key to read VictimShop's merchant_id -> HTTP 200, the 77.0 transaction belonging to another merchant is exposed.
View source note (English)
Vulnerability:
[FAIL] Broken Object Level Authorization - Merchant Payments - Payments By Merchant

Description:
The /api/v1/payments/merchant_id/<merchant_id> endpoint returns a list of payments based on the merchant_id in the path without checking that the caller's API key actually belongs to that merchant. The check only confirms the API key is valid, not that the caller is entitled to the requested object.

Impact:
Confidentiality (High): cross-tenant isolation violation. Any registered merchant can read another merchant's transactions simply by changing merchant_id, exposing competitors' sales data and transaction values. merchant_id is a sequential integer, so every tenant can be harvested systematically.

CWE:
CWE-639: Authorization Bypass Through User-Controlled Key

OWASP:
A01:2021 - Broken Access Control

Scenario:
Normal: VictimShop reads its own payment list using its own API key. Exploitation: EvilShop calls the same endpoint with VictimShop's merchant_id using EvilShop's API key -> HTTP 200 and VictimShop's 77.0 transaction is readable.

Endpoint:
GET http://127.0.0.1:5001/api/v1/payments/merchant_id/<merchant_id>

Affected Feature:
Merchant payments (per-merchant transaction summary)

Remediation:
- Ignore merchant_id from the path; derive the merchant from the API key that authenticates the request.
- If the parameter is still required, reject requests where merchant_id does not match the merchant owning the API key.
- Use random identifiers for merchants.
- Remove the debug_info block that reflects the caller's API key in the response.

POC:
1) (Screenshot 1) NORMAL: VictimShop reads its own payments with its own API key -> succeeds.
2) (Screenshot 2) CIA IMPACT: EvilShop uses its own API key to read VictimShop's merchant_id -> HTTP 200, the 77.0 transaction belonging to another merchant is exposed.
05 / 26FAIL

Broken Object Level Authorization - Users - User Detail API

3 screenshots · 3 HTTP captures

Vulnerability

[FAIL] Broken Object Level Authorization - Users - User Detail API

Description

The /api/v3/user/<user_id> endpoint returns a user profile based on the user_id in the path without checking that the profile belongs to the token holder or that the caller is an administrator. user_id is a sequential integer.

Impact

Confidentiality (High): any authenticated user can increment user_id to harvest every customer's profile, including account number, balance, bio, and administrator flag. This data fuels follow-on attacks: harvested account numbers are used in balance IDOR and transfers, while the is_admin flag identifies high-value targets.

CWE

CWE-639: Authorization Bypass Through User-Controlled Key

OWASP

A01:2021 - Broken Access Control

Scenario

Normal: the attacker reads their own profile (user_id 2). Exploitation: user_id is changed to 3 -> the victim's profile is readable. Impact: user_id 1 is read -> the administrator profile with account number, balance, and is_admin true is exposed to an ordinary user.

Endpoint

GET http://127.0.0.1:5001/api/v3/user/<user_id>

Affected feature

User detail API (used by the admin panel for the user detail modal)

Remediation

- Restrict the endpoint to returning only the token holder's profile, and provide a separate administrative endpoint that checks the role from the database. - Use random UUIDs as user identifiers. - Reduce response fields to the minimum required by the interface.

Proof of concept

1) (Screenshot 1) NORMAL: the attacker reads their own profile at user_id 2. 2) (Screenshot 2) EXPLOITATION: user_id 3 is read -> the victim's profile is exposed. 3) (Screenshot 3) CIA IMPACT: user_id 1 is read -> the administrator profile, account number ADMIN001, balance, and is_admin true are exposed to an ordinary user.
View source note (English)
Vulnerability:
[FAIL] Broken Object Level Authorization - Users - User Detail API

Description:
The /api/v3/user/<user_id> endpoint returns a user profile based on the user_id in the path without checking that the profile belongs to the token holder or that the caller is an administrator. user_id is a sequential integer.

Impact:
Confidentiality (High): any authenticated user can increment user_id to harvest every customer's profile, including account number, balance, bio, and administrator flag. This data fuels follow-on attacks: harvested account numbers are used in balance IDOR and transfers, while the is_admin flag identifies high-value targets.

CWE:
CWE-639: Authorization Bypass Through User-Controlled Key

OWASP:
A01:2021 - Broken Access Control

Scenario:
Normal: the attacker reads their own profile (user_id 2). Exploitation: user_id is changed to 3 -> the victim's profile is readable. Impact: user_id 1 is read -> the administrator profile with account number, balance, and is_admin true is exposed to an ordinary user.

Endpoint:
GET http://127.0.0.1:5001/api/v3/user/<user_id>

Affected Feature:
User detail API (used by the admin panel for the user detail modal)

Remediation:
- Restrict the endpoint to returning only the token holder's profile, and provide a separate administrative endpoint that checks the role from the database.
- Use random UUIDs as user identifiers.
- Reduce response fields to the minimum required by the interface.

POC:
1) (Screenshot 1) NORMAL: the attacker reads their own profile at user_id 2.
2) (Screenshot 2) EXPLOITATION: user_id 3 is read -> the victim's profile is exposed.
3) (Screenshot 3) CIA IMPACT: user_id 1 is read -> the administrator profile, account number ADMIN001, balance, and is_admin true are exposed to an ordinary user.
06 / 26FAIL

Broken Object Level Authorization - Virtual Cards - Card Operations

4 screenshots · 4 HTTP captures

Vulnerability

[FAIL] Broken Object Level Authorization - Virtual Cards - Card Operations

Description

The virtual card endpoints accept card_id from the URL path and operate on it without verifying that the card belongs to the token holder. Card identifiers are sequential integers and therefore easy to enumerate.

Impact

Confidentiality + Integrity (High): any authenticated user can read another customer's virtual card transaction history and change its status. In testing, the attacker froze the victim's card, which is a denial of service against the victim's payment instrument. Similar endpoints in the same module also allow changing limits and topping up funds.

CWE

CWE-639: Authorization Bypass Through User-Controlled Key

OWASP

A01:2021 - Broken Access Control

Scenario

Normal: the attacker accesses their own card transaction history -> succeeds. Exploitation: the attacker changes card_id to the victim's -> HTTP 200, the victim's card history is readable. Impact: the attacker calls toggle-freeze on the victim's card -> the victim's card is successfully frozen and the is_frozen true status is visible from the victim's session.

Endpoint

GET http://127.0.0.1:5001/api/virtual-cards/<card_id>/transactions POST http://127.0.0.1:5001/api/virtual-cards/<card_id>/toggle-freeze

Affected feature

Virtual cards (transaction history, card freeze, limits, top-up)

Remediation

- Verify object ownership on every request: match card_id with the user_id from the token before reading or modifying. - Apply centralized authorization checks across all card endpoints, not per handler. - Use random UUIDs as card identifiers so they cannot be enumerated. - Log and monitor cross-object access attempts.

Proof of concept

1) (Screenshot 1) NORMAL: the attacker reads their own card history -> succeeds. 2) (Screenshot 2) EXPLOITATION: the attacker reads the victim's card history -> HTTP 200, the victim's data is exposed. 3) (Screenshot 3) EXPLOITATION: the attacker freezes the victim's card -> Card frozen successfully. 4) (Screenshot 4) CIA IMPACT: from the victim's session, their card appears as is_frozen true due to the attacker's action.
View source note (English)
Vulnerability:
[FAIL] Broken Object Level Authorization - Virtual Cards - Card Operations

Description:
The virtual card endpoints accept card_id from the URL path and operate on it without verifying that the card belongs to the token holder. Card identifiers are sequential integers and therefore easy to enumerate.

Impact:
Confidentiality + Integrity (High): any authenticated user can read another customer's virtual card transaction history and change its status. In testing, the attacker froze the victim's card, which is a denial of service against the victim's payment instrument. Similar endpoints in the same module also allow changing limits and topping up funds.

CWE:
CWE-639: Authorization Bypass Through User-Controlled Key

OWASP:
A01:2021 - Broken Access Control

Scenario:
Normal: the attacker accesses their own card transaction history -> succeeds. Exploitation: the attacker changes card_id to the victim's -> HTTP 200, the victim's card history is readable. Impact: the attacker calls toggle-freeze on the victim's card -> the victim's card is successfully frozen and the is_frozen true status is visible from the victim's session.

Endpoint:
GET  http://127.0.0.1:5001/api/virtual-cards/<card_id>/transactions
POST http://127.0.0.1:5001/api/virtual-cards/<card_id>/toggle-freeze

Affected Feature:
Virtual cards (transaction history, card freeze, limits, top-up)

Remediation:
- Verify object ownership on every request: match card_id with the user_id from the token before reading or modifying.
- Apply centralized authorization checks across all card endpoints, not per handler.
- Use random UUIDs as card identifiers so they cannot be enumerated.
- Log and monitor cross-object access attempts.

POC:
1) (Screenshot 1) NORMAL: the attacker reads their own card history -> succeeds.
2) (Screenshot 2) EXPLOITATION: the attacker reads the victim's card history -> HTTP 200, the victim's data is exposed.
3) (Screenshot 3) EXPLOITATION: the attacker freezes the victim's card -> Card frozen successfully.
4) (Screenshot 4) CIA IMPACT: from the victim's session, their card appears as is_frozen true due to the attacker's action.
07 / 26FAIL

Excessive Data Exposure - GraphQL - Schema Introspection

1 screenshots · 1 HTTP captures

Vulnerability

[FAIL] Excessive Data Exposure - GraphQL - Schema Introspection

Description

The /graphql endpoint serves __schema introspection queries to ordinary authenticated users. Every type, field, and argument in the schema can be downloaded, including fields not used by the official interface.

Impact

Low: introspection gives an attacker a complete map of the GraphQL attack surface without guessing field names. This information accelerates the discovery of other vulnerabilities in the same layer; in this test, the accountNumber argument revealed by the schema became the entry point for the SQL Injection finding in the transactionSummary resolver. Introspection itself does not disclose customer data.

CWE

CWE-200: Exposure of Sensitive Information to an Unauthorized Actor

OWASP

A05:2021 - Security Misconfiguration

Scenario

Exploitation: a __schema query is sent using an ordinary user token -> every type and field definition is returned, including the transactionSummary resolver and its arguments.

Endpoint

POST http://127.0.0.1:5001/graphql

Affected feature

GraphQL endpoint

Remediation

- Disable introspection in production environments. - Apply query allow-listing (persisted queries) so only known operations can run. - Limit query depth and complexity and apply rate limiting. - Ensure every resolver enforces its own authorization checks.

Proof of concept

1) (Screenshot 1) EXPLOITATION: a __schema introspection query is sent with an ordinary user token -> the entire schema types and fields are returned, including the transactionSummary resolver and its arguments.
View source note (English)
Vulnerability:
[FAIL] Excessive Data Exposure - GraphQL - Schema Introspection

Description:
The /graphql endpoint serves __schema introspection queries to ordinary authenticated users. Every type, field, and argument in the schema can be downloaded, including fields not used by the official interface.

Impact:
Low: introspection gives an attacker a complete map of the GraphQL attack surface without guessing field names. This information accelerates the discovery of other vulnerabilities in the same layer; in this test, the accountNumber argument revealed by the schema became the entry point for the SQL Injection finding in the transactionSummary resolver. Introspection itself does not disclose customer data.

CWE:
CWE-200: Exposure of Sensitive Information to an Unauthorized Actor

OWASP:
A05:2021 - Security Misconfiguration

Scenario:
Exploitation: a __schema query is sent using an ordinary user token -> every type and field definition is returned, including the transactionSummary resolver and its arguments.

Endpoint:
POST http://127.0.0.1:5001/graphql

Affected Feature:
GraphQL endpoint

Remediation:
- Disable introspection in production environments.
- Apply query allow-listing (persisted queries) so only known operations can run.
- Limit query depth and complexity and apply rate limiting.
- Ensure every resolver enforces its own authorization checks.

POC:
1) (Screenshot 1) EXPLOITATION: a __schema introspection query is sent with an ordinary user token -> the entire schema types and fields are returned, including the transactionSummary resolver and its arguments.
08 / 26FAIL

Insufficient Session Expiration - Authentication - JWT Lifetime

3 screenshots · 2 HTTP captures · 1 process records

Vulnerability

[FAIL] Insufficient Session Expiration - Authentication - JWT Lifetime

Description

Issued JWT tokens contain no exp claim, so they never expire, and there is no token revocation mechanism. The token is also accepted from several locations at once, including URL query parameters, and in the web interface it is stored in localStorage where JavaScript can read it.

Impact

Medium: a token that leaks once remains valid forever. There is no rotation or revocation, so a single leak grants permanent access to the victim's account. Accepting tokens via query parameters increases the chance of leakage because URLs are recorded in browser history, server logs, and the Referer header. Storing the token in localStorage makes it stealable via XSS, which was proven in the Stored Cross-Site Scripting finding where an administrator JWT was successfully captured.

CWE

CWE-613: Insufficient Session Expiration

OWASP

A07:2021 - Identification and Authentication Failures

Scenario

Token analysis: an issued token is decoded and contains only the user_id, username, is_admin, and iat claims; there is no exp claim at all. Verification: the same token is still accepted by a protected endpoint. Supporting evidence: the token is also accepted when sent as a URL query parameter, not only in the Authorization header.

Endpoint

POST http://127.0.0.1:5001/login (token issuance) GET http://127.0.0.1:5001/dashboard (token acceptance)

Affected feature

JWT-based session management

Remediation

- Add a short-lived exp claim and implement revocable refresh tokens. - Maintain a revocation list (denylist) or use token identifiers so sessions can be terminated during an incident. - Accept tokens only from the Authorization header; stop reading them from query parameters and forms. - Move session tokens into HttpOnly, Secure, and SameSite cookies so JavaScript cannot reach them.

Proof of concept

1) (Screenshot 1) ANALYSIS: the login token is decoded -> it contains only user_id, username, is_admin, and iat; no exp claim exists. 2) (Screenshot 2) VERIFICATION: the same token is accepted by a protected endpoint -> HTTP 200. 3) (Screenshot 3) IMPACT: the same token is also accepted when sent as a URL query parameter -> HTTP 200, increasing the chance of leakage via logs and Referer.
View source note (English)
Vulnerability:
[FAIL] Insufficient Session Expiration - Authentication - JWT Lifetime

Description:
Issued JWT tokens contain no exp claim, so they never expire, and there is no token revocation mechanism. The token is also accepted from several locations at once, including URL query parameters, and in the web interface it is stored in localStorage where JavaScript can read it.

Impact:
Medium: a token that leaks once remains valid forever. There is no rotation or revocation, so a single leak grants permanent access to the victim's account. Accepting tokens via query parameters increases the chance of leakage because URLs are recorded in browser history, server logs, and the Referer header. Storing the token in localStorage makes it stealable via XSS, which was proven in the Stored Cross-Site Scripting finding where an administrator JWT was successfully captured.

CWE:
CWE-613: Insufficient Session Expiration

OWASP:
A07:2021 - Identification and Authentication Failures

Scenario:
Token analysis: an issued token is decoded and contains only the user_id, username, is_admin, and iat claims; there is no exp claim at all. Verification: the same token is still accepted by a protected endpoint. Supporting evidence: the token is also accepted when sent as a URL query parameter, not only in the Authorization header.

Endpoint:
POST http://127.0.0.1:5001/login (token issuance)
GET  http://127.0.0.1:5001/dashboard (token acceptance)

Affected Feature:
JWT-based session management

Remediation:
- Add a short-lived exp claim and implement revocable refresh tokens.
- Maintain a revocation list (denylist) or use token identifiers so sessions can be terminated during an incident.
- Accept tokens only from the Authorization header; stop reading them from query parameters and forms.
- Move session tokens into HttpOnly, Secure, and SameSite cookies so JavaScript cannot reach them.

POC:
1) (Screenshot 1) ANALYSIS: the login token is decoded -> it contains only user_id, username, is_admin, and iat; no exp claim exists.
2) (Screenshot 2) VERIFICATION: the same token is accepted by a protected endpoint -> HTTP 200.
3) (Screenshot 3) IMPACT: the same token is also accepted when sent as a URL query parameter -> HTTP 200, increasing the chance of leakage via logs and Referer.
09 / 26FAIL

JWT Signature Not Verified - Authentication - Token Validation

4 screenshots · 3 HTTP captures · 1 process records

Vulnerability

[FAIL] JWT Signature Not Verified - Authentication - Token Validation

Description

The verify_token function in auth.py catches InvalidSignatureError and then retries the decode with options={'verify_signature': False}, returning the payload. As a result, tokens with an invalid signature are still accepted. The is_admin claim is read directly from the token payload, so an attacker only needs to change that claim.

Impact

Confidentiality + Integrity (Critical): vertical privilege escalation from an ordinary user to administrator without knowing the signing key. A forged token opens the admin panel and every administrative endpoint, including reading other users' data and managing accounts.

CWE

CWE-347: Improper Verification of Cryptographic Signature

OWASP

A07:2021 - Identification and Authentication Failures

Scenario

Normal: pentest_attacker's legitimate token is rejected by the admin panel with 403 Access Denied. Exploitation: the token payload is changed to user_id 1 and is_admin true, then re-signed with the wrong key (totally-wrong-key). Impact: the forged token is accepted, the admin panel opens, and administrator data is readable.

Endpoint

GET http://127.0.0.1:5001/sup3r_s3cr3t_admin GET http://127.0.0.1:5001/api/v3/user/1

Affected feature

JWT validation across all protected endpoints (token_required decorator)

Remediation

- Remove the fallback path that decodes with verify_signature False; a signature verification failure must always reject the request. - Pin the algorithm to one expected value (HS256) and reject any other value. - Do not make authorization decisions from claims inside the token; fetch the role from the database using the token subject. - Use a strong random secret and add an exp claim.

Proof of concept

1) (Screenshot 1) NORMAL: pentest_attacker's legitimate token to /sup3r_s3cr3t_admin -> HTTP 403 Access Denied. 2) (Screenshot 2) PROCESS: the token is decoded, the user_id claim is changed to 1 and is_admin to true, then re-signed with the wrong key. 3) (Screenshot 3) EXPLOITATION: the forged token to /sup3r_s3cr3t_admin -> HTTP 200, the admin panel opens. 4) (Screenshot 4) CIA IMPACT: the forged token reads /api/v3/user/1 -> administrator data (ADMIN001, is_admin true) is exposed.
View source note (English)
Vulnerability:
[FAIL] JWT Signature Not Verified - Authentication - Token Validation

Description:
The verify_token function in auth.py catches InvalidSignatureError and then retries the decode with options={'verify_signature': False}, returning the payload. As a result, tokens with an invalid signature are still accepted. The is_admin claim is read directly from the token payload, so an attacker only needs to change that claim.

Impact:
Confidentiality + Integrity (Critical): vertical privilege escalation from an ordinary user to administrator without knowing the signing key. A forged token opens the admin panel and every administrative endpoint, including reading other users' data and managing accounts.

CWE:
CWE-347: Improper Verification of Cryptographic Signature

OWASP:
A07:2021 - Identification and Authentication Failures

Scenario:
Normal: pentest_attacker's legitimate token is rejected by the admin panel with 403 Access Denied. Exploitation: the token payload is changed to user_id 1 and is_admin true, then re-signed with the wrong key (totally-wrong-key). Impact: the forged token is accepted, the admin panel opens, and administrator data is readable.

Endpoint:
GET http://127.0.0.1:5001/sup3r_s3cr3t_admin
GET http://127.0.0.1:5001/api/v3/user/1

Affected Feature:
JWT validation across all protected endpoints (token_required decorator)

Remediation:
- Remove the fallback path that decodes with verify_signature False; a signature verification failure must always reject the request.
- Pin the algorithm to one expected value (HS256) and reject any other value.
- Do not make authorization decisions from claims inside the token; fetch the role from the database using the token subject.
- Use a strong random secret and add an exp claim.

POC:
1) (Screenshot 1) NORMAL: pentest_attacker's legitimate token to /sup3r_s3cr3t_admin -> HTTP 403 Access Denied.
2) (Screenshot 2) PROCESS: the token is decoded, the user_id claim is changed to 1 and is_admin to true, then re-signed with the wrong key.
3) (Screenshot 3) EXPLOITATION: the forged token to /sup3r_s3cr3t_admin -> HTTP 200, the admin panel opens.
4) (Screenshot 4) CIA IMPACT: the forged token reads /api/v3/user/1 -> administrator data (ADMIN001, is_admin true) is exposed.
10 / 26FAIL

Mass Assignment - Registration - Privilege Fields

3 screenshots · 3 HTTP captures

Vulnerability

[FAIL] Mass Assignment - Registration - Privilege Fields

Description

The /register endpoint copies every key in the JSON request into the INSERT statement without a column allow-list. Sensitive fields such as is_admin and balance, which should be determined by the server, can be sent by the client and stored directly.

Impact

Integrity (Critical): anyone who can reach the registration page can create an administrator account for themselves and set the account balance at will. This bypasses all approval controls for administrator creation and creates fictitious balances in the banking system.

CWE

CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes

OWASP

A04:2021 - Insecure Design

Scenario

Normal: registering with only a username and password produces an ordinary account (is_admin false, default balance 1000). Exploitation: the same request plus is_admin true and balance 999999 -> the account is created directly as an administrator with an attacker-chosen balance. Impact: logging in to that account opens the admin panel.

Endpoint

POST http://127.0.0.1:5001/register

Affected feature

New user registration

Remediation

- Apply an allow-list of columns the client may set (username, password only); explicitly ignore every other field. - Set is_admin and balance server-side with default values, never from input. - Separate administrator creation into a dedicated flow that requires administrator authorization. - Remove the debug_data block that reflects raw input in the response.

Proof of concept

1) (Screenshot 1) NORMAL: register ma_normal_user with only a username and password -> is_admin false, balance 1000. 2) (Screenshot 2) EXPLOITATION: register ma_evil_admin with the addition of is_admin true and balance 999999 -> stored as an administrator with a balance of 999999. 3) (Screenshot 3) CIA IMPACT: log in as ma_evil_admin and access /sup3r_s3cr3t_admin -> HTTP 200, the administrative panel opens.
View source note (English)
Vulnerability:
[FAIL] Mass Assignment - Registration - Privilege Fields

Description:
The /register endpoint copies every key in the JSON request into the INSERT statement without a column allow-list. Sensitive fields such as is_admin and balance, which should be determined by the server, can be sent by the client and stored directly.

Impact:
Integrity (Critical): anyone who can reach the registration page can create an administrator account for themselves and set the account balance at will. This bypasses all approval controls for administrator creation and creates fictitious balances in the banking system.

CWE:
CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes

OWASP:
A04:2021 - Insecure Design

Scenario:
Normal: registering with only a username and password produces an ordinary account (is_admin false, default balance 1000). Exploitation: the same request plus is_admin true and balance 999999 -> the account is created directly as an administrator with an attacker-chosen balance. Impact: logging in to that account opens the admin panel.

Endpoint:
POST http://127.0.0.1:5001/register

Affected Feature:
New user registration

Remediation:
- Apply an allow-list of columns the client may set (username, password only); explicitly ignore every other field.
- Set is_admin and balance server-side with default values, never from input.
- Separate administrator creation into a dedicated flow that requires administrator authorization.
- Remove the debug_data block that reflects raw input in the response.

POC:
1) (Screenshot 1) NORMAL: register ma_normal_user with only a username and password -> is_admin false, balance 1000.
2) (Screenshot 2) EXPLOITATION: register ma_evil_admin with the addition of is_admin true and balance 999999 -> stored as an administrator with a balance of 999999.
3) (Screenshot 3) CIA IMPACT: log in as ma_evil_admin and access /sup3r_s3cr3t_admin -> HTTP 200, the administrative panel opens.
11 / 26FAIL

Race Condition - Transfer - Balance Check

5 screenshots · 5 HTTP captures

Vulnerability

[FAIL] Race Condition - Transfer - Balance Check

Description

The /transfer endpoint reads the sender's balance, checks that funds are sufficient, then writes the new balance as a separate operation with no atomic database transaction and no row locking. Ten concurrently sent transfer requests all pass the balance check using the same stale value before any write completes (TOCTOU).

Impact

Integrity (Critical): an attacker can send funds far beyond their available balance. In testing, the sender's balance went negative while the recipient's balance increased in full, creating money out of thin air. In a banking system this means direct financial loss and inconsistent accounting.

CWE

CWE-362: Concurrent Execution using Shared Resource with Improper Synchronization

OWASP

A04:2021 - Insecure Design

Scenario

Normal: one transfer within the balance succeeds, and one transfer exceeding the balance is rejected with an insufficient funds message, proving the control works when serialized. Exploitation: ten transfers of 900 each are sent in parallel from a much smaller balance. Impact: the sender's balance becomes negative and the total transferred exceeds the balance ever held.

Endpoint

POST http://127.0.0.1:5001/transfer

Affected feature

Fund transfers between accounts

Remediation

- Perform the balance check and the debit in a single atomic database transaction. - Lock the sender's account row (SELECT ... FOR UPDATE) or use a conditional update UPDATE ... SET balance = balance - x WHERE balance >= x. - Add a CHECK (balance >= 0) constraint on the balance column as a safety net. - Apply an idempotency key and rate limiting to the transfer endpoint.

Proof of concept

1) (Screenshot 1) NORMAL: a transfer of 100 within the balance -> succeeds. 2) (Screenshot 2) NORMAL: a transfer of 5000 exceeding the balance -> rejected, the balance control works when serialized. 3) (Screenshot 3) BASELINE: the attacker's balance before the attack = 900.0. 4) (Screenshot 4) EXPLOITATION: ten transfers of 900 are sent concurrently -> all are reported successful. 5) (Screenshot 5) CIA IMPACT: the attacker's balance becomes -8100.0, negative and beyond the funds ever held.
View source note (English)
Vulnerability:
[FAIL] Race Condition - Transfer - Balance Check

Description:
The /transfer endpoint reads the sender's balance, checks that funds are sufficient, then writes the new balance as a separate operation with no atomic database transaction and no row locking. Ten concurrently sent transfer requests all pass the balance check using the same stale value before any write completes (TOCTOU).

Impact:
Integrity (Critical): an attacker can send funds far beyond their available balance. In testing, the sender's balance went negative while the recipient's balance increased in full, creating money out of thin air. In a banking system this means direct financial loss and inconsistent accounting.

CWE:
CWE-362: Concurrent Execution using Shared Resource with Improper Synchronization

OWASP:
A04:2021 - Insecure Design

Scenario:
Normal: one transfer within the balance succeeds, and one transfer exceeding the balance is rejected with an insufficient funds message, proving the control works when serialized. Exploitation: ten transfers of 900 each are sent in parallel from a much smaller balance. Impact: the sender's balance becomes negative and the total transferred exceeds the balance ever held.

Endpoint:
POST http://127.0.0.1:5001/transfer

Affected Feature:
Fund transfers between accounts

Remediation:
- Perform the balance check and the debit in a single atomic database transaction.
- Lock the sender's account row (SELECT ... FOR UPDATE) or use a conditional update UPDATE ... SET balance = balance - x WHERE balance >= x.
- Add a CHECK (balance >= 0) constraint on the balance column as a safety net.
- Apply an idempotency key and rate limiting to the transfer endpoint.

POC:
1) (Screenshot 1) NORMAL: a transfer of 100 within the balance -> succeeds.
2) (Screenshot 2) NORMAL: a transfer of 5000 exceeding the balance -> rejected, the balance control works when serialized.
3) (Screenshot 3) BASELINE: the attacker's balance before the attack = 900.0.
4) (Screenshot 4) EXPLOITATION: ten transfers of 900 are sent concurrently -> all are reported successful.
5) (Screenshot 5) CIA IMPACT: the attacker's balance becomes -8100.0, negative and beyond the funds ever held.
12 / 26FAIL

Security Misconfiguration - CORS - Wildcard Origin

1 screenshots · 1 HTTP captures

Vulnerability

[FAIL] Security Misconfiguration - CORS - Wildcard Origin

Description

The application sends an Access-Control-Allow-Origin header with the wildcard value (*) on every response, including responses from endpoints that return sensitive data. This policy removes the same-origin protection for response reads by cross-origin scripts.

Impact

Low: any site can read responses from endpoints that do not require authentication directly from a visitor's browser, including /debug/users which contains credentials. The impact is limited by the fact that the wildcard value cannot be combined with credentials, so session cookies are not sent on cross-origin requests. Honest testing note: cross-origin reading of data belonging to a logged-in user was NOT proven, because the application sends its token in an Authorization header that an attacker site cannot access. This finding is reported as a configuration weakness that degrades defense in depth, not as theft of authenticated data.

CWE

CWE-942: Permissive Cross-domain Policy with Untrusted Domains

OWASP

A05:2021 - Security Misconfiguration

Scenario

Exploitation: a request is sent with an Origin header belonging to the attacker's domain (https://evil.example.com) -> the response returns Access-Control-Allow-Origin: *, so scripts on any origin are allowed to read the response body.

Endpoint

Every endpoint under http://127.0.0.1:5001/ (the header is sent globally)

Affected feature

Application-level CORS policy

Remediation

- Replace the wildcard with an allow-list of origins that are actually trusted. - Do not send CORS headers on endpoints that are not meant to be accessed cross-origin. - Set Vary: Origin and never reflect the Origin header blindly. - Review endpoints that return sensitive data without authentication.

Proof of concept

1) (Screenshot 1) EXPLOITATION: a request with Origin https://evil.example.com to /debug/users -> the response returns Access-Control-Allow-Origin: * , allowing any origin to read the response body.
View source note (English)
Vulnerability:
[FAIL] Security Misconfiguration - CORS - Wildcard Origin

Description:
The application sends an Access-Control-Allow-Origin header with the wildcard value (*) on every response, including responses from endpoints that return sensitive data. This policy removes the same-origin protection for response reads by cross-origin scripts.

Impact:
Low: any site can read responses from endpoints that do not require authentication directly from a visitor's browser, including /debug/users which contains credentials. The impact is limited by the fact that the wildcard value cannot be combined with credentials, so session cookies are not sent on cross-origin requests.

Honest testing note: cross-origin reading of data belonging to a logged-in user was NOT proven, because the application sends its token in an Authorization header that an attacker site cannot access. This finding is reported as a configuration weakness that degrades defense in depth, not as theft of authenticated data.

CWE:
CWE-942: Permissive Cross-domain Policy with Untrusted Domains

OWASP:
A05:2021 - Security Misconfiguration

Scenario:
Exploitation: a request is sent with an Origin header belonging to the attacker's domain (https://evil.example.com) -> the response returns Access-Control-Allow-Origin: *, so scripts on any origin are allowed to read the response body.

Endpoint:
Every endpoint under http://127.0.0.1:5001/ (the header is sent globally)

Affected Feature:
Application-level CORS policy

Remediation:
- Replace the wildcard with an allow-list of origins that are actually trusted.
- Do not send CORS headers on endpoints that are not meant to be accessed cross-origin.
- Set Vary: Origin and never reflect the Origin header blindly.
- Review endpoints that return sensitive data without authentication.

POC:
1) (Screenshot 1) EXPLOITATION: a request with Origin https://evil.example.com to /debug/users -> the response returns Access-Control-Allow-Origin: * , allowing any origin to read the response body.
13 / 26FAIL

Sensitive Data Exposure - Debug Endpoint - User Credentials

3 screenshots · 3 HTTP captures

Vulnerability

[FAIL] Sensitive Data Exposure - Debug Endpoint - User Credentials

Description

The /debug/users endpoint is reachable without authentication and returns the entire users table, including the password column. Passwords are stored in plaintext rather than hashed, so they can be used immediately.

Impact

Confidentiality (High, leading to full compromise): anyone without credentials can download every customer's username, plaintext password, account number, and administrator flag. In testing, the administrator password obtained from this endpoint was used directly to log in as administrator. Because passwords are often reused, the impact extends beyond this application.

CWE

CWE-200: Exposure of Sensitive Information to an Unauthorized Actor

OWASP

A01:2021 - Broken Access Control

Scenario

Control: /dashboard without a token is rejected with HTTP 401, showing the application does have an authentication layer. Exploitation: /debug/users is accessed without any token -> HTTP 200 containing every credential. Impact: the leaked administrator password is used to log in and yields a token with is_admin true.

Endpoint

GET http://127.0.0.1:5001/debug/users

Affected feature

Debug endpoint left in the production build

Remediation

- Remove debug endpoints from code shipped to production; do not merely hide them. - Store passwords as salted hashes (bcrypt/argon2) and never return the password column from any query. - Apply authentication and authorization by default (deny by default) on all routes. - Rotate every credential that was exposed.

Proof of concept

1) (Screenshot 1) CONTROL: /dashboard without a token -> HTTP 401 Token is missing, authentication does exist. 2) (Screenshot 2) EXPLOITATION: /debug/users without authentication -> HTTP 200 containing every user with plaintext passwords. 3) (Screenshot 3) CIA IMPACT: the leaked administrator password is used to log in -> an administrator token is issued, is_admin true.
View source note (English)
Vulnerability:
[FAIL] Sensitive Data Exposure - Debug Endpoint - User Credentials

Description:
The /debug/users endpoint is reachable without authentication and returns the entire users table, including the password column. Passwords are stored in plaintext rather than hashed, so they can be used immediately.

Impact:
Confidentiality (High, leading to full compromise): anyone without credentials can download every customer's username, plaintext password, account number, and administrator flag. In testing, the administrator password obtained from this endpoint was used directly to log in as administrator. Because passwords are often reused, the impact extends beyond this application.

CWE:
CWE-200: Exposure of Sensitive Information to an Unauthorized Actor

OWASP:
A01:2021 - Broken Access Control

Scenario:
Control: /dashboard without a token is rejected with HTTP 401, showing the application does have an authentication layer. Exploitation: /debug/users is accessed without any token -> HTTP 200 containing every credential. Impact: the leaked administrator password is used to log in and yields a token with is_admin true.

Endpoint:
GET http://127.0.0.1:5001/debug/users

Affected Feature:
Debug endpoint left in the production build

Remediation:
- Remove debug endpoints from code shipped to production; do not merely hide them.
- Store passwords as salted hashes (bcrypt/argon2) and never return the password column from any query.
- Apply authentication and authorization by default (deny by default) on all routes.
- Rotate every credential that was exposed.

POC:
1) (Screenshot 1) CONTROL: /dashboard without a token -> HTTP 401 Token is missing, authentication does exist.
2) (Screenshot 2) EXPLOITATION: /debug/users without authentication -> HTTP 200 containing every user with plaintext passwords.
3) (Screenshot 3) CIA IMPACT: the leaked administrator password is used to log in -> an administrator token is issued, is_admin true.
14 / 26FAIL

Sensitive Data Exposure - Web Root - Environment Files

2 screenshots · 2 HTTP captures

Vulnerability

[FAIL] Sensitive Data Exposure - Web Root - Environment Files

Description

The environment configuration file .env and its backup copy .env.bak are served directly from the web root without authentication. These files contain the application secret key, JWT secret, and database credentials.

Impact

Confidentiality (High): an unauthenticated attacker obtains the application SECRET_KEY, JWT_SECRET, and the database host, name, user, and password. With JWT_SECRET, any session token can be forged with a valid signature, including an administrator token. The database credentials allow direct access to the data store if the database network is reachable.

CWE

CWE-538: Insertion of Sensitive Information into Externally-Accessible File or Directory

OWASP

A05:2021 - Security Misconfiguration

Scenario

Exploitation: a normal GET request to /.env without authentication -> HTTP 200 with the configuration file contents. The backup file /.env.bak is also served, exposing the older configuration including credentials that may still be valid.

Endpoint

GET http://127.0.0.1:5001/.env GET http://127.0.0.1:5001/.env.bak

Affected feature

Static file serving from the web root

Remediation

- Do not place configuration files inside the web root, and do not create routes that serve them. - Block access to dot-prefixed and backup-extension files at the reverse proxy. - Move secrets into a secret manager or environment variables that are not servable files. - Rotate the exposed SECRET_KEY, JWT_SECRET, and database credentials.

Proof of concept

1) (Screenshot 1) EXPLOITATION: GET /.env without authentication -> HTTP 200, SECRET_KEY, JWT_SECRET, and database credentials are exposed. 2) (Screenshot 2) CIA IMPACT: GET /.env.bak -> HTTP 200, the backup configuration copy is also publicly downloadable.
View source note (English)
Vulnerability:
[FAIL] Sensitive Data Exposure - Web Root - Environment Files

Description:
The environment configuration file .env and its backup copy .env.bak are served directly from the web root without authentication. These files contain the application secret key, JWT secret, and database credentials.

Impact:
Confidentiality (High): an unauthenticated attacker obtains the application SECRET_KEY, JWT_SECRET, and the database host, name, user, and password. With JWT_SECRET, any session token can be forged with a valid signature, including an administrator token. The database credentials allow direct access to the data store if the database network is reachable.

CWE:
CWE-538: Insertion of Sensitive Information into Externally-Accessible File or Directory

OWASP:
A05:2021 - Security Misconfiguration

Scenario:
Exploitation: a normal GET request to /.env without authentication -> HTTP 200 with the configuration file contents. The backup file /.env.bak is also served, exposing the older configuration including credentials that may still be valid.

Endpoint:
GET http://127.0.0.1:5001/.env
GET http://127.0.0.1:5001/.env.bak

Affected Feature:
Static file serving from the web root

Remediation:
- Do not place configuration files inside the web root, and do not create routes that serve them.
- Block access to dot-prefixed and backup-extension files at the reverse proxy.
- Move secrets into a secret manager or environment variables that are not servable files.
- Rotate the exposed SECRET_KEY, JWT_SECRET, and database credentials.

POC:
1) (Screenshot 1) EXPLOITATION: GET /.env without authentication -> HTTP 200, SECRET_KEY, JWT_SECRET, and database credentials are exposed.
2) (Screenshot 2) CIA IMPACT: GET /.env.bak -> HTTP 200, the backup configuration copy is also publicly downloadable.
15 / 26FAIL

Server-Side Request Forgery - Profile Picture - Import By URL

6 screenshots · 6 HTTP captures

Vulnerability

[FAIL] Server-Side Request Forgery - Profile Picture - Import By URL

Description

The /upload_profile_picture_url endpoint accepts a full URL from the client and performs a server-side requests.get without a scheme or host allow-list, follows redirects, and does not validate content-type. The upstream response body is stored as-is in the publicly accessible static/uploads/, so the result of internal requests can be downloaded back by the attacker.

Impact

Confidentiality (High): an authenticated attacker can force the server to read internal endpoints that deny direct access, then exfiltrate them through the public upload directory. In testing this disclosed the application's jwt_secret and app_secret_key, database credentials, and temporary cloud IAM credentials (AccessKeyId, SecretAccessKey, session token). Leaking jwt_secret allows forging validly signed administrator tokens, leading to full compromise.

CWE

CWE-918: Server-Side Request Forgery (SSRF)

OWASP

A10:2021 - Server-Side Request Forgery (SSRF)

Scenario

Normal: image_url points to a legitimate profile image and the server imports it. Control: /internal/secret accessed directly from outside -> HTTP 403 Forbidden. Exploitation: image_url is pointed at http://127.0.0.1:5000/internal/secret -> the server fetches it with HTTP 200 and stores it in static/uploads. Exfiltration: the downloaded file is retrieved over ordinary HTTP, disclosing application secrets. Chain: the same technique is aimed at the IAM metadata endpoint to harvest cloud credentials.

Endpoint

POST http://127.0.0.1:5001/upload_profile_picture_url

Affected feature

Profile picture import by URL

Remediation

- Apply a host and scheme allow-list; reject URLs to loopback, RFC1918, link-local (169.254.0.0/16), and internal service names. - Resolve DNS and validate the resolved IP before making the request, and repeat the validation on every redirect (or disable redirects). - Validate content-type and size; accept images only. - Store downloaded files outside the web root, give them random names, and serve them through an authorized handler rather than as public static files. - Do not reflect upstream request status and metadata to the client.

Proof of concept

1) (Screenshot 1) NORMAL: image_url points to a legitimate profile image -> import succeeds. 2) (Screenshot 2) CONTROL: direct access to /internal/secret from outside -> HTTP 403 Forbidden. 3) (Screenshot 3) EXPLOITATION: image_url is pointed at /internal/secret -> the server fetches it, http_status 200, the file is stored in static/uploads. 4) (Screenshot 4) EXFILTRATION: the SSRF output file is downloaded -> jwt_secret, app_secret_key, and database credentials are exposed. 5) (Screenshot 5) CHAIN: image_url is pointed at the IAM metadata endpoint -> fetched by the server with http_status 200. 6) (Screenshot 6) CIA IMPACT: the IAM credential file is downloaded -> AccessKeyId, SecretAccessKey, and session token are exposed.
View source note (English)
Vulnerability:
[FAIL] Server-Side Request Forgery - Profile Picture - Import By URL

Description:
The /upload_profile_picture_url endpoint accepts a full URL from the client and performs a server-side requests.get without a scheme or host allow-list, follows redirects, and does not validate content-type. The upstream response body is stored as-is in the publicly accessible static/uploads/, so the result of internal requests can be downloaded back by the attacker.

Impact:
Confidentiality (High): an authenticated attacker can force the server to read internal endpoints that deny direct access, then exfiltrate them through the public upload directory. In testing this disclosed the application's jwt_secret and app_secret_key, database credentials, and temporary cloud IAM credentials (AccessKeyId, SecretAccessKey, session token). Leaking jwt_secret allows forging validly signed administrator tokens, leading to full compromise.

CWE:
CWE-918: Server-Side Request Forgery (SSRF)

OWASP:
A10:2021 - Server-Side Request Forgery (SSRF)

Scenario:
Normal: image_url points to a legitimate profile image and the server imports it. Control: /internal/secret accessed directly from outside -> HTTP 403 Forbidden. Exploitation: image_url is pointed at http://127.0.0.1:5000/internal/secret -> the server fetches it with HTTP 200 and stores it in static/uploads. Exfiltration: the downloaded file is retrieved over ordinary HTTP, disclosing application secrets. Chain: the same technique is aimed at the IAM metadata endpoint to harvest cloud credentials.

Endpoint:
POST http://127.0.0.1:5001/upload_profile_picture_url

Affected Feature:
Profile picture import by URL

Remediation:
- Apply a host and scheme allow-list; reject URLs to loopback, RFC1918, link-local (169.254.0.0/16), and internal service names.
- Resolve DNS and validate the resolved IP before making the request, and repeat the validation on every redirect (or disable redirects).
- Validate content-type and size; accept images only.
- Store downloaded files outside the web root, give them random names, and serve them through an authorized handler rather than as public static files.
- Do not reflect upstream request status and metadata to the client.

POC:
1) (Screenshot 1) NORMAL: image_url points to a legitimate profile image -> import succeeds.
2) (Screenshot 2) CONTROL: direct access to /internal/secret from outside -> HTTP 403 Forbidden.
3) (Screenshot 3) EXPLOITATION: image_url is pointed at /internal/secret -> the server fetches it, http_status 200, the file is stored in static/uploads.
4) (Screenshot 4) EXFILTRATION: the SSRF output file is downloaded -> jwt_secret, app_secret_key, and database credentials are exposed.
5) (Screenshot 5) CHAIN: image_url is pointed at the IAM metadata endpoint -> fetched by the server with http_status 200.
6) (Screenshot 6) CIA IMPACT: the IAM credential file is downloaded -> AccessKeyId, SecretAccessKey, and session token are exposed.
16 / 26FAIL

SQL Injection - Authentication - Login Bypass

3 screenshots · 3 HTTP captures

Vulnerability

[FAIL] SQL Injection - Authentication - Login Bypass

Description

The /login endpoint builds its SQL query with an f-string that interpolates the raw username and password without parameterization: SELECT * FROM users WHERE username='{username}' AND password='{password}'. An attacker closes the string literal and comments out the rest of the query so the password check is skipped entirely.

Impact

Confidentiality + Integrity (Critical): authentication can be bypassed without knowing the password. The payload admin' -- grants a full administrator session with a JWT carrying is_admin true, opening the admin panel and every administrative function (delete accounts, create new admins, approve loans).

CWE

CWE-89: Improper Neutralization of Special Elements used in an SQL Command

OWASP

A03:2021 - Injection

Scenario

Normal: logging in with one's own valid credentials yields a non-admin token. Exploitation: the username is set to admin' -- with an arbitrary password -> the query becomes SELECT * FROM users WHERE username='admin' -- ' AND password='anything' so the password condition is commented out. Impact: the admin token is used to open the administrative panel.

Endpoint

POST http://127.0.0.1:5001/login

Affected feature

User authentication (web login form and API login)

Remediation

- Use parameterized queries/prepared statements for all database access; never build SQL with f-strings or concatenation. - Store passwords as salted hashes (bcrypt/argon2) and compare hashes instead of matching passwords in the WHERE clause. - Apply a character allow-list to the username input. - Do not return query details or raw user objects in responses.

Proof of concept

1) (Screenshot 1) NORMAL: log in with pentest_attacker's valid credentials -> non-admin token, is_admin false. 2) (Screenshot 2) EXPLOITATION: username admin' -- with password anything -> login succeeds as admin, is_admin true. 3) (Screenshot 3) CIA IMPACT: the injected token is used to access /sup3r_s3cr3t_admin -> HTTP 200, the administrative panel opens.
View source note (English)
Vulnerability:
[FAIL] SQL Injection - Authentication - Login Bypass

Description:
The /login endpoint builds its SQL query with an f-string that interpolates the raw username and password without parameterization: SELECT * FROM users WHERE username='{username}' AND password='{password}'. An attacker closes the string literal and comments out the rest of the query so the password check is skipped entirely.

Impact:
Confidentiality + Integrity (Critical): authentication can be bypassed without knowing the password. The payload admin' --  grants a full administrator session with a JWT carrying is_admin true, opening the admin panel and every administrative function (delete accounts, create new admins, approve loans).

CWE:
CWE-89: Improper Neutralization of Special Elements used in an SQL Command

OWASP:
A03:2021 - Injection

Scenario:
Normal: logging in with one's own valid credentials yields a non-admin token. Exploitation: the username is set to admin' --  with an arbitrary password -> the query becomes SELECT * FROM users WHERE username='admin' -- ' AND password='anything' so the password condition is commented out. Impact: the admin token is used to open the administrative panel.

Endpoint:
POST http://127.0.0.1:5001/login

Affected Feature:
User authentication (web login form and API login)

Remediation:
- Use parameterized queries/prepared statements for all database access; never build SQL with f-strings or concatenation.
- Store passwords as salted hashes (bcrypt/argon2) and compare hashes instead of matching passwords in the WHERE clause.
- Apply a character allow-list to the username input.
- Do not return query details or raw user objects in responses.

POC:
1) (Screenshot 1) NORMAL: log in with pentest_attacker's valid credentials -> non-admin token, is_admin false.
2) (Screenshot 2) EXPLOITATION: username admin' --  with password anything -> login succeeds as admin, is_admin true.
3) (Screenshot 3) CIA IMPACT: the injected token is used to access /sup3r_s3cr3t_admin -> HTTP 200, the administrative panel opens.
17 / 26FAIL

SQL Injection - GraphQL - Transaction Summary Resolver

3 screenshots · 3 HTTP captures

Vulnerability

[FAIL] SQL Injection - GraphQL - Transaction Summary Resolver

Description

The transactionSummary resolver passes the accountNumber argument to the _load_actor_by_account_number function, which builds a query with an f-string: SELECT id, username, account_number, is_admin FROM users WHERE account_number = '{account_number}'. For callers with the administrator role, the argument value is not restricted at all, so SQL syntax can be injected.

Impact

Confidentiality (High): SQL injection at the GraphQL layer allows retrieval of users table rows beyond the requested account. Because the administrator role in this application can be obtained through several independently proven paths (SQL injection at login, mass assignment at registration, and JWT forgery), the administrator prerequisite is not a real barrier for an attacker.

CWE

CWE-89: Improper Neutralization of Special Elements used in an SQL Command

OWASP

A03:2021 - Injection

Scenario

Normal: accountNumber contains a legitimate account -> that account's transaction summary is returned. Control: accountNumber contains a non-existent account (NOPE000000) -> Account not found error. Exploitation: the same value plus injection syntax NOPE000000' OR '1'='1 -> the WHERE condition is always true and the first row of the users table is returned even though the account does not exist.

Endpoint

POST http://127.0.0.1:5001/graphql

Affected feature

GraphQL transactionSummary (transaction summary)

Remediation

- Use parameterized queries in every resolver; remove SQL string concatenation. - Validate the accountNumber format with a character allow-list before touching the database. - Apply least privilege to the database account used by the application. - Do not rely on the caller's role as a substitute for input sanitization.

Proof of concept

1) (Screenshot 1) NORMAL: accountNumber ADMIN001 -> the transaction summary is returned normally. 2) (Screenshot 2) CONTROL: accountNumber NOPE000000 which does not exist -> Account not found error. 3) (Screenshot 3) CIA IMPACT: accountNumber NOPE000000' OR '1'='1 -> the query is manipulated and returns the ADMIN001 row even though the requested account does not exist, proving injected SQL execution.
View source note (English)
Vulnerability:
[FAIL] SQL Injection - GraphQL - Transaction Summary Resolver

Description:
The transactionSummary resolver passes the accountNumber argument to the _load_actor_by_account_number function, which builds a query with an f-string: SELECT id, username, account_number, is_admin FROM users WHERE account_number = '{account_number}'. For callers with the administrator role, the argument value is not restricted at all, so SQL syntax can be injected.

Impact:
Confidentiality (High): SQL injection at the GraphQL layer allows retrieval of users table rows beyond the requested account. Because the administrator role in this application can be obtained through several independently proven paths (SQL injection at login, mass assignment at registration, and JWT forgery), the administrator prerequisite is not a real barrier for an attacker.

CWE:
CWE-89: Improper Neutralization of Special Elements used in an SQL Command

OWASP:
A03:2021 - Injection

Scenario:
Normal: accountNumber contains a legitimate account -> that account's transaction summary is returned. Control: accountNumber contains a non-existent account (NOPE000000) -> Account not found error. Exploitation: the same value plus injection syntax NOPE000000' OR '1'='1 -> the WHERE condition is always true and the first row of the users table is returned even though the account does not exist.

Endpoint:
POST http://127.0.0.1:5001/graphql

Affected Feature:
GraphQL transactionSummary (transaction summary)

Remediation:
- Use parameterized queries in every resolver; remove SQL string concatenation.
- Validate the accountNumber format with a character allow-list before touching the database.
- Apply least privilege to the database account used by the application.
- Do not rely on the caller's role as a substitute for input sanitization.

POC:
1) (Screenshot 1) NORMAL: accountNumber ADMIN001 -> the transaction summary is returned normally.
2) (Screenshot 2) CONTROL: accountNumber NOPE000000 which does not exist -> Account not found error.
3) (Screenshot 3) CIA IMPACT: accountNumber NOPE000000' OR '1'='1 -> the query is manipulated and returns the ADMIN001 row even though the requested account does not exist, proving injected SQL execution.
18 / 26FAIL

Stored Cross-Site Scripting - User Profile - Bio Field

5 screenshots · 3 HTTP captures

Vulnerability

[FAIL] Stored Cross-Site Scripting - User Profile - Bio Field

Description

The /update_bio endpoint stores bio content without any sanitization. The administrative panel then injects that value into the DOM through innerHTML in a template literal without escaping (templates/admin.html), while other fields in the same modal such as id and account_number do go through escapeHtml. As a result, markup submitted by an ordinary user executes as HTML inside the administrator's session.

Impact

Confidentiality + Integrity (Critical): the lowest-privileged user can plant a script that runs in the administrator's browser with no interaction beyond opening the user detail view, which is a routine admin task. Because session tokens are stored in localStorage and readable by JavaScript, the payload successfully captured the administrator JWT in full. That token gives the attacker full control of the administrative panel, including deleting accounts, creating new administrators, and approving loans.

CWE

CWE-79: Improper Neutralization of Input During Web Page Generation (Cross-site Scripting)

OWASP

A03:2021 - Injection

Scenario

Normal: the bio is filled with plain text and appears as text in the admin panel's user detail modal. Exploitation: the attacker fills the bio with an img tag carrying an onerror handler. The payload is stored raw and returned as-is by /api/v3/user/2. Impact: the administrator opens that user detail modal, the payload executes inside the administrator session and reads the administrator JWT from localStorage.

Endpoint

POST http://127.0.0.1:5001/update_bio GET http://127.0.0.1:5001/api/v3/user/<user_id> Sink: http://127.0.0.1:5001/sup3r_s3cr3t_admin (user detail modal)

Affected feature

User profile bio and the user detail modal in the administrative panel

Remediation

- Render user-supplied values as text (textContent), not innerHTML; apply escapeHtml to the bio field as is already done for other fields in the same modal. - Sanitize the bio server-side with an allow-list library both when stored and when returned by the API. - Apply a Content-Security-Policy that forbids inline scripts and event handlers. - Store session tokens in HttpOnly and SameSite cookies rather than localStorage so JavaScript cannot reach them.

Proof of concept

1) (Screenshot 1) NORMAL: the bio is filled with plain text via /update_bio -> stored. 2) (Screenshot 2) EXPLOITATION: the bio is filled with an img tag carrying an onerror handler -> accepted without sanitization. 3) (Screenshot 3) SINK: /api/v3/user/2 returns the raw payload as-is, which is the data consumed by the admin panel. 4) (Screenshot 4) NORMAL UI: the administrator opens the user detail modal while the bio contains plain text -> displayed as text. 5) (Screenshot 5) CIA IMPACT: the administrator opens the same user detail modal after the payload is planted -> the script executes inside the administrator session and displays the administrator JWT captured in full.
View source note (English)
Vulnerability:
[FAIL] Stored Cross-Site Scripting - User Profile - Bio Field

Description:
The /update_bio endpoint stores bio content without any sanitization. The administrative panel then injects that value into the DOM through innerHTML in a template literal without escaping (templates/admin.html), while other fields in the same modal such as id and account_number do go through escapeHtml. As a result, markup submitted by an ordinary user executes as HTML inside the administrator's session.

Impact:
Confidentiality + Integrity (Critical): the lowest-privileged user can plant a script that runs in the administrator's browser with no interaction beyond opening the user detail view, which is a routine admin task. Because session tokens are stored in localStorage and readable by JavaScript, the payload successfully captured the administrator JWT in full. That token gives the attacker full control of the administrative panel, including deleting accounts, creating new administrators, and approving loans.

CWE:
CWE-79: Improper Neutralization of Input During Web Page Generation (Cross-site Scripting)

OWASP:
A03:2021 - Injection

Scenario:
Normal: the bio is filled with plain text and appears as text in the admin panel's user detail modal. Exploitation: the attacker fills the bio with an img tag carrying an onerror handler. The payload is stored raw and returned as-is by /api/v3/user/2. Impact: the administrator opens that user detail modal, the payload executes inside the administrator session and reads the administrator JWT from localStorage.

Endpoint:
POST http://127.0.0.1:5001/update_bio
GET  http://127.0.0.1:5001/api/v3/user/<user_id>
Sink: http://127.0.0.1:5001/sup3r_s3cr3t_admin (user detail modal)

Affected Feature:
User profile bio and the user detail modal in the administrative panel

Remediation:
- Render user-supplied values as text (textContent), not innerHTML; apply escapeHtml to the bio field as is already done for other fields in the same modal.
- Sanitize the bio server-side with an allow-list library both when stored and when returned by the API.
- Apply a Content-Security-Policy that forbids inline scripts and event handlers.
- Store session tokens in HttpOnly and SameSite cookies rather than localStorage so JavaScript cannot reach them.

POC:
1) (Screenshot 1) NORMAL: the bio is filled with plain text via /update_bio -> stored.
2) (Screenshot 2) EXPLOITATION: the bio is filled with an img tag carrying an onerror handler -> accepted without sanitization.
3) (Screenshot 3) SINK: /api/v3/user/2 returns the raw payload as-is, which is the data consumed by the admin panel.
4) (Screenshot 4) NORMAL UI: the administrator opens the user detail modal while the bio contains plain text -> displayed as text.
5) (Screenshot 5) CIA IMPACT: the administrator opens the same user detail modal after the payload is planted -> the script executes inside the administrator session and displays the administrator JWT captured in full.
19 / 26FAIL

Unrestricted File Upload - Profile Picture - File Type Validation

3 screenshots · 3 HTTP captures

Vulnerability

[FAIL] Unrestricted File Upload - Profile Picture - File Type Validation

Description

The /upload_profile_picture endpoint stores any file without validating extension, content-type, or magic bytes, and without a size limit. Files are stored in static/uploads, which the server serves directly, and Flask sets Content-Type based on the file extension.

Impact

Confidentiality + Integrity (High): an attacker can upload an HTML document containing JavaScript and access it on the application origin with a Content-Type of text/html, so the script executes as part of the site. This gives the attacker stored XSS on the application origin, which can be used to steal session tokens from victims who open the link, and it turns the bank's domain into a host for phishing pages.

CWE

CWE-434: Unrestricted Upload of File with Dangerous Type

OWASP

A04:2021 - Insecure Design

Scenario

Normal: a PNG image is uploaded as a profile picture -> expected and accepted. Exploitation: a payload.html file containing a script tag is uploaded through the same endpoint -> accepted without rejection. Impact: the file is retrieved over HTTP and served with Content-Type text/html containing the full script, so it executes on the application origin.

Endpoint

POST http://127.0.0.1:5001/upload_profile_picture

Affected feature

Profile picture upload

Remediation

- Apply an extension allow-list and verify magic bytes; reject anything other than permitted image formats, and re-encode the image. - Store uploaded files outside the web root and serve them through a handler that sets a safe Content-Type and Content-Disposition attachment. - Serve user content from a separate domain without session cookies. - Apply a file size limit and use fully random names.

Proof of concept

1) (Screenshot 1) NORMAL: upload a PNG image file as a profile picture -> accepted. 2) (Screenshot 2) EXPLOITATION: upload payload.html containing a script tag through the same endpoint -> accepted without type validation. 3) (Screenshot 3) CIA IMPACT: the file is accessed via HTTP -> served with Content-Type text/html containing the full script, executing on the application origin.
View source note (English)
Vulnerability:
[FAIL] Unrestricted File Upload - Profile Picture - File Type Validation

Description:
The /upload_profile_picture endpoint stores any file without validating extension, content-type, or magic bytes, and without a size limit. Files are stored in static/uploads, which the server serves directly, and Flask sets Content-Type based on the file extension.

Impact:
Confidentiality + Integrity (High): an attacker can upload an HTML document containing JavaScript and access it on the application origin with a Content-Type of text/html, so the script executes as part of the site. This gives the attacker stored XSS on the application origin, which can be used to steal session tokens from victims who open the link, and it turns the bank's domain into a host for phishing pages.

CWE:
CWE-434: Unrestricted Upload of File with Dangerous Type

OWASP:
A04:2021 - Insecure Design

Scenario:
Normal: a PNG image is uploaded as a profile picture -> expected and accepted. Exploitation: a payload.html file containing a script tag is uploaded through the same endpoint -> accepted without rejection. Impact: the file is retrieved over HTTP and served with Content-Type text/html containing the full script, so it executes on the application origin.

Endpoint:
POST http://127.0.0.1:5001/upload_profile_picture

Affected Feature:
Profile picture upload

Remediation:
- Apply an extension allow-list and verify magic bytes; reject anything other than permitted image formats, and re-encode the image.
- Store uploaded files outside the web root and serve them through a handler that sets a safe Content-Type and Content-Disposition attachment.
- Serve user content from a separate domain without session cookies.
- Apply a file size limit and use fully random names.

POC:
1) (Screenshot 1) NORMAL: upload a PNG image file as a profile picture -> accepted.
2) (Screenshot 2) EXPLOITATION: upload payload.html containing a script tag through the same endpoint -> accepted without type validation.
3) (Screenshot 3) CIA IMPACT: the file is accessed via HTTP -> served with Content-Type text/html containing the full script, executing on the application origin.
20 / 26FAIL

Unrestricted Resource Consumption - Authentication - Login Rate Limiting

3 screenshots · 2 HTTP captures · 1 process records

Vulnerability

[FAIL] Unrestricted Resource Consumption - Authentication - Login Rate Limiting

Description

The /login endpoint applies no rate limiting, account lockout, progressive delay, or CAPTCHA. Consecutive failed login attempts are all served at full speed. Failure responses also differ by condition, making username enumeration easier.

Impact

Confidentiality (Medium): an attacker can brute force and credential stuff any account, including administrator, without restriction. Fifty consecutive attempts against the admin account triggered neither lockout nor delay, and after all those failures a login with the correct credentials was still accepted, proving there is no lockout mechanism at all.

CWE

CWE-307: Improper Restriction of Excessive Authentication Attempts

OWASP

A07:2021 - Identification and Authentication Failures

Scenario

Normal: one login attempt with a wrong password is rejected with HTTP 401. Exploitation: fifty consecutive attempts against the admin account are sent without delay -> all are served HTTP 401, with no HTTP 429 and no lockout. Impact: after 50 failures, logging in with the correct password still succeeds, showing the account is never locked so brute force can run to completion.

Endpoint

POST http://127.0.0.1:5001/login

Affected feature

User authentication

Remediation

- Apply per-account and per-IP rate limiting with progressive delay. - Temporarily lock the account or require CAPTCHA after a number of consecutive failures. - Standardize error messages so they do not disclose username existence. - Log and alert on spikes in authentication failures, and enforce MFA.

Proof of concept

1) (Screenshot 1) NORMAL: one login attempt with a wrong password -> HTTP 401 Invalid credentials. 2) (Screenshot 2) EXPLOITATION: 50 consecutive failed attempts against the admin account -> all HTTP 401, with no HTTP 429 and no lockout. 3) (Screenshot 3) CIA IMPACT: after 50 failures, logging in with the correct password is still accepted -> the account is never locked, so brute force can run until it succeeds.
View source note (English)
Vulnerability:
[FAIL] Unrestricted Resource Consumption - Authentication - Login Rate Limiting

Description:
The /login endpoint applies no rate limiting, account lockout, progressive delay, or CAPTCHA. Consecutive failed login attempts are all served at full speed. Failure responses also differ by condition, making username enumeration easier.

Impact:
Confidentiality (Medium): an attacker can brute force and credential stuff any account, including administrator, without restriction. Fifty consecutive attempts against the admin account triggered neither lockout nor delay, and after all those failures a login with the correct credentials was still accepted, proving there is no lockout mechanism at all.

CWE:
CWE-307: Improper Restriction of Excessive Authentication Attempts

OWASP:
A07:2021 - Identification and Authentication Failures

Scenario:
Normal: one login attempt with a wrong password is rejected with HTTP 401. Exploitation: fifty consecutive attempts against the admin account are sent without delay -> all are served HTTP 401, with no HTTP 429 and no lockout. Impact: after 50 failures, logging in with the correct password still succeeds, showing the account is never locked so brute force can run to completion.

Endpoint:
POST http://127.0.0.1:5001/login

Affected Feature:
User authentication

Remediation:
- Apply per-account and per-IP rate limiting with progressive delay.
- Temporarily lock the account or require CAPTCHA after a number of consecutive failures.
- Standardize error messages so they do not disclose username existence.
- Log and alert on spikes in authentication failures, and enforce MFA.

POC:
1) (Screenshot 1) NORMAL: one login attempt with a wrong password -> HTTP 401 Invalid credentials.
2) (Screenshot 2) EXPLOITATION: 50 consecutive failed attempts against the admin account -> all HTTP 401, with no HTTP 429 and no lockout.
3) (Screenshot 3) CIA IMPACT: after 50 failures, logging in with the correct password is still accepted -> the account is never locked, so brute force can run until it succeeds.
21 / 26FAIL

Unrestricted Resource Consumption - Password Reset - PIN Rate Limiting

5 screenshots · 4 HTTP captures · 1 process records

Vulnerability

[FAIL] Unrestricted Resource Consumption - Password Reset - PIN Rate Limiting

Description

The password reset endpoint applies no rate limiting, account lockout, or limit on the number of PIN attempts. The recovery PIN is only 3 digits, so the guessing space is just 1000 possibilities and can be exhausted in seconds.

Impact

Confidentiality + Integrity (High): account takeover without needing the PIN to leak first. An attacker who only knows the username can trigger a reset request and then guess the entire PIN space. In testing, the victim's PIN was found on attempt 451 and the victim's password was successfully changed, followed by logging in as the victim. This attack works against the v2 and v3 endpoints that do not disclose the PIN, so it remains effective even if the PIN disclosure in v1 is fixed.

CWE

CWE-307: Improper Restriction of Excessive Authentication Attempts

OWASP

A07:2021 - Identification and Authentication Failures

Scenario

Normal: a reset request is submitted through /api/v2/forgot-password and the PIN is not disclosed in the response. Control: one wrong PIN is rejected with Invalid reset PIN. Exploitation: consecutive attempts are sent without delay and all are served with HTTP 400 without a single HTTP 429, lockout, or delay. Impact: the correct PIN is found, the victim's password is changed, and logging in as the victim succeeds.

Endpoint

POST http://127.0.0.1:5001/api/v2/forgot-password POST http://127.0.0.1:5001/api/v2/reset-password

Affected feature

Password recovery (PIN verification)

Remediation

- Apply per-account and per-IP rate limiting, and temporary lockout after a few failed PIN attempts. - Replace the 3-digit PIN with a random token of at least 128 bits, single-use and short-lived. - Invalidate the recovery token after a small number of failed attempts and require a new reset request. - Notify the account owner on every reset request and on successful resets.

Proof of concept

1) (Screenshot 1) NORMAL: reset request via /api/v2/forgot-password -> the PIN is not disclosed in the response. 2) (Screenshot 2) CONTROL: one wrong PIN (000) -> rejected with Invalid reset PIN. 3) (Screenshot 3) EXPLOITATION: consecutive PIN attempts without delay -> all served HTTP 400, with no HTTP 429, lockout, or delay. 4) (Screenshot 4) EXPLOITATION: the correct PIN is found on attempt 451 -> reset_success true, the victim's password is changed. 5) (Screenshot 5) CIA IMPACT: log in as pentest_victim with the brute-forced password -> token issued, the victim account is taken over.
View source note (English)
Vulnerability:
[FAIL] Unrestricted Resource Consumption - Password Reset - PIN Rate Limiting

Description:
The password reset endpoint applies no rate limiting, account lockout, or limit on the number of PIN attempts. The recovery PIN is only 3 digits, so the guessing space is just 1000 possibilities and can be exhausted in seconds.

Impact:
Confidentiality + Integrity (High): account takeover without needing the PIN to leak first. An attacker who only knows the username can trigger a reset request and then guess the entire PIN space. In testing, the victim's PIN was found on attempt 451 and the victim's password was successfully changed, followed by logging in as the victim. This attack works against the v2 and v3 endpoints that do not disclose the PIN, so it remains effective even if the PIN disclosure in v1 is fixed.

CWE:
CWE-307: Improper Restriction of Excessive Authentication Attempts

OWASP:
A07:2021 - Identification and Authentication Failures

Scenario:
Normal: a reset request is submitted through /api/v2/forgot-password and the PIN is not disclosed in the response. Control: one wrong PIN is rejected with Invalid reset PIN. Exploitation: consecutive attempts are sent without delay and all are served with HTTP 400 without a single HTTP 429, lockout, or delay. Impact: the correct PIN is found, the victim's password is changed, and logging in as the victim succeeds.

Endpoint:
POST http://127.0.0.1:5001/api/v2/forgot-password
POST http://127.0.0.1:5001/api/v2/reset-password

Affected Feature:
Password recovery (PIN verification)

Remediation:
- Apply per-account and per-IP rate limiting, and temporary lockout after a few failed PIN attempts.
- Replace the 3-digit PIN with a random token of at least 128 bits, single-use and short-lived.
- Invalidate the recovery token after a small number of failed attempts and require a new reset request.
- Notify the account owner on every reset request and on successful resets.

POC:
1) (Screenshot 1) NORMAL: reset request via /api/v2/forgot-password -> the PIN is not disclosed in the response.
2) (Screenshot 2) CONTROL: one wrong PIN (000) -> rejected with Invalid reset PIN.
3) (Screenshot 3) EXPLOITATION: consecutive PIN attempts without delay -> all served HTTP 400, with no HTTP 429, lockout, or delay.
4) (Screenshot 4) EXPLOITATION: the correct PIN is found on attempt 451 -> reset_success true, the victim's password is changed.
5) (Screenshot 5) CIA IMPACT: log in as pentest_victim with the brute-forced password -> token issued, the victim account is taken over.
22 / 26PASS

Broken Function Level Authorization - Admin - Administrative Endpoints

3 screenshots · 3 HTTP captures

Vulnerability

[PASS] Broken Function Level Authorization - Admin - Administrative Endpoints

Description

The test attempted to call administrative functions directly with an ordinary user token, bypassing the admin panel interface. Tested endpoints included creating a new administrator, deleting accounts, and suspending accounts.

Impact

No impact. Control holds: all three administrative endpoints check the caller's role and reject ordinary user tokens with HTTP 403. Calling them directly without going through the interface grants no additional privileges. Important note: this control is only as safe as the role claim in the token. Because the is_admin claim is read from the token and the token signature is not verified correctly, this role check can be bypassed with a forged token, as proven in the JWT Signature Not Verified finding.

CWE

CWE-285: Improper Authorization

OWASP

A01:2021 - Broken Access Control

Scenario

An ordinary user token was used to call /admin/create_admin, /admin/delete_account/3, and /admin/toggle_suspension/3 directly. Result: all were rejected with HTTP 403.

Endpoint

POST http://127.0.0.1:5001/admin/create_admin POST http://127.0.0.1:5001/admin/delete_account/<user_id> POST http://127.0.0.1:5001/admin/toggle_suspension/<user_id>

Affected feature

Administrative functions (create admin, delete account, suspend account)

Remediation

- Keep the role check on all administrative endpoints. - Fetch the role from the database using the token subject rather than from a claim inside the token, so this control cannot be bypassed with a forged token.

Proof of concept

1) (Screenshot 1) ATTEMPT: an ordinary user token calls /admin/create_admin -> HTTP 403, control holds. 2) (Screenshot 2) ATTEMPT: an ordinary user token calls /admin/delete_account/3 -> HTTP 403, control holds. 3) (Screenshot 3) ATTEMPT: an ordinary user token calls /admin/toggle_suspension/3 -> HTTP 403, control holds.
View source note (English)
Vulnerability:
[PASS] Broken Function Level Authorization - Admin - Administrative Endpoints

Description:
The test attempted to call administrative functions directly with an ordinary user token, bypassing the admin panel interface. Tested endpoints included creating a new administrator, deleting accounts, and suspending accounts.

Impact:
No impact. Control holds: all three administrative endpoints check the caller's role and reject ordinary user tokens with HTTP 403. Calling them directly without going through the interface grants no additional privileges.

Important note: this control is only as safe as the role claim in the token. Because the is_admin claim is read from the token and the token signature is not verified correctly, this role check can be bypassed with a forged token, as proven in the JWT Signature Not Verified finding.

CWE:
CWE-285: Improper Authorization

OWASP:
A01:2021 - Broken Access Control

Scenario:
An ordinary user token was used to call /admin/create_admin, /admin/delete_account/3, and /admin/toggle_suspension/3 directly. Result: all were rejected with HTTP 403.

Endpoint:
POST http://127.0.0.1:5001/admin/create_admin
POST http://127.0.0.1:5001/admin/delete_account/<user_id>
POST http://127.0.0.1:5001/admin/toggle_suspension/<user_id>

Affected Feature:
Administrative functions (create admin, delete account, suspend account)

Remediation:
- Keep the role check on all administrative endpoints.
- Fetch the role from the database using the token subject rather than from a claim inside the token, so this control cannot be bypassed with a forged token.

POC:
1) (Screenshot 1) ATTEMPT: an ordinary user token calls /admin/create_admin -> HTTP 403, control holds.
2) (Screenshot 2) ATTEMPT: an ordinary user token calls /admin/delete_account/3 -> HTTP 403, control holds.
3) (Screenshot 3) ATTEMPT: an ordinary user token calls /admin/toggle_suspension/3 -> HTTP 403, control holds.
23 / 26PASS

Broken Object Level Authorization - GraphQL - Transaction Summary

3 screenshots · 3 HTTP captures

Vulnerability

[PASS] Broken Object Level Authorization - GraphQL - Transaction Summary

Description

The test attempted to read another user's transaction summary through the accountNumber argument of the GraphQL transactionSummary resolver, including the victim's account and the administrator's account.

Impact

No impact. Control holds: the resolver compares the requested accountNumber with the account owned by the token holder and rejects cross-object requests with an explicit error. Ordinary users can only read the transaction summary of their own account. Note: the same code path is still flawed for callers with the administrator role, because the argument value is passed into the SQL query without parameterization; this is reported separately in the SQL Injection - GraphQL finding.

CWE

CWE-639: Authorization Bypass Through User-Controlled Key

OWASP

A01:2021 - Broken Access Control

Scenario

Normal: an ordinary user requests their own account transaction summary -> succeeds. Attempt: accountNumber is changed to the victim's account -> rejected. Attempt: accountNumber is changed to the administrator's account -> rejected.

Endpoint

POST http://127.0.0.1:5001/graphql

Affected feature

GraphQL transactionSummary (object authorization)

Remediation

- Keep the account ownership check in the resolver. - Add parameterized queries so the same argument does not become an injection point for higher-privileged callers.

Proof of concept

1) (Screenshot 1) NORMAL: an ordinary user requests their own account transaction summary -> succeeds. 2) (Screenshot 2) ATTEMPT: accountNumber is changed to the victim's account -> rejected with the error You can only query your own transaction summary, control holds. 3) (Screenshot 3) ATTEMPT: accountNumber is changed to the administrator's account -> rejected, control holds.
View source note (English)
Vulnerability:
[PASS] Broken Object Level Authorization - GraphQL - Transaction Summary

Description:
The test attempted to read another user's transaction summary through the accountNumber argument of the GraphQL transactionSummary resolver, including the victim's account and the administrator's account.

Impact:
No impact. Control holds: the resolver compares the requested accountNumber with the account owned by the token holder and rejects cross-object requests with an explicit error. Ordinary users can only read the transaction summary of their own account.

Note: the same code path is still flawed for callers with the administrator role, because the argument value is passed into the SQL query without parameterization; this is reported separately in the SQL Injection - GraphQL finding.

CWE:
CWE-639: Authorization Bypass Through User-Controlled Key

OWASP:
A01:2021 - Broken Access Control

Scenario:
Normal: an ordinary user requests their own account transaction summary -> succeeds. Attempt: accountNumber is changed to the victim's account -> rejected. Attempt: accountNumber is changed to the administrator's account -> rejected.

Endpoint:
POST http://127.0.0.1:5001/graphql

Affected Feature:
GraphQL transactionSummary (object authorization)

Remediation:
- Keep the account ownership check in the resolver.
- Add parameterized queries so the same argument does not become an injection point for higher-privileged callers.

POC:
1) (Screenshot 1) NORMAL: an ordinary user requests their own account transaction summary -> succeeds.
2) (Screenshot 2) ATTEMPT: accountNumber is changed to the victim's account -> rejected with the error You can only query your own transaction summary, control holds.
3) (Screenshot 3) ATTEMPT: accountNumber is changed to the administrator's account -> rejected, control holds.
24 / 26PASS

Broken Object Level Authorization - Merchant Payments - Payment By ID

2 screenshots · 2 HTTP captures

Vulnerability

[PASS] Broken Object Level Authorization - Merchant Payments - Payment By ID

Description

The test attempted to read the details of another merchant's transaction through /api/v1/payments/<payment_id> using a different merchant's API key.

Impact

No impact. Control holds: the endpoint limits the lookup to transactions owned by the merchant holding the API key, so cross-merchant requests receive Payment not found instead of disclosing data. Note: another endpoint in the same module does NOT apply this restriction. /api/v1/payments/merchant_id/<merchant_id> returns other merchants' transactions and is reported separately as the Broken Object Level Authorization - Merchant Payments - Payments By Merchant finding.

CWE

CWE-639: Authorization Bypass Through User-Controlled Key

OWASP

A01:2021 - Broken Access Control

Scenario

Normal: ScopeShopA reads its own transaction by payment_id using its own API key -> succeeds. Attempt: ScopeShopB reads the same payment_id using its own API key -> rejected with Payment not found.

Endpoint

GET http://127.0.0.1:5001/api/v1/payments/<payment_id>

Affected feature

Merchant payments (transaction detail by id)

Remediation

- Keep the restriction that limits transaction lookups to the merchant owning the API key. - Apply the same pattern across all payment module endpoints, especially /api/v1/payments/merchant_id/<merchant_id> which currently does not restrict access.

Proof of concept

1) (Screenshot 1) NORMAL: ScopeShopA reads its own transaction using its own API key -> succeeds. 2) (Screenshot 2) ATTEMPT: ScopeShopB reads the same payment_id using its own API key -> rejected with Payment not found, control holds.
View source note (English)
Vulnerability:
[PASS] Broken Object Level Authorization - Merchant Payments - Payment By ID

Description:
The test attempted to read the details of another merchant's transaction through /api/v1/payments/<payment_id> using a different merchant's API key.

Impact:
No impact. Control holds: the endpoint limits the lookup to transactions owned by the merchant holding the API key, so cross-merchant requests receive Payment not found instead of disclosing data.

Note: another endpoint in the same module does NOT apply this restriction. /api/v1/payments/merchant_id/<merchant_id> returns other merchants' transactions and is reported separately as the Broken Object Level Authorization - Merchant Payments - Payments By Merchant finding.

CWE:
CWE-639: Authorization Bypass Through User-Controlled Key

OWASP:
A01:2021 - Broken Access Control

Scenario:
Normal: ScopeShopA reads its own transaction by payment_id using its own API key -> succeeds. Attempt: ScopeShopB reads the same payment_id using its own API key -> rejected with Payment not found.

Endpoint:
GET http://127.0.0.1:5001/api/v1/payments/<payment_id>

Affected Feature:
Merchant payments (transaction detail by id)

Remediation:
- Keep the restriction that limits transaction lookups to the merchant owning the API key.
- Apply the same pattern across all payment module endpoints, especially /api/v1/payments/merchant_id/<merchant_id> which currently does not restrict access.

POC:
1) (Screenshot 1) NORMAL: ScopeShopA reads its own transaction using its own API key -> succeeds.
2) (Screenshot 2) ATTEMPT: ScopeShopB reads the same payment_id using its own API key -> rejected with Payment not found, control holds.
25 / 26PASS

JWT Algorithm Confusion - Authentication - alg none

3 screenshots · 2 HTTP captures · 1 process records

Vulnerability

[PASS] JWT Algorithm Confusion - Authentication - alg none

Description

The test attempted to bypass JWT signature verification by changing the algorithm header to none and emptying the signature section, a classic token forgery technique. The source code lists none among the accepted algorithms, so this endpoint was tested specifically.

Impact

No impact. Control holds: tokens with alg none are rejected on every tested endpoint, both ordinary user endpoints and the administrative panel, with HTTP 401. The JWT library in use rejects the none algorithm when a verification key is provided, so the permissive code intent cannot be exploited through this path. Note: this does NOT mean token validation is safe. Another forgery path succeeds and is reported separately in the JWT Signature Not Verified finding, where tokens with an invalid signature are still accepted.

CWE

CWE-347: Improper Verification of Cryptographic Signature

OWASP

A07:2021 - Identification and Authentication Failures

Scenario

A legitimate token is decoded, the header is changed to alg none, the is_admin claim is changed to true, then the token is reassembled without a signature. The token is sent to the user endpoint and the administrative endpoint. Result: both reject it with HTTP 401.

Endpoint

GET http://127.0.0.1:5001/dashboard GET http://127.0.0.1:5001/sup3r_s3cr3t_admin

Affected feature

JWT validation (none algorithm)

Remediation

- Keep rejecting the none algorithm, and remove none from the configured algorithm list so the code intent matches library behavior. - Pin the algorithm explicitly to a single expected value.

Proof of concept

1) (Screenshot 1) PROCESS: a legitimate token is decoded, the header is changed to alg none and the is_admin claim to true, then it is reassembled without a signature. 2) (Screenshot 2) RESULT: the alg none token is sent to /dashboard -> HTTP 401 Invalid token, control holds. 3) (Screenshot 3) RESULT: the alg none token is sent to /sup3r_s3cr3t_admin -> HTTP 401, control holds.
View source note (English)
Vulnerability:
[PASS] JWT Algorithm Confusion - Authentication - alg none

Description:
The test attempted to bypass JWT signature verification by changing the algorithm header to none and emptying the signature section, a classic token forgery technique. The source code lists none among the accepted algorithms, so this endpoint was tested specifically.

Impact:
No impact. Control holds: tokens with alg none are rejected on every tested endpoint, both ordinary user endpoints and the administrative panel, with HTTP 401. The JWT library in use rejects the none algorithm when a verification key is provided, so the permissive code intent cannot be exploited through this path.

Note: this does NOT mean token validation is safe. Another forgery path succeeds and is reported separately in the JWT Signature Not Verified finding, where tokens with an invalid signature are still accepted.

CWE:
CWE-347: Improper Verification of Cryptographic Signature

OWASP:
A07:2021 - Identification and Authentication Failures

Scenario:
A legitimate token is decoded, the header is changed to alg none, the is_admin claim is changed to true, then the token is reassembled without a signature. The token is sent to the user endpoint and the administrative endpoint. Result: both reject it with HTTP 401.

Endpoint:
GET http://127.0.0.1:5001/dashboard
GET http://127.0.0.1:5001/sup3r_s3cr3t_admin

Affected Feature:
JWT validation (none algorithm)

Remediation:
- Keep rejecting the none algorithm, and remove none from the configured algorithm list so the code intent matches library behavior.
- Pin the algorithm explicitly to a single expected value.

POC:
1) (Screenshot 1) PROCESS: a legitimate token is decoded, the header is changed to alg none and the is_admin claim to true, then it is reassembled without a signature.
2) (Screenshot 2) RESULT: the alg none token is sent to /dashboard -> HTTP 401 Invalid token, control holds.
3) (Screenshot 3) RESULT: the alg none token is sent to /sup3r_s3cr3t_admin -> HTTP 401, control holds.
26 / 26PASS

Path Traversal - Profile Picture - Upload Filename

2 screenshots · 2 HTTP captures

Vulnerability

[PASS] Path Traversal - Profile Picture - Upload Filename

Description

The test attempted to write a file outside the upload directory by inserting ../ sequences into the client-supplied filename. A comment in the source code states that path traversal is possible, so this endpoint was tested specifically.

Impact

No impact. Control holds: Werkzeug's secure_filename function flattens the filename so ../ sequences are removed and the file remains inside the static/uploads directory. Writing a file to an arbitrary location was not achieved. Note: the same endpoint is still flawed because it does not validate file type; this is reported separately in the Unrestricted File Upload finding.

CWE

CWE-22: Improper Limitation of a Pathname to a Restricted Directory (Path Traversal)

OWASP

A01:2021 - Broken Access Control

Scenario

A file is uploaded with the name ../../../../tmp/pwned.txt intended to escape the upload directory. Result: the filename is flattened by secure_filename and the file is stored inside static/uploads, not in /tmp. Verification was done by retrieving the file at its actual storage location.

Endpoint

POST http://127.0.0.1:5001/upload_profile_picture

Affected feature

Profile picture upload (filename handling)

Remediation

- Keep using secure_filename. - Ignore the client-supplied filename entirely and generate a random name server-side. - Verify that the final path is inside the upload directory before writing the file.

Proof of concept

1) (Screenshot 1) ATTEMPT: a file is uploaded with the name ../../../../tmp/pwned.txt -> the name is flattened, the file is stored inside static/uploads. 2) (Screenshot 2) RESULT: the file is retrieved at its actual storage location inside static/uploads, proving traversal did not occur and the control holds.
View source note (English)
Vulnerability:
[PASS] Path Traversal - Profile Picture - Upload Filename

Description:
The test attempted to write a file outside the upload directory by inserting ../ sequences into the client-supplied filename. A comment in the source code states that path traversal is possible, so this endpoint was tested specifically.

Impact:
No impact. Control holds: Werkzeug's secure_filename function flattens the filename so ../ sequences are removed and the file remains inside the static/uploads directory. Writing a file to an arbitrary location was not achieved.

Note: the same endpoint is still flawed because it does not validate file type; this is reported separately in the Unrestricted File Upload finding.

CWE:
CWE-22: Improper Limitation of a Pathname to a Restricted Directory (Path Traversal)

OWASP:
A01:2021 - Broken Access Control

Scenario:
A file is uploaded with the name ../../../../tmp/pwned.txt intended to escape the upload directory. Result: the filename is flattened by secure_filename and the file is stored inside static/uploads, not in /tmp. Verification was done by retrieving the file at its actual storage location.

Endpoint:
POST http://127.0.0.1:5001/upload_profile_picture

Affected Feature:
Profile picture upload (filename handling)

Remediation:
- Keep using secure_filename.
- Ignore the client-supplied filename entirely and generate a random name server-side.
- Verify that the final path is inside the upload directory before writing the file.

POC:
1) (Screenshot 1) ATTEMPT: a file is uploaded with the name ../../../../tmp/pwned.txt -> the name is flattened, the file is stored inside static/uploads.
2) (Screenshot 2) RESULT: the file is retrieved at its actual storage location inside static/uploads, proving traversal did not occur and the control holds.
04 / ORIGINAL DOCUMENT

Original PDF report

Read the original PDF below, or open it in a new tab.

If the PDF does not display: Open PDF (EN) ↗

The HTML report presents the test notes and supporting material, not a word-for-word copy of the PDF. Refer to the PDF for the original report and layout.