mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2026-01-11 19:21:11 +03:00
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:
parent
ae3920f0b3
commit
1336215439
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
6
sql_cli/__init__.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
"""
|
||||
SQLMap CLI Package
|
||||
A beautiful CLI wrapper for sqlmap with automated testing capabilities
|
||||
"""
|
||||
|
||||
__version__ = "1.0.0"
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"""
|
||||
|
|
|
|||
63
sqlmapcli.py
63
sqlmapcli.py
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user