Apply code review fixes: add __init__.py, fix filename collisions, improve error handling, add input validation, fix header concatenation, deduplicate vulnerabilities, rename test file

Co-authored-by: GilbertKrantz <90319182+GilbertKrantz@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-01-07 13:26:23 +00:00
parent ae3920f0b3
commit 1336215439
7 changed files with 117 additions and 19 deletions

View File

@ -75,10 +75,10 @@ python sqlmapcli.py -u "https://demo.owasp-juice.shop/rest/user/login" --data='{
Test multiple endpoints with concurrency:
```bash
# Test multiple endpoints from a JSON file with 5 concurrent scans (default)
# Test multiple endpoints from a JSON file with auto-scaled concurrency (default, typically 2x CPU cores)
python sqlmapcli.py -b endpoints.json --level 2 --risk 2
# Test with higher concurrency (10 concurrent scans)
# Test with specific concurrency (10 concurrent scans)
python sqlmapcli.py -b endpoints.json --level 2 --risk 2 --concurrency 10
# Test with custom settings

View File

@ -74,7 +74,7 @@ python sqlmapcli.py --interactive
```
-u, --url Target URL
-b, --batch-file JSON file with multiple endpoints
-c, --concurrency Concurrent scans for batch mode (default: 5)
-c, --concurrency Concurrent scans for batch mode (default: 0 for auto-scale based on CPU count)
--comprehensive Run all risk/level combinations (1-3 risk, 1-5 levels)
--level {1-5} Test level (default: 1)
--risk {1-3} Test risk (default: 1)

6
sql_cli/__init__.py Normal file
View File

@ -0,0 +1,6 @@
"""
SQLMap CLI Package
A beautiful CLI wrapper for sqlmap with automated testing capabilities
"""
__version__ = "1.0.0"

View File

@ -122,10 +122,21 @@ class SQLMapScanner:
# Cleanup temporary output directory
try:
shutil.rmtree(tmp_output_dir)
except:
pass
except Exception as cleanup_error:
console.log(
f"Failed to remove temporary sqlmap output directory {tmp_output_dir!r}: {cleanup_error}"
)
return process.returncode == 0, full_output
else:
# Run without progress (non-interactive)
result = subprocess.run(
cmd, capture_output=True, text=True, timeout=600
)
# Cleanup temporary output directory
try:
shutil.rmtree(tmp_output_dir)
except Exception as cleanup_error:
console.log(
f"Failed to remove temporary sqlmap output directory {tmp_output_dir!r}: {cleanup_error}"
@ -255,9 +266,33 @@ class SQLMapScanner:
)
if parsed["is_vulnerable"]:
self.results["vulnerabilities"].extend(
parsed["vulnerabilities"]
)
# Deduplicate vulnerabilities across different level/risk combinations
existing_keys = set()
for v in self.results["vulnerabilities"]:
if isinstance(v, dict):
param = v.get("parameter")
vtype = v.get("type")
title = v.get("title")
else:
param = getattr(v, "parameter", None)
vtype = getattr(v, "type", None)
title = getattr(v, "title", None)
existing_keys.add((param, vtype, title))
for v in parsed["vulnerabilities"]:
if isinstance(v, dict):
param = v.get("parameter")
vtype = v.get("type")
title = v.get("title")
else:
param = getattr(v, "parameter", None)
vtype = getattr(v, "type", None)
title = getattr(v, "title", None)
key = (param, vtype, title)
if key not in existing_keys:
self.results["vulnerabilities"].append(v)
existing_keys.add(key)
results_table.add_row(
str(level),
@ -359,7 +394,7 @@ class SQLMapScanner:
headers = endpoint.get("headers")
if headers is not None and isinstance(headers, list):
headers = "\\n".join(headers)
headers = "\n".join(headers)
try:
# Force verbosity 3 for better progress tracking if in batch mode

View File

@ -1,4 +1,6 @@
import re
import hashlib
import random
from pathlib import Path
from datetime import datetime
from rich.console import Console
@ -9,11 +11,15 @@ SQLMAP_PATH = Path(__file__).parent.parent / "sqlmap.py"
LOGS_DIR = Path(__file__).parent.parent / "logs"
def get_log_filename(url: str) -> Path:
"""Generate a log filename based on URL and timestamp"""
"""Generate a log filename based on URL and timestamp with hash for uniqueness"""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
# Sanitize URL for filename
safe_url = re.sub(r'[^\w\-_\.]', '_', url)[:50]
return LOGS_DIR / f"sqlmap_{safe_url}_{timestamp}.log"
# Create a hash of the URL to ensure uniqueness
url_hash = hashlib.md5(url.encode()).hexdigest()[:8]
# Add random component for additional uniqueness in batch scenarios
random_component = random.randint(1000, 9999)
# Sanitize URL for filename (keep it readable but short)
safe_url = re.sub(r'[^\w\-_\.]', '_', url)[:30]
return LOGS_DIR / f"sqlmap_{safe_url}_{url_hash}_{timestamp}_{random_component}.log"
def save_log(log_file: Path, content: str):
"""Save content to log file"""

View File

@ -73,14 +73,50 @@ def interactive_mode(scanner: SQLMapScanner):
)
if scan_type == "quick":
level = int(Prompt.ask("[cyan]Test level (1-5)[/cyan]", default="1"))
risk = int(Prompt.ask("[cyan]Test risk (1-3)[/cyan]", default="1"))
# Input validation for level and risk
while True:
try:
level_str = Prompt.ask("[cyan]Test level (1-5)[/cyan]", default="1")
level = int(level_str)
if 1 <= level <= 5:
break
console.print("[red]Level must be between 1 and 5[/red]")
except ValueError:
console.print("[red]Please enter a valid number[/red]")
while True:
try:
risk_str = Prompt.ask("[cyan]Test risk (1-3)[/cyan]", default="1")
risk = int(risk_str)
if 1 <= risk <= 3:
break
console.print("[red]Risk must be between 1 and 3[/red]")
except ValueError:
console.print("[red]Please enter a valid number[/red]")
scanner.quick_scan(url, level, risk, data=data, headers=headers)
else:
max_level = int(
Prompt.ask("[cyan]Maximum test level (1-5)[/cyan]", default="5")
)
max_risk = int(Prompt.ask("[cyan]Maximum test risk (1-3)[/cyan]", default="3"))
# Input validation for max_level and max_risk
while True:
try:
max_level_str = Prompt.ask("[cyan]Maximum test level (1-5)[/cyan]", default="5")
max_level = int(max_level_str)
if 1 <= max_level <= 5:
break
console.print("[red]Level must be between 1 and 5[/red]")
except ValueError:
console.print("[red]Please enter a valid number[/red]")
while True:
try:
max_risk_str = Prompt.ask("[cyan]Maximum test risk (1-3)[/cyan]", default="3")
max_risk = int(max_risk_str)
if 1 <= max_risk <= 3:
break
console.print("[red]Risk must be between 1 and 3[/red]")
except ValueError:
console.print("[red]Please enter a valid number[/red]")
scanner.comprehensive_scan(url, max_level, max_risk, data=data, headers=headers)
@ -197,6 +233,21 @@ Examples:
verbose=verbose_level,
)
return
except FileNotFoundError:
console.print(
f"[bold red]Error: Batch file not found: {args.batch_file}[/bold red]"
)
sys.exit(1)
except json.JSONDecodeError as e:
console.print(
f"[bold red]Error: Invalid JSON in batch file '{args.batch_file}': {e}[/bold red]"
)
sys.exit(1)
except PermissionError:
console.print(
f"[bold red]Error: Permission denied when reading batch file: {args.batch_file}[/bold red]"
)
sys.exit(1)
except Exception as e:
console.print(f"[bold red]Error loading batch file: {e}[/bold red]")
sys.exit(1)