mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2026-01-12 19:46:18 +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:
|
Test multiple endpoints with concurrency:
|
||||||
|
|
||||||
```bash
|
```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
|
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
|
python sqlmapcli.py -b endpoints.json --level 2 --risk 2 --concurrency 10
|
||||||
|
|
||||||
# Test with custom settings
|
# Test with custom settings
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ python sqlmapcli.py --interactive
|
||||||
```
|
```
|
||||||
-u, --url Target URL
|
-u, --url Target URL
|
||||||
-b, --batch-file JSON file with multiple endpoints
|
-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)
|
--comprehensive Run all risk/level combinations (1-3 risk, 1-5 levels)
|
||||||
--level {1-5} Test level (default: 1)
|
--level {1-5} Test level (default: 1)
|
||||||
--risk {1-3} Test risk (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
|
# Cleanup temporary output directory
|
||||||
try:
|
try:
|
||||||
shutil.rmtree(tmp_output_dir)
|
shutil.rmtree(tmp_output_dir)
|
||||||
except:
|
except Exception as cleanup_error:
|
||||||
pass
|
console.log(
|
||||||
|
f"Failed to remove temporary sqlmap output directory {tmp_output_dir!r}: {cleanup_error}"
|
||||||
|
)
|
||||||
|
|
||||||
return process.returncode == 0, full_output
|
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:
|
except Exception as cleanup_error:
|
||||||
console.log(
|
console.log(
|
||||||
f"Failed to remove temporary sqlmap output directory {tmp_output_dir!r}: {cleanup_error}"
|
f"Failed to remove temporary sqlmap output directory {tmp_output_dir!r}: {cleanup_error}"
|
||||||
|
|
@ -255,9 +266,33 @@ class SQLMapScanner:
|
||||||
)
|
)
|
||||||
|
|
||||||
if parsed["is_vulnerable"]:
|
if parsed["is_vulnerable"]:
|
||||||
self.results["vulnerabilities"].extend(
|
# Deduplicate vulnerabilities across different level/risk combinations
|
||||||
parsed["vulnerabilities"]
|
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(
|
results_table.add_row(
|
||||||
str(level),
|
str(level),
|
||||||
|
|
@ -359,7 +394,7 @@ class SQLMapScanner:
|
||||||
|
|
||||||
headers = endpoint.get("headers")
|
headers = endpoint.get("headers")
|
||||||
if headers is not None and isinstance(headers, list):
|
if headers is not None and isinstance(headers, list):
|
||||||
headers = "\\n".join(headers)
|
headers = "\n".join(headers)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Force verbosity 3 for better progress tracking if in batch mode
|
# Force verbosity 3 for better progress tracking if in batch mode
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import re
|
import re
|
||||||
|
import hashlib
|
||||||
|
import random
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
|
@ -9,11 +11,15 @@ SQLMAP_PATH = Path(__file__).parent.parent / "sqlmap.py"
|
||||||
LOGS_DIR = Path(__file__).parent.parent / "logs"
|
LOGS_DIR = Path(__file__).parent.parent / "logs"
|
||||||
|
|
||||||
def get_log_filename(url: str) -> Path:
|
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")
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
# Sanitize URL for filename
|
# Create a hash of the URL to ensure uniqueness
|
||||||
safe_url = re.sub(r'[^\w\-_\.]', '_', url)[:50]
|
url_hash = hashlib.md5(url.encode()).hexdigest()[:8]
|
||||||
return LOGS_DIR / f"sqlmap_{safe_url}_{timestamp}.log"
|
# 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):
|
def save_log(log_file: Path, content: str):
|
||||||
"""Save content to log file"""
|
"""Save content to log file"""
|
||||||
|
|
|
||||||
63
sqlmapcli.py
63
sqlmapcli.py
|
|
@ -73,14 +73,50 @@ def interactive_mode(scanner: SQLMapScanner):
|
||||||
)
|
)
|
||||||
|
|
||||||
if scan_type == "quick":
|
if scan_type == "quick":
|
||||||
level = int(Prompt.ask("[cyan]Test level (1-5)[/cyan]", default="1"))
|
# Input validation for level and risk
|
||||||
risk = int(Prompt.ask("[cyan]Test risk (1-3)[/cyan]", default="1"))
|
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)
|
scanner.quick_scan(url, level, risk, data=data, headers=headers)
|
||||||
else:
|
else:
|
||||||
max_level = int(
|
# Input validation for max_level and max_risk
|
||||||
Prompt.ask("[cyan]Maximum test level (1-5)[/cyan]", default="5")
|
while True:
|
||||||
)
|
try:
|
||||||
max_risk = int(Prompt.ask("[cyan]Maximum test risk (1-3)[/cyan]", default="3"))
|
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)
|
scanner.comprehensive_scan(url, max_level, max_risk, data=data, headers=headers)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -197,6 +233,21 @@ Examples:
|
||||||
verbose=verbose_level,
|
verbose=verbose_level,
|
||||||
)
|
)
|
||||||
return
|
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:
|
except Exception as e:
|
||||||
console.print(f"[bold red]Error loading batch file: {e}[/bold red]")
|
console.print(f"[bold red]Error loading batch file: {e}[/bold red]")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user