Version: v1.2.2
- Official Docker image:
razor29/rva:latestis the default image used by the compose files. - Protected Entity Mode: Prevents destructive changes to protected demo users/products while keeping non-protected objects fully exploitable.
- Legacy endpoints maintained: Deprecated address search and legacy order status endpoints remain available for demo scenarios (with intentional BOLA behavior).
- Legacy B2B partner lookup: Unauthenticated endpoint that returns internal pricing snapshots for demonstration.
- Multi-DB support:
memory,sqlite, andexternalmodes with optional peer sync.
Purpose: This project is a deliberately vulnerable API-based e-commerce application built using Python and FastAPI. Its primary goal is to demonstrate common API security vulnerabilities, with a strong focus on Broken Object Level Authorization (BOLA), Broken Function Level Authorization (BFLA), and Mass Assignment/Parameter Pollution. The API is designed to primarily use path and query parameters for interactions, minimizing the use of request bodies for vulnerable endpoints to simulate specific attack vectors.
Design:
- Pure API-focused: A minimal Flask-based demo UI is included under
frontend/, but the primary interaction is via API clients (e.g., Postman, curl, custom scripts). - Framework: FastAPI (Python) for its modern features, speed, and automatic OpenAPI documentation.
- Database: A simple in-memory Python dictionary acts as the database to keep the setup lightweight and focus on API logic rather than database intricacies. Data is ephemeral and resets on application restart.
- Authentication: JWT (JSON Web Tokens) are used for authenticating users. Tokens are passed via Authorization headers (
Bearer <token>). Access tokens expire after 3 hours; the UI redirects to login when they are expired or invalid. - Vulnerability Focus: Endpoints are intentionally designed with security flaws to serve as learning examples.
The application simulates basic e-commerce functionalities:
- User Management:
- User registration (
POST /api/auth/register) - User login (
POST /api/auth/login) - Get user details (
GET /api/users/{user_id}) - Update user details (
PUT /api/users/{user_id}) - Delete user (
DELETE /api/users/{user_id})
- User registration (
- Product Catalog:
- Create product (
POST /api/products/) - Get all products (
GET /api/products/) - Get product by ID (
GET /api/products/{product_id}) - Search products by name (
GET /api/products/search) - Update product (
PUT /api/products/{product_id}) - Delete product (
DELETE /api/products/{product_id})
- Create product (
- Stock Management:
- Update product stock (
PUT /api/stock/{product_id})
- Update product stock (
- User Profiles (Addresses & Credit Cards):
- Manage user addresses (CRUD operations under
/api/users/{user_id}/addresses) - Manage user credit cards (CRUD operations under
/api/users/{user_id}/credit-cards) - Note: Card numbers are "hashed" for storage simulation.
- Manage user addresses (CRUD operations under
- Order Management:
- Create an order for a user (
POST /api/users/{user_id}/orders) - Get orders for a user (
GET /api/users/{user_id}/orders) - Get specific order details (
GET /api/users/{user_id}/orders/{order_id})
- Create an order for a user (
- Coupons & Discounts:
- Look up a coupon (
GET /api/coupons/{coupon_code}) - Create coupon (
POST /api/admin/coupons) – no admin check (BFLA demo) - Delete coupon (
DELETE /api/admin/coupons/{coupon_code_or_id}) - Apply coupon to an order (
POST /api/users/{user_id}/orders/{order_id}/apply-coupon?coupon_code=...)
- Look up a coupon (
- Language: Python 3.9+
- Framework: FastAPI
- Dependencies:
fastapi: Web frameworkuvicorn[standard]: ASGI serverpydantic: Data validation and settings managementpython-jose[cryptography]: JWT handlingpasslib[bcrypt]: Password hashing
- API Specification: OpenAPI 3.x. The schema is defined in
openapi.yamland also accessible interactively via:- Swagger UI:
http://localhost:8000/docs - ReDoc:
http://localhost:8000/redoc
- Swagger UI:
This application intentionally includes the following vulnerabilities:
- Description: Users can access or modify data objects belonging to other users by manipulating object IDs in the request (typically in path parameters or query parameters). The application fails to verify if the authenticated user has the right to perform the requested action on the specific object.
- Note: Destructive actions on protected users themselves still return HTTP 403. Addresses and credit cards belonging to a protected user can usually be modified or removed, but attempts fail if they would delete the user's last item. Protected users are free to set any of their addresses or cards as the default; the prior default simply has
is_defaultcleared. - Affected Endpoints & Exploitation:
- User Details:
GET /api/users/{user_id}: Any authenticated user can view another user's details by providing theiruser_id.PUT /api/users/{user_id}: Any authenticated user can attempt to update another user's details.
- User Addresses: (Operate on
user_idfrom path without matching authenticated user)GET /api/users/{user_id}/addressesPOST /api/users/{user_id}/addressesGET /api/users/{user_id}/addresses/{address_id}PUT /api/users/{user_id}/addresses/{address_id}DELETE /api/users/{user_id}/addresses/{address_id}
- User Credit Cards: (Operate on
user_idfrom path without matching authenticated user)GET /api/users/{user_id}/credit-cardsPOST /api/users/{user_id}/credit-cardsGET /api/users/{user_id}/credit-cards/{card_id}DELETE /api/users/{user_id}/credit-cards/{card_id}
- User Orders: (Operate on
user_idfrom path without matching authenticated user)GET /api/users/{user_id}/ordersPOST /api/users/{user_id}/orders:- BOLA on
user_idin path: An attacker can place an order for another user. - BOLA on
address_id&credit_card_idin query parameters: An attacker can use their own token but specify another user's address or credit card ID (if known) to place an order, potentially charging it to another user or shipping to an unauthorized address.
- BOLA on
GET /api/users/{user_id}/orders/{order_id}
- User Details:
- How to Test:
- Register two users, User A and User B.
- Authenticate as User A.
- Attempt to access/modify User B's resources using User B's
user_idin the path or User B'saddress_id/card_idin query parameters where applicable.
This often manifests as Mass Assignment or Parameter Pollution, where users can illegitimately modify object properties.
- Description: The application allows users to modify sensitive object properties (e.g.,
is_admin,internal_status) that they should not have control over, typically by including them as unexpected query parameters. - Affected Endpoints & Exploitation:
PUT /api/users/{user_id}?is_admin=true: A regular user can attempt to escalate their privileges to admin by updating their own profile and addingis_admin=trueas a query parameter. The endpoint improperly processes this parameter.PUT /api/products/{product_id}?internal_status=discontinued: A user (even non-admin due to BFLA) can modify a product'sinternal_statusfield, which should ideally be restricted.
- How to Test:
- Authenticate as a regular user.
- Call
PUT /users/{your_user_id}with a valid payload for updatable fields (e.g., email) and append&is_admin=true(or?is_admin=trueif no other query params) to the URL. Check if the user'sis_adminstatus changes. - Call
PUT /products/{product_id}and append&internal_status=some_valueto the URL.
- Description: Regular users can access administrative functions or functionalities reserved for privileged users because the application does not adequately check the user's role or permissions before granting access to these functions.
- Affected Endpoints & Exploitation:
- Note: Deleting or modifying protected demo entities returns HTTP 403 with a "protected for demo" message.
POST /api/products/: Any authenticated user can create new products (typically an admin function).DELETE /api/products/{product_id}: Any authenticated user can delete products.PUT /api/stock/{product_id}: Any authenticated user can update product stock levels. If the product is protected, the action is logged but still allowed.DELETE /api/users/{user_id}: Any authenticated user can delete any other user if they know theiruser_id. This is a combination of BFLA (no admin check for delete function) and BOLA (can target any user).
- How to Test:
- Authenticate as a regular (non-admin) user.
- Attempt to call the administrative endpoints listed above.
- Description: This category covers security flaws resulting from improper configuration or setup.
- Manifestations in this project:
- Hardcoded Secrets: The
SECRET_KEYfor JWT signing is hardcoded inapp/security.py. In a real application, this should be sourced from environment variables or a secure configuration management system. - Verbose Errors (Potentially): While FastAPI handles many errors gracefully, detailed stack traces might be exposed if
debug=Truewere used in a production-like setting (uvicorn default is often non-debug). - Intentional Vulnerabilities by Design: The entire application is "misconfigured" to be vulnerable for educational purposes.
- Hardcoded Secrets: The
- How to Test: Review
app/security.pyfor the hardcoded secret.
- Description: The application might be vulnerable to injection attacks if user input is not properly sanitized before being used in queries or commands.
- Affected Endpoints & Exploitation:
GET /api/products/search?name=<query>: Thenamequery parameter is used for searching products. While the current in-memory search is a simple stringincheck, if this were backed by a SQL database and the query constructed unsafely, it could be vulnerable to SQL Injection. The current implementation might allow for unexpected behavior depending on how the substring search is performed with special characters.
- How to Test:
- Try injecting various characters and sequences into the
nameparameter of the product search endpoint (e.g.,',"*,;, basic XSS payloads if it were reflected, etc.) to observe behavior. For the current in-memory setup, the impact is limited, but it demonstrates an input vector.
- Try injecting various characters and sequences into the
- Docker (Recommended)
- Python 3.9+ (if running locally without Docker)
- Git (for cloning, though not strictly necessary if files are manually downloaded)
The application selects its database backend based on environment variables:
DB_MODE– one ofmemory(default),sqlite, orexternal.DB_SQLITE_PATH– location for the SQLite file when usingDB_MODE=sqlite. Defaults to/app/data/db.sqlitein the container.DB_URL– used only whenDB_MODE=external. Provide a full SQLAlchemy connection string. If the URL points to a SQLite file the built-in SQLite backend is reused. For other databases (e.g., PostgreSQL/MySQL) make sure the appropriate drivers are installed.DB_SYNC_PEER– optional base URL of another instance for database synchronization.DB_SYNC_INTERVAL– polling interval in seconds whenDB_SYNC_PEERis set (default: 60).DB_SKIP_SCHEMA_INIT– set totrueto skip automatic table creation. Use this when the database schema is managed externally.DB_SKIP_AUTO_SEED– set totrueto skip loadingprepopulated_data.jsonon startup. Useful when a peer or init job already seeded the data.DB_RESET_ON_START– set totrueto drop and recreate tables fromprepopulated_data.jsonon startup (intended for demos/tests).DB_STARTUP_LOCK– set tofalseto disable the advisory lock used to serialize schema creation and auto-seeding across multiple workers (default: enabled where supported).
In-memory database (ephemeral)
docker run -d -p 8000:80 \
-e DB_MODE=memory \
--name radware-vuln-api razor29/rva:latestData is stored only in memory and will be lost when the container stops.
SQLite inside the container
docker run -d -p 8000:80 \
-e DB_MODE=sqlite \
-e DB_SQLITE_PATH=/data/db.sqlite \
-v $(pwd)/data:/data \
--name radware-vuln-api razor29/rva:latestThis stores the SQLite database in ./data/db.sqlite on the host. Remove the -v option if you want the database kept only inside the container and discarded when it is removed.
External database
docker run -d -p 8000:80 \
-e DB_MODE=external \
-e DB_URL=postgresql+psycopg2://user:pass@dbserver/dbname \
--name radware-vuln-api razor29/rva:latestReplace the connection string with one appropriate for your database engine.
Peer sync
docker run -d -p 8000:80 \
-e DB_SYNC_PEER=http://other-instance:8000 \
-e DB_SYNC_INTERVAL=30 \
--name radware-vuln-api razor29/rva:latestThe service will periodically push and pull data with the peer.
-
Build the Docker image: Open a terminal in the project's root directory (where
Dockerfileis located) and run:docker build -t razor29/rva:latest . -
Run the Docker container:
docker run -d -p 8000:80 --name radware-vuln-api razor29/rva:latest
The API will be accessible at
http://localhost:8000.The container defaults to a single Uvicorn worker. Set
UVICORN_WORKERSif you need additional workers:docker run -d -p 8000:80 -e UVICORN_WORKERS=2 \ --name radware-vuln-api razor29/rva:latest
-
Clone the Repository (if applicable): If you have the project as a git repository:
git clone <repository_url> cd <repository_directory>
Otherwise, ensure you are in the project's root directory.
-
Create and Activate a Virtual Environment:
python3 -m venv venv source venv/bin/activate # On Windows: venv\Scripts\activate
-
Install Dependencies:
pip install -r requirements.txt
-
Run the Application:
uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
The
--reloadflag enables auto-reloading on code changes, useful for development. To run both the API and the demo UI together, use the providedrun_dev.shscript instead.
- Base URL:
http://localhost:8000 - Interactive API Documentation (Swagger UI):
http://localhost:8000/docs - Alternative API Documentation (ReDoc):
http://localhost:8000/redoc
- DO NOT deploy this application in a production environment or any publicly accessible network.
- It contains severe security flaws by design.
- Use responsibly and ethically for learning and demonstration.
This table summarizes the intentionally implemented vulnerabilities and their exploitation methods:
| Vulnerability Type (OWASP API 2023) | Brief Description | Example Exploitable Endpoint(s) | Exploitation Method Summary |
|---|---|---|---|
| API1:2023 - Broken Object Level Authorization (BOLA) | Users can access or modify data objects belonging to other users by manipulating object IDs in the request (path/query). Protected users themselves cannot be deleted and their usernames are immutable, but their addresses and credit cards may be edited or removed unless it would delete the last item. Setting a new default is allowed; the prior default simply becomes non‑default. Non-protected objects remain fully exploitable. | GET /api/users/{user_id}PUT /api/users/{user_id}GET /api/users/{user_id}/addressesPOST /api/users/{user_id}/addressesGET /api/users/{user_id}/credit-cardsPOST /api/users/{user_id}/credit-cardsPUT /api/users/{user_id}/credit-cards/{card_id}DELETE /api/users/{user_id}/credit-cards/{card_id}GET /api/users/{user_id}/ordersPOST /api/users/{user_id}/orders |
Manipulate user_id in path or use another user's address_id/credit_card_id in query to access, create, update, or delete resources for other users (subject to the protected-item rules) |
| API3:2023 - Broken Object Property Level Authorization (Parameter Pollution / Mass Assignment) | Users can modify sensitive object properties (e.g., is_admin, internal_status) by including them as query parameters, even if not intended. Protected fields like username or email on protected users are immutable, but attributes like is_admin remain modifiable for demo purposes. |
PUT /api/users/{user_id}?is_admin=truePUT /api/products/{product_id}?internal_status=discontinued |
Add privileged or internal fields as query parameters to escalate privileges or change internal state |
| API5:2023 - Broken Function Level Authorization (BFLA) | Regular users can access admin-only functions (e.g., product management, user deletion) due to missing role checks. | POST /api/productsDELETE /api/products/{product_id}PUT /api/stock/{product_id}DELETE /api/users/{user_id} |
Call admin endpoints as a regular user (no admin check; attempts against protected demo entities return 403 but succeed on others) |
| API8:2023 - Security Misconfiguration | Hardcoded secrets, verbose errors, and intentional misconfiguration for demonstration. | app/security.pyApplication config |
Hardcoded JWT secret, potential for verbose error output |
| Potential for Injection | Naive input handling in product search could allow for injection if backed by a real DB. | GET /api/products/search?name=<query> |
Pass special characters or payloads in name parameter (see homepage.spec.ts) |
| Step | HTTP Method | Endpoint | Purpose/Parameters |
|---|---|---|---|
| 1 | POST | /api/auth/register?username=alice&email=alice@example.com&password=Password123! |
Register a new user |
| 2 | POST | /api/auth/login?username=alice&password=Password123! |
Login, obtain JWT token |
| 3 | GET | /api/products |
Browse product catalog |
| 4 | GET | /api/users/{user_id}/addresses |
List addresses (empty initially) |
| 5 | POST | /api/users/{user_id}/addresses?street=Main%20St&city=Townsville&country=USA&zip_code=12345&is_default=true |
Add a shipping address |
| 6 | GET | /api/users/{user_id}/credit-cards |
List credit cards (empty initially) |
| 7 | POST | /api/users/{user_id}/credit-cards?cardholder_name=Alice&card_number=4111111111111111&expiry_month=12&expiry_year=2029&cvv=123&is_default=true |
Add a credit card |
| 8 | POST | /api/users/{user_id}/orders?address_id={address_id}&credit_card_id={card_id}&product_id_1={product_id}&quantity_1=1 |
Place an order |
| 9 | GET | /api/users/{user_id}/orders |
List user's orders |
| Step | HTTP Method | Endpoint | Purpose/Parameters |
|---|---|---|---|
| 1 | POST | /api/auth/register?username=admin&email=admin@example.com&password=AdminPass123! |
Register admin user (set is_admin via parameter pollution, see below) |
| 2 | POST | /api/auth/login?username=admin&password=AdminPass123! |
Login as admin |
| 3 | POST | /api/products?name=New%20Product&price=99.99&description=Demo&category=Test |
Create a new product |
| 4 | PUT | /api/products/{product_id}?price=89.99 |
Update product price |
| 5 | DELETE | /api/products/{product_id} |
Delete product |
| Step | HTTP Method | Endpoint | Purpose/Parameters |
|---|---|---|---|
| 1 | POST | /api/auth/login?username=alice&password=Password123! |
Login as user |
| 2 | PUT | /api/users/{user_id}?email=newalice@example.com |
Update email address |
| 3 | GET | /api/users/{user_id} |
Retrieve updated profile |
| Step | HTTP Method | Endpoint | Purpose/Parameters |
|---|---|---|---|
| 1 | POST | /api/auth/login?username=alice&password=Password123! |
Login as user |
| 2 | POST | /api/users/{user_id}/addresses?street=Second%20St&city=Townsville&country=USA&zip_code=54321&is_default=false |
Add a second address |
| 3 | GET | /api/users/{user_id}/addresses |
List all addresses |
| 4 | DELETE | /api/users/{user_id}/addresses/{address_id} |
Remove an address |
| Step | HTTP Method | Endpoint | Purpose/Parameters |
|---|---|---|---|
| 1 | POST | /api/auth/login?username=alice&password=Password123! |
Login as user |
| 2 | POST | /api/users/{user_id}/credit-cards?cardholder_name=Alice%20B&card_number=4222222222222222&expiry_month=11&expiry_year=2030&cvv=456&is_default=false |
Add a second credit card |
| 3 | GET | /api/users/{user_id}/credit-cards |
List all credit cards |
| 4 | DELETE | /api/users/{user_id}/credit-cards/{card_id} |
Remove a credit card |
| Step | HTTP Method | Endpoint | Purpose/Attack |
|---|---|---|---|
| 1 | POST | /api/auth/login?username=attacker&password=AttackerPass! |
Attacker logs in, obtains token |
| 2 | GET | /api/users/{victim_user_id} |
Attacker accesses victim's profile |
| 3 | GET | /api/users/{victim_user_id}/orders |
Attacker lists victim's orders |
| 4 | POST | /api/users/{victim_user_id}/addresses?... |
Attacker creates address for victim |
| 5 | POST | /api/users/{victim_user_id}/orders?address_id={victim_address_id}&credit_card_id={victim_card_id}&product_id_1={product_id}&quantity_1=1 |
Attacker places order for victim (using victim's address/card) |
| Step | HTTP Method | Endpoint | Purpose/Attack |
|---|---|---|---|
| 1 | POST | /api/auth/login?username=regular&password=RegularPass! |
Regular user logs in |
| 2 | POST | /api/products?name=Malicious%20Product&price=1.00 |
Regular user creates a product (should be admin-only) |
| 3 | DELETE | /api/products/{product_id} |
Regular user deletes a product |
| 4 | PUT | /api/stock/{product_id}?quantity=100 |
Regular user updates product stock |
| 5 | DELETE | /api/users/{victim_user_id} |
Regular user deletes another user |
| Step | HTTP Method | Endpoint | Purpose/Attack |
|---|---|---|---|
| 1 | POST | /api/auth/register?username=evil&email=evil@example.com&password=EvilPass! |
Register as a regular user |
| 2 | POST | /api/auth/login?username=evil&password=EvilPass! |
Login as evil user |
| 3 | PUT | /api/users/{evil_user_id}?is_admin=true |
Escalate privileges to admin via query parameter |
| 4 | POST | /api/products?name=Backdoor&price=0.01 |
Now create a product as an admin |
| Step | HTTP Method | Endpoint | Purpose/Attack |
|---|---|---|---|
| 1 | POST | /api/auth/login?username=attacker&password=AttackerPass! |
Attacker logs in |
| 2 | POST | /api/users/{victim_user_id}/addresses?street=Hacked%20St&city=Exploit&country=Nowhere&zip_code=99999&is_default=false |
Attacker creates address for victim |
| 3 | GET | /api/users/{victim_user_id}/addresses |
Attacker verifies address was created |
| Step | HTTP Method | Endpoint | Purpose/Attack |
|---|---|---|---|
| 1 | POST | /api/auth/login?username=attacker&password=AttackerPass! |
Attacker logs in |
| 2 | GET | /api/users/{victim_user_id}/credit-cards |
Attacker lists victim's credit cards |
| 3 | POST | /api/users/{attacker_user_id}/orders?address_id={attacker_address_id}&credit_card_id={victim_card_id}&product_id_1={product_id}&quantity_1=1 |
Attacker places order using victim's card |
| Step | HTTP Method | Endpoint | Purpose/Attack |
|---|---|---|---|
| 1 | POST | /api/auth/login?username=regular&password=RegularPass! |
Regular user logs in |
| 2 | PUT | /api/products/{product_id}?internal_status=hidden |
User sets internal status field |
| Step | HTTP Method | Endpoint | Purpose/Attack |
|---|---|---|---|
| 1 | GET | /api/products/search?name=' OR 1=1 -- |
Attempt SQL/NoSQL injection (limited impact in demo, but demonstrates input vector) |