mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-18 13:39:40 +03:00
Add folder anatomy (scripts/agent.py + references/api-reference.md) for 648 cybersecurity skills
Complete skill folder anatomy across all cybersecurity skills: - scripts/agent.py: 80-150 line Python agents using real libraries (impacket, boto3, azure-mgmt-*, kubernetes, pefile, yara, scapy, shodan, stix2, etc.) - references/api-reference.md: real API documentation with method signatures - LICENSE: MIT license for all skill folders
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Anthropic Agent Skills Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
# API Reference: Detecting Broken Object Property Level Authorization
|
||||
|
||||
## OWASP API3:2023 Classification
|
||||
|
||||
| Category | Description |
|
||||
|----------|-------------|
|
||||
| Excessive Data Exposure | API returns more properties than client needs |
|
||||
| Mass Assignment | API accepts more properties than intended |
|
||||
| CWE-213 | Exposure of Sensitive Information Due to Incompatible Policies |
|
||||
| CWE-915 | Improperly Controlled Modification of Dynamically-Determined Object Attributes |
|
||||
|
||||
## Python requests Library
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
# GET - test for excessive exposure
|
||||
resp = requests.get(url, headers={"Authorization": f"Bearer {token}"}, timeout=10)
|
||||
resp.status_code # 200, 401, 403
|
||||
resp.json() # parsed response body
|
||||
|
||||
# PUT - test for mass assignment
|
||||
resp = requests.put(url, json={"role": "admin"}, headers=headers, timeout=10)
|
||||
|
||||
# PATCH - test for partial mass assignment
|
||||
resp = requests.patch(url, json={"is_admin": True}, headers=headers, timeout=10)
|
||||
```
|
||||
|
||||
## Sensitive Field Patterns
|
||||
|
||||
| Severity | Fields |
|
||||
|----------|--------|
|
||||
| Critical | password, password_hash, secret, token, api_key, private_key |
|
||||
| High | ssn, credit_card, card_number, cvv, bank_account |
|
||||
| Medium | salary, role, permissions, is_admin, session_id |
|
||||
| Low | phone, address, date_of_birth, gender |
|
||||
|
||||
## Mass Assignment Test Payloads
|
||||
|
||||
```json
|
||||
{"role": "admin"}
|
||||
{"is_admin": true}
|
||||
{"is_verified": true}
|
||||
{"account_type": "premium"}
|
||||
{"discount_rate": 100}
|
||||
{"permissions": ["admin", "write", "delete"]}
|
||||
```
|
||||
|
||||
## Burp Suite Extensions
|
||||
|
||||
```
|
||||
# Autorize - test authorization across roles
|
||||
# Param Miner - discover hidden parameters
|
||||
# JSON Beautifier - inspect response properties
|
||||
```
|
||||
|
||||
## CLI Usage
|
||||
|
||||
```bash
|
||||
python agent.py --base-url https://api.example.com \
|
||||
--endpoint /api/v1/users/123 \
|
||||
--token "eyJhbGciOiJIUzI1NiJ9..." \
|
||||
--expected-fields id username name email \
|
||||
--test both --method PUT
|
||||
```
|
||||
|
||||
## Mitigation Patterns
|
||||
|
||||
```python
|
||||
# Allowlist serialization (Django REST Framework)
|
||||
class UserSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['id', 'username', 'name'] # explicit allowlist
|
||||
read_only_fields = ['id', 'role', 'is_admin']
|
||||
```
|
||||
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env python3
|
||||
"""BOPLA vulnerability scanner for OWASP API3:2023 Broken Object Property Level Authorization.
|
||||
|
||||
Tests APIs for excessive data exposure and mass assignment vulnerabilities
|
||||
by comparing responses against expected field sets and injecting extra properties.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from copy import deepcopy
|
||||
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
print("Install requests: pip install requests")
|
||||
sys.exit(1)
|
||||
|
||||
SENSITIVE_PATTERNS = {
|
||||
"critical": ["password", "password_hash", "secret", "token", "api_key",
|
||||
"private_key", "access_token", "refresh_token"],
|
||||
"high": ["ssn", "social_security", "tax_id", "credit_card", "card_number",
|
||||
"cvv", "bank_account", "routing_number"],
|
||||
"medium": ["salary", "income", "internal_notes", "role", "permissions",
|
||||
"is_admin", "is_superuser", "session_id", "ip_address"],
|
||||
"low": ["phone", "address", "date_of_birth", "dob", "gender"]
|
||||
}
|
||||
|
||||
MASS_ASSIGNMENT_PAYLOADS = [
|
||||
("role", "admin"), ("is_admin", True), ("is_verified", True),
|
||||
("email_verified", True), ("account_type", "premium"),
|
||||
("discount_rate", 100), ("permissions", ["admin", "write", "delete"]),
|
||||
("account_balance", 999999), ("subscription_tier", "enterprise"),
|
||||
]
|
||||
|
||||
|
||||
def classify_field(field_name):
|
||||
lower = field_name.lower().split(".")[-1]
|
||||
for severity, patterns in SENSITIVE_PATTERNS.items():
|
||||
for p in patterns:
|
||||
if p in lower:
|
||||
return severity.upper()
|
||||
return None
|
||||
|
||||
|
||||
def flatten_keys(obj, prefix=""):
|
||||
keys = []
|
||||
for k, v in obj.items():
|
||||
full = f"{prefix}.{k}" if prefix else k
|
||||
keys.append(full)
|
||||
if isinstance(v, dict):
|
||||
keys.extend(flatten_keys(v, full))
|
||||
return keys
|
||||
|
||||
|
||||
def test_excessive_exposure(base_url, endpoint, expected_fields, headers):
|
||||
findings = []
|
||||
url = f"{base_url.rstrip('/')}{endpoint}"
|
||||
try:
|
||||
resp = requests.get(url, headers=headers, timeout=10)
|
||||
if resp.status_code != 200:
|
||||
return findings
|
||||
data = resp.json()
|
||||
objects = data if isinstance(data, list) else [data]
|
||||
if isinstance(data, dict) and "data" in data:
|
||||
inner = data["data"]
|
||||
objects = inner if isinstance(inner, list) else [inner]
|
||||
for obj in objects[:5]:
|
||||
if not isinstance(obj, dict):
|
||||
continue
|
||||
response_fields = set(flatten_keys(obj))
|
||||
unexpected = response_fields - set(expected_fields)
|
||||
for field in unexpected:
|
||||
sev = classify_field(field)
|
||||
if sev:
|
||||
findings.append({
|
||||
"endpoint": endpoint, "method": "GET",
|
||||
"type": "excessive_exposure", "severity": sev,
|
||||
"property": field,
|
||||
"detail": f"Unexpected sensitive field '{field}' in response"
|
||||
})
|
||||
except (requests.RequestException, json.JSONDecodeError) as e:
|
||||
findings.append({"error": str(e)})
|
||||
return findings
|
||||
|
||||
|
||||
def test_mass_assignment(base_url, endpoint, headers, method="PUT"):
|
||||
findings = []
|
||||
url = f"{base_url.rstrip('/')}{endpoint}"
|
||||
try:
|
||||
original = requests.get(url, headers=headers, timeout=10)
|
||||
original_data = original.json() if original.status_code == 200 else {}
|
||||
except (requests.RequestException, json.JSONDecodeError):
|
||||
original_data = {}
|
||||
|
||||
for field_name, injected_value in MASS_ASSIGNMENT_PAYLOADS:
|
||||
if original_data.get(field_name) == injected_value:
|
||||
continue
|
||||
test_data = deepcopy(original_data)
|
||||
test_data[field_name] = injected_value
|
||||
try:
|
||||
h = {**headers, "Content-Type": "application/json"}
|
||||
if method == "PUT":
|
||||
resp = requests.put(url, json=test_data, headers=h, timeout=10)
|
||||
elif method == "PATCH":
|
||||
resp = requests.patch(url, json={field_name: injected_value}, headers=h, timeout=10)
|
||||
else:
|
||||
resp = requests.post(url, json=test_data, headers=h, timeout=10)
|
||||
if resp.status_code in (200, 201, 204):
|
||||
verify = requests.get(url, headers=headers, timeout=10)
|
||||
if verify.status_code == 200 and verify.json().get(field_name) == injected_value:
|
||||
sev = "CRITICAL" if field_name in ("role", "is_admin", "permissions") else "HIGH"
|
||||
findings.append({
|
||||
"endpoint": endpoint, "method": method,
|
||||
"type": "mass_assignment", "severity": sev,
|
||||
"property": field_name,
|
||||
"detail": f"Injected {field_name}={injected_value} accepted"
|
||||
})
|
||||
if field_name in original_data:
|
||||
requests.patch(url, json={field_name: original_data[field_name]},
|
||||
headers=h, timeout=10)
|
||||
except requests.RequestException:
|
||||
continue
|
||||
return findings
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="BOPLA Vulnerability Scanner (OWASP API3:2023)")
|
||||
parser.add_argument("--base-url", required=True, help="API base URL")
|
||||
parser.add_argument("--endpoint", required=True, help="Endpoint to test (e.g. /api/v1/users/1)")
|
||||
parser.add_argument("--token", help="Bearer token for Authorization header")
|
||||
parser.add_argument("--expected-fields", nargs="+", default=[], help="Expected response fields")
|
||||
parser.add_argument("--test", choices=["exposure", "mass_assignment", "both"], default="both")
|
||||
parser.add_argument("--method", choices=["PUT", "PATCH", "POST"], default="PUT")
|
||||
args = parser.parse_args()
|
||||
|
||||
headers = {}
|
||||
if args.token:
|
||||
headers["Authorization"] = f"Bearer {args.token}"
|
||||
|
||||
results = {"endpoint": args.endpoint, "findings": []}
|
||||
if args.test in ("exposure", "both"):
|
||||
results["findings"].extend(
|
||||
test_excessive_exposure(args.base_url, args.endpoint, args.expected_fields, headers))
|
||||
if args.test in ("mass_assignment", "both"):
|
||||
results["findings"].extend(
|
||||
test_mass_assignment(args.base_url, args.endpoint, headers, args.method))
|
||||
|
||||
results["total_findings"] = len(results["findings"])
|
||||
results["by_severity"] = {}
|
||||
for f in results["findings"]:
|
||||
sev = f.get("severity", "UNKNOWN")
|
||||
results["by_severity"][sev] = results["by_severity"].get(sev, 0) + 1
|
||||
|
||||
print(json.dumps(results, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user