mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-29 07:30:59 +03:00
Production hardening: security fixes, code quality, 724 skills complete
- Fix 25 shell=True subprocess calls with list-based commands - Fix 49 verify=False in defensive skills (env-var override) - Add timeout to 231 HTTP/subprocess/socket calls - Fix 6 SQL injection patterns with whitelist validation - Replace 8 __import__() with standard imports - Remove 701 unused imports across 442 files - Add authorized-testing disclaimers to all offensive skills - Complete 11 incomplete skill directories - Expand 10 stub SKILL.md files with full content - Fix 2 YAML parse errors in frontmatter - Fix 5 pre-existing syntax errors - Convert 22 hardcoded paths/ports to environment variables - Back up 21 redundant skill pairs to .bak - Fix 2 global declaration errors - 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE) - 0 compile errors across all 724 agent.py files
This commit is contained in:
+186
-14
@@ -1,27 +1,199 @@
|
||||
# API Reference: MobSF Android static analysis agent
|
||||
# API Reference: MobSF Android Static Analysis
|
||||
|
||||
## API Details
|
||||
MobSF API: POST /api/v1/upload, POST /api/v1/scan, GET /api/v1/report_json, API key auth
|
||||
## Libraries Used
|
||||
|
||||
| Library | Purpose |
|
||||
|---------|---------|
|
||||
| `requests` | HTTP client for MobSF REST API v1 |
|
||||
| `json` | Parse scan reports and finding data |
|
||||
| `os` | Read `MOBSF_URL` and `MOBSF_API_KEY` environment variables |
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install requests
|
||||
|
||||
# MobSF server (Docker)
|
||||
docker pull opensecurity/mobile-security-framework-mobsf
|
||||
docker run -it -p 8000:8000 opensecurity/mobile-security-framework-mobsf
|
||||
```
|
||||
|
||||
## Libraries
|
||||
|
||||
| Library | Use |
|
||||
|---------|-----|
|
||||
| `requests` | requests |
|
||||
|
||||
## Authentication
|
||||
|
||||
| Method | Header |
|
||||
|--------|--------|
|
||||
| Bearer Token | `Authorization: Bearer <token>` |
|
||||
| API Key | `X-API-Key: <key>` |
|
||||
MobSF uses API key authentication. The default key is shown on the MobSF dashboard:
|
||||
|
||||
```python
|
||||
import requests
|
||||
import os
|
||||
|
||||
MOBSF_URL = os.environ.get("MOBSF_URL", "http://localhost:8000")
|
||||
MOBSF_KEY = os.environ["MOBSF_API_KEY"]
|
||||
headers = {"Authorization": MOBSF_KEY}
|
||||
```
|
||||
|
||||
## REST API v1 Endpoints
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| POST | `/api/v1/upload` | Upload APK, IPA, ZIP, or APPX for analysis |
|
||||
| POST | `/api/v1/scan` | Trigger static analysis on uploaded file |
|
||||
| GET | `/api/v1/report_json` | Get full JSON analysis report |
|
||||
| POST | `/api/v1/download_pdf` | Download PDF report |
|
||||
| GET | `/api/v1/scans` | List recent scans |
|
||||
| POST | `/api/v1/delete_scan` | Delete a scan and its data |
|
||||
| POST | `/api/v1/search` | Search scans by hash or filename |
|
||||
| POST | `/api/v1/compare` | Compare two app scans |
|
||||
| GET | `/api/v1/scorecard` | Get app security scorecard |
|
||||
| GET | `/api/v1/scan_logs` | View live scan logs |
|
||||
|
||||
## Core Operations
|
||||
|
||||
### Upload an APK
|
||||
```python
|
||||
def upload_apk(file_path):
|
||||
with open(file_path, "rb") as f:
|
||||
resp = requests.post(
|
||||
f"{MOBSF_URL}/api/v1/upload",
|
||||
files={"file": (os.path.basename(file_path), f, "application/octet-stream")},
|
||||
headers=headers,
|
||||
timeout=120,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
result = resp.json()
|
||||
return result["hash"], result["scan_type"], result["file_name"]
|
||||
# hash: SHA-256 of the uploaded file
|
||||
# scan_type: "apk", "ipa", "zip", "appx"
|
||||
```
|
||||
|
||||
### Trigger Static Analysis
|
||||
```python
|
||||
def start_scan(file_hash, scan_type, file_name):
|
||||
resp = requests.post(
|
||||
f"{MOBSF_URL}/api/v1/scan",
|
||||
data={
|
||||
"hash": file_hash,
|
||||
"scan_type": scan_type,
|
||||
"file_name": file_name,
|
||||
},
|
||||
headers=headers,
|
||||
timeout=600, # Scans can take several minutes
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
```
|
||||
|
||||
### Retrieve JSON Report
|
||||
```python
|
||||
def get_report(file_hash):
|
||||
resp = requests.post(
|
||||
f"{MOBSF_URL}/api/v1/report_json",
|
||||
data={"hash": file_hash},
|
||||
headers=headers,
|
||||
timeout=60,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
```
|
||||
|
||||
### Extract Key Findings
|
||||
```python
|
||||
def extract_findings(report):
|
||||
findings = {
|
||||
"security_score": report.get("security_score", "N/A"),
|
||||
"app_name": report.get("app_name"),
|
||||
"package_name": report.get("package_name"),
|
||||
"target_sdk": report.get("target_sdk"),
|
||||
"min_sdk": report.get("min_sdk"),
|
||||
"permissions": {
|
||||
"dangerous": [],
|
||||
"normal": [],
|
||||
},
|
||||
"manifest_issues": [],
|
||||
"code_issues": [],
|
||||
"binary_issues": [],
|
||||
}
|
||||
|
||||
# Dangerous permissions
|
||||
for perm, details in report.get("permissions", {}).items():
|
||||
status = details.get("status", "normal")
|
||||
if status == "dangerous":
|
||||
findings["permissions"]["dangerous"].append(perm)
|
||||
else:
|
||||
findings["permissions"]["normal"].append(perm)
|
||||
|
||||
# Manifest analysis
|
||||
for issue in report.get("manifest_analysis", []):
|
||||
if issue.get("severity") in ("high", "warning"):
|
||||
findings["manifest_issues"].append({
|
||||
"title": issue["title"],
|
||||
"severity": issue["severity"],
|
||||
"description": issue["description"],
|
||||
})
|
||||
|
||||
# Code analysis
|
||||
for issue_key, issue_data in report.get("code_analysis", {}).items():
|
||||
findings["code_issues"].append({
|
||||
"rule": issue_key,
|
||||
"severity": issue_data.get("level"),
|
||||
"description": issue_data.get("description"),
|
||||
"files": issue_data.get("path", [])[:5],
|
||||
})
|
||||
|
||||
return findings
|
||||
```
|
||||
|
||||
### Download PDF Report
|
||||
```python
|
||||
def download_pdf(file_hash, output_path):
|
||||
resp = requests.post(
|
||||
f"{MOBSF_URL}/api/v1/download_pdf",
|
||||
data={"hash": file_hash},
|
||||
headers=headers,
|
||||
timeout=120,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
with open(output_path, "wb") as f:
|
||||
f.write(resp.content)
|
||||
```
|
||||
|
||||
### Compare Two Applications
|
||||
```python
|
||||
resp = requests.post(
|
||||
f"{MOBSF_URL}/api/v1/compare",
|
||||
data={"hash1": hash_v1, "hash2": hash_v2},
|
||||
headers=headers,
|
||||
timeout=120,
|
||||
)
|
||||
comparison = resp.json()
|
||||
# Shows permission changes, new vulnerabilities, code changes
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
```json
|
||||
{"timestamp": "ISO-8601", "target": "URL", "findings": [], "risk_level": "HIGH"}
|
||||
{
|
||||
"file_name": "app-debug.apk",
|
||||
"app_name": "TestApp",
|
||||
"package_name": "com.example.testapp",
|
||||
"security_score": 42,
|
||||
"target_sdk": "33",
|
||||
"min_sdk": "24",
|
||||
"permissions": {
|
||||
"android.permission.INTERNET": {"status": "normal", "description": "..."},
|
||||
"android.permission.READ_CONTACTS": {"status": "dangerous", "description": "..."}
|
||||
},
|
||||
"manifest_analysis": [
|
||||
{"title": "Application is debuggable", "severity": "high", "description": "..."}
|
||||
],
|
||||
"code_analysis": {
|
||||
"android_insecure_random": {
|
||||
"level": "high",
|
||||
"description": "Insecure Random Number Generator",
|
||||
"path": ["com/example/CryptoUtils.java"]
|
||||
}
|
||||
},
|
||||
"binary_analysis": [
|
||||
{"title": "NX bit not set", "severity": "high"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,60 +1,261 @@
|
||||
#!/usr/bin/env python3
|
||||
"""MobSF Android static analysis agent."""
|
||||
import argparse, json, sys
|
||||
"""MobSF Android static analysis agent.
|
||||
|
||||
Automates APK upload, static analysis scanning, and report retrieval
|
||||
via the MobSF REST API. Extracts security findings including manifest
|
||||
analysis, code analysis, binary analysis, and certificate checks.
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
requests = None
|
||||
print("[!] 'requests' library required: pip install requests", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def run_scan(target, token=None):
|
||||
|
||||
def get_mobsf_config():
|
||||
"""Return MobSF server URL and API key from env or defaults."""
|
||||
server = os.environ.get("MOBSF_URL", "http://localhost:8000")
|
||||
api_key = os.environ.get("MOBSF_API_KEY", "")
|
||||
if not api_key:
|
||||
print("[!] Set MOBSF_API_KEY environment variable", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
return server.rstrip("/"), api_key
|
||||
|
||||
|
||||
def upload_apk(server, api_key, apk_path):
|
||||
"""Upload an APK file to MobSF and return the file hash."""
|
||||
url = f"{server}/api/v1/upload"
|
||||
headers = {"Authorization": api_key}
|
||||
if not os.path.isfile(apk_path):
|
||||
print(f"[!] File not found: {apk_path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
print(f"[*] Uploading {os.path.basename(apk_path)} to MobSF...")
|
||||
with open(apk_path, "rb") as f:
|
||||
resp = requests.post(
|
||||
url,
|
||||
files={"file": (os.path.basename(apk_path), f, "application/octet-stream")},
|
||||
headers=headers,
|
||||
timeout=300,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
file_hash = data.get("hash", "")
|
||||
scan_type = data.get("scan_type", "apk")
|
||||
file_name = data.get("file_name", os.path.basename(apk_path))
|
||||
print(f"[+] Uploaded: {file_name} (hash: {file_hash}, type: {scan_type})")
|
||||
return file_hash, scan_type, file_name
|
||||
|
||||
|
||||
def start_scan(server, api_key, file_hash, scan_type, file_name):
|
||||
"""Trigger static analysis scan on the uploaded APK."""
|
||||
url = f"{server}/api/v1/scan"
|
||||
headers = {"Authorization": api_key}
|
||||
print(f"[*] Starting static analysis scan for {file_name}...")
|
||||
resp = requests.post(
|
||||
url,
|
||||
data={"hash": file_hash, "scan_type": scan_type, "file_name": file_name},
|
||||
headers=headers,
|
||||
timeout=600,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
print("[+] Scan complete")
|
||||
return resp.json()
|
||||
|
||||
|
||||
def get_report(server, api_key, file_hash):
|
||||
"""Retrieve the JSON report for a scanned APK."""
|
||||
url = f"{server}/api/v1/report_json"
|
||||
headers = {"Authorization": api_key}
|
||||
resp = requests.post(
|
||||
url,
|
||||
data={"hash": file_hash},
|
||||
headers=headers,
|
||||
timeout=120,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
|
||||
|
||||
def get_scorecard(server, api_key, file_hash):
|
||||
"""Retrieve the security scorecard for a scanned APK."""
|
||||
url = f"{server}/api/v1/scorecard"
|
||||
headers = {"Authorization": api_key}
|
||||
resp = requests.post(
|
||||
url,
|
||||
data={"hash": file_hash},
|
||||
headers=headers,
|
||||
timeout=60,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
|
||||
|
||||
def extract_findings(report):
|
||||
"""Extract structured security findings from MobSF report JSON."""
|
||||
findings = []
|
||||
if not requests: return [{"error": "requests required"}]
|
||||
headers = {"Authorization": f"Bearer {token}"} if token else {}
|
||||
try:
|
||||
resp = requests.get(f"{target}", headers=headers, timeout=15)
|
||||
if resp.status_code == 200:
|
||||
findings.append({"check": "Target Accessible", "status": "OK", "severity": "INFO"})
|
||||
else:
|
||||
findings.append({"check": "Target Access", "status": f"HTTP {resp.status_code}", "severity": "MEDIUM"})
|
||||
except requests.RequestException as e:
|
||||
findings.append({"error": str(e)})
|
||||
|
||||
# Manifest analysis
|
||||
for item in report.get("manifest_analysis", []):
|
||||
findings.append({
|
||||
"category": "manifest",
|
||||
"title": item.get("title", "Unknown"),
|
||||
"severity": item.get("severity", "info").upper(),
|
||||
"description": item.get("description", ""),
|
||||
})
|
||||
|
||||
# Code analysis
|
||||
code_analysis = report.get("code_analysis", {})
|
||||
if isinstance(code_analysis, dict):
|
||||
for key, value in code_analysis.items():
|
||||
if isinstance(value, dict):
|
||||
findings.append({
|
||||
"category": "code",
|
||||
"title": value.get("metadata", {}).get("description", key),
|
||||
"severity": value.get("metadata", {}).get("severity", "info").upper(),
|
||||
"description": value.get("metadata", {}).get("cwe", ""),
|
||||
"files": list(value.get("files", {}).keys())[:5],
|
||||
})
|
||||
|
||||
# Binary analysis
|
||||
for item in report.get("binary_analysis", []):
|
||||
findings.append({
|
||||
"category": "binary",
|
||||
"title": item.get("name", "Unknown"),
|
||||
"severity": item.get("severity", "info").upper(),
|
||||
"description": item.get("description", ""),
|
||||
})
|
||||
|
||||
# Certificate analysis
|
||||
cert_info = report.get("certificate_analysis", {})
|
||||
if isinstance(cert_info, dict):
|
||||
cert_findings = cert_info.get("certificate_findings", [])
|
||||
for item in cert_findings:
|
||||
findings.append({
|
||||
"category": "certificate",
|
||||
"title": item.get("title", "Certificate finding"),
|
||||
"severity": item.get("severity", "info").upper(),
|
||||
"description": item.get("description", ""),
|
||||
})
|
||||
|
||||
# Permissions
|
||||
permissions = report.get("permissions", {})
|
||||
dangerous_perms = []
|
||||
if isinstance(permissions, dict):
|
||||
for perm, details in permissions.items():
|
||||
if isinstance(details, dict) and details.get("status") == "dangerous":
|
||||
dangerous_perms.append(perm)
|
||||
if dangerous_perms:
|
||||
findings.append({
|
||||
"category": "permissions",
|
||||
"title": f"{len(dangerous_perms)} dangerous permissions declared",
|
||||
"severity": "WARNING",
|
||||
"description": ", ".join(dangerous_perms[:10]),
|
||||
})
|
||||
|
||||
return findings
|
||||
|
||||
def analyze_results(target, token=None):
|
||||
findings = []
|
||||
if not requests: return []
|
||||
headers = {"Authorization": f"Bearer {token}"} if token else {}
|
||||
try:
|
||||
resp = requests.get(f"{target}/api/v1/results", headers=headers, timeout=15)
|
||||
if resp.status_code == 200:
|
||||
data = resp.json()
|
||||
for item in data.get("findings", data.get("results", [])):
|
||||
severity = item.get("severity", item.get("risk", "MEDIUM"))
|
||||
findings.append({"check": item.get("name", item.get("title", "unknown")),
|
||||
"severity": severity.upper() if isinstance(severity, str) else "MEDIUM"})
|
||||
except requests.RequestException:
|
||||
pass
|
||||
return findings
|
||||
|
||||
def format_summary(report, findings):
|
||||
"""Print a human-readable summary of the analysis."""
|
||||
app_name = report.get("app_name", "Unknown")
|
||||
package = report.get("package_name", "Unknown")
|
||||
version = report.get("version_name", "Unknown")
|
||||
sdk_min = report.get("min_sdk", "?")
|
||||
sdk_target = report.get("target_sdk", "?")
|
||||
security_score = report.get("security_score", "N/A")
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f" MobSF Static Analysis Report")
|
||||
print(f"{'='*60}")
|
||||
print(f" App Name : {app_name}")
|
||||
print(f" Package : {package}")
|
||||
print(f" Version : {version}")
|
||||
print(f" Min SDK : {sdk_min} | Target SDK: {sdk_target}")
|
||||
print(f" Security : {security_score}/100")
|
||||
print(f"{'='*60}")
|
||||
|
||||
severity_counts = {}
|
||||
for f in findings:
|
||||
sev = f.get("severity", "INFO")
|
||||
severity_counts[sev] = severity_counts.get(sev, 0) + 1
|
||||
|
||||
print(f"\n Findings Summary:")
|
||||
for sev in ["CRITICAL", "HIGH", "WARNING", "MEDIUM", "INFO", "LOW"]:
|
||||
if sev in severity_counts:
|
||||
print(f" {sev:10s}: {severity_counts[sev]}")
|
||||
|
||||
print(f"\n Top Findings:")
|
||||
high_findings = [f for f in findings if f.get("severity") in ("CRITICAL", "HIGH", "WARNING")]
|
||||
for f in high_findings[:10]:
|
||||
print(f" [{f['severity']:8s}] [{f['category']:12s}] {f['title']}")
|
||||
|
||||
return severity_counts
|
||||
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser(description="MobSF Android static analysis agent")
|
||||
p.add_argument("--target", required=True, help="Target URL or IP")
|
||||
p.add_argument("--token", help="API token")
|
||||
p.add_argument("--output", "-o", help="Output JSON report")
|
||||
p.add_argument("--verbose", "-v", action="store_true")
|
||||
a = p.parse_args()
|
||||
print("[*] MobSF Android static analysis agent")
|
||||
report = {"timestamp": datetime.now(timezone.utc).isoformat(), "target": a.target, "findings": []}
|
||||
report["findings"].extend(run_scan(a.target, a.token))
|
||||
report["findings"].extend(analyze_results(a.target, a.token))
|
||||
high = sum(1 for f in report["findings"] if f.get("severity") in ("HIGH", "CRITICAL"))
|
||||
report["risk_level"] = "CRITICAL" if high > 2 else "HIGH" if high else "MEDIUM" if report["findings"] else "LOW"
|
||||
print(f"[*] {len(report['findings'])} findings, risk: {report['risk_level']}")
|
||||
if a.output:
|
||||
with open(a.output, "w") as f: json.dump(report, f, indent=2)
|
||||
parser = argparse.ArgumentParser(
|
||||
description="MobSF Android static analysis agent - upload, scan, and report"
|
||||
)
|
||||
parser.add_argument("--apk", required=True, help="Path to the APK file to analyze")
|
||||
parser.add_argument("--output", "-o", help="Output JSON report path")
|
||||
parser.add_argument("--server", help="MobSF server URL (or set MOBSF_URL env var)")
|
||||
parser.add_argument("--api-key", help="MobSF API key (or set MOBSF_API_KEY env var)")
|
||||
parser.add_argument("--hash", help="Skip upload; use existing hash to retrieve report")
|
||||
parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.server:
|
||||
os.environ["MOBSF_URL"] = args.server
|
||||
if args.api_key:
|
||||
os.environ["MOBSF_API_KEY"] = args.api_key
|
||||
|
||||
server, api_key = get_mobsf_config()
|
||||
|
||||
if args.hash:
|
||||
file_hash = args.hash
|
||||
print(f"[*] Using existing hash: {file_hash}")
|
||||
else:
|
||||
print(json.dumps(report, indent=2))
|
||||
file_hash, scan_type, file_name = upload_apk(server, api_key, args.apk)
|
||||
start_scan(server, api_key, file_hash, scan_type, file_name)
|
||||
|
||||
report = get_report(server, api_key, file_hash)
|
||||
findings = extract_findings(report)
|
||||
severity_counts = format_summary(report, findings)
|
||||
|
||||
output_report = {
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
"tool": "MobSF",
|
||||
"apk": args.apk,
|
||||
"app_name": report.get("app_name", ""),
|
||||
"package_name": report.get("package_name", ""),
|
||||
"version": report.get("version_name", ""),
|
||||
"security_score": report.get("security_score", 0),
|
||||
"severity_counts": severity_counts,
|
||||
"findings": findings,
|
||||
"risk_level": (
|
||||
"CRITICAL" if severity_counts.get("CRITICAL", 0) > 0
|
||||
else "HIGH" if severity_counts.get("HIGH", 0) > 0
|
||||
else "MEDIUM" if severity_counts.get("WARNING", 0) > 0
|
||||
else "LOW"
|
||||
),
|
||||
}
|
||||
|
||||
if args.output:
|
||||
with open(args.output, "w") as f:
|
||||
json.dump(output_report, f, indent=2)
|
||||
print(f"\n[+] Report saved to {args.output}")
|
||||
elif args.verbose:
|
||||
print(json.dumps(output_report, indent=2))
|
||||
|
||||
print(f"\n[*] Risk Level: {output_report['risk_level']}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user