sqlmap/sqlmapcli.py

557 lines
18 KiB
Python
Executable File

#!/usr/bin/env python3
"""
SQLMap CLI - A beautiful CLI wrapper for sqlmap
Automates comprehensive SQL injection testing with a single command
"""
import subprocess
import sys
import argparse
from pathlib import Path
from typing import List, Dict, Tuple, Optional, TypedDict, Any
from datetime import datetime
try:
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from rich.progress import (
Progress,
SpinnerColumn,
TextColumn,
BarColumn,
TimeElapsedColumn,
)
from rich.prompt import Prompt, Confirm
from rich import box
except ImportError:
print("Error: 'rich' library is required. Install it with: pip install rich")
sys.exit(1)
console = Console()
SQLMAP_PATH = Path(__file__).parent / "sqlmap.py"
SQL_TECHNIQUES = {
"B": "Boolean-based blind",
"E": "Error-based",
"U": "Union query-based",
"S": "Stacked queries",
"T": "Time-based blind",
"Q": "Inline queries",
}
class ScanResult(TypedDict):
total_tests: int
vulnerabilities: List[Dict[str, str]]
start_time: Optional[datetime]
end_time: Optional[datetime]
target: Optional[str]
class SQLMapCLI:
def __init__(self):
self.console = Console()
self.results: ScanResult = {
"total_tests": 0,
"vulnerabilities": [],
"start_time": None,
"end_time": None,
"target": None,
}
def print_banner(self):
"""Display a beautiful banner"""
banner = """
╔═══════════════════════════════════════════════════════════════╗
║ ║
║ ███████╗ ██████╗ ██╗ ███╗ ███╗ █████╗ ██████╗ ║
║ ██╔════╝██╔═══██╗██║ ████╗ ████║██╔══██╗██╔══██╗ ║
║ ███████╗██║ ██║██║ ██╔████╔██║███████║██████╔╝ ║
║ ╚════██║██║▄▄ ██║██║ ██║╚██╔╝██║██╔══██║██╔═══╝ ║
║ ███████║╚██████╔╝███████╗██║ ╚═╝ ██║██║ ██║██║ ║
║ ╚══════╝ ╚══▀▀═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ║
║ ║
║ CLI - Automated SQL Injection Testing ║
║ ║
╚═══════════════════════════════════════════════════════════════╝
"""
self.console.print(banner, style="bold cyan")
self.console.print(
Panel(
"[yellow]⚠️ Legal Disclaimer: Only use on targets you have permission to test[/yellow]",
border_style="yellow",
box=box.ROUNDED,
)
)
self.console.print()
def run_sqlmap_test(
self,
url: str,
level: int,
risk: int,
technique: str = "BEUSTQ",
batch: bool = True,
data: Optional[str] = None,
verbose: int = 1,
extra_args: Optional[List[str]] = None,
) -> Tuple[bool, str]:
"""Run sqlmap with specified parameters"""
cmd = [
sys.executable,
str(SQLMAP_PATH),
"-u",
url,
f"--level={level}",
f"--risk={risk}",
f"--technique={technique}",
"-v",
str(verbose),
]
if batch:
cmd.append("--batch")
if data:
cmd.extend(["--data", data, "--method", "POST"])
if extra_args:
cmd.extend(extra_args)
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=600, # 10 minute timeout per test
)
return result.returncode == 0, result.stdout + result.stderr
except subprocess.TimeoutExpired:
return False, "Test timed out after 10 minutes"
except Exception as e:
return False, str(e)
def parse_results(self, output: str) -> Dict[str, Any]:
"""Parse sqlmap output for vulnerabilities"""
vulns = []
# Look for vulnerability indicators
if "sqlmap identified the following injection point" in output:
# Extract injection details
lines = output.split("\n")
current_param = "Unknown" # Default parameter name
for i, line in enumerate(lines):
if "Parameter:" in line:
current_param = line.split("Parameter:")[1].strip()
elif "Type:" in line:
vuln_type = line.split("Type:")[1].strip()
# Check if next line contains the title
if i + 1 < len(lines) and "Title:" in lines[i + 1]:
title = lines[i + 1].split("Title:")[1].strip()
vulns.append(
{
"parameter": current_param,
"type": vuln_type,
"title": title,
}
)
# Check for backend DBMS detection
backend_dbms = None
if "back-end DBMS:" in output.lower():
for line in output.split("\n"):
if "back-end DBMS:" in line.lower():
backend_dbms = line.split(":", 1)[1].strip()
break
return {
"vulnerabilities": vulns,
"backend_dbms": backend_dbms,
"is_vulnerable": len(vulns) > 0 or "vulnerable" in output.lower(),
}
def comprehensive_scan(
self,
url: str,
max_level: int = 5,
max_risk: int = 3,
techniques: str = "BEUSTQ",
data: Optional[str] = None,
verbose: int = 1,
):
"""Run comprehensive scan with all levels and risks"""
self.results["target"] = url
self.results["start_time"] = datetime.now()
# Create results table
results_table = Table(title="Scan Results", box=box.ROUNDED)
results_table.add_column("Level", style="cyan", justify="center")
results_table.add_column("Risk", style="yellow", justify="center")
results_table.add_column("Status", justify="center")
results_table.add_column("Findings", style="magenta")
total_tests = max_level * max_risk
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
BarColumn(),
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
TimeElapsedColumn(),
console=self.console,
) as progress:
overall_task = progress.add_task(
f"[cyan]Scanning {url}...", total=total_tests
)
for level in range(1, max_level + 1):
for risk in range(1, max_risk + 1):
progress.update(
overall_task,
description=f"[cyan]Testing Level {level}, Risk {risk}...",
)
success, output = self.run_sqlmap_test(
url, level, risk, techniques, data=data, verbose=verbose
)
parsed = self.parse_results(output)
status = "" if success else ""
status_style = "green" if success else "red"
findings = (
"No vulnerabilities"
if not parsed["is_vulnerable"]
else f"{len(parsed['vulnerabilities'])} found!"
)
findings_style = (
"green" if not parsed["is_vulnerable"] else "bold red"
)
if parsed["is_vulnerable"]:
self.results["vulnerabilities"].extend(
parsed["vulnerabilities"]
)
results_table.add_row(
str(level),
str(risk),
f"[{status_style}]{status}[/{status_style}]",
f"[{findings_style}]{findings}[/{findings_style}]",
)
progress.update(overall_task, advance=1)
self.results["total_tests"] += 1
self.results["end_time"] = datetime.now()
# Display results
self.console.print()
self.console.print(results_table)
self.display_summary()
def quick_scan(
self,
url: str,
level: int = 1,
risk: int = 1,
data: Optional[str] = None,
raw: bool = False,
verbose: int = 1,
):
"""Run a quick scan with default settings"""
self.results["target"] = url
self.results["start_time"] = datetime.now()
if not raw:
scan_info = f"[cyan]Running quick scan on:[/cyan]\n[yellow]{url}[/yellow]\n[dim]Level: {level}, Risk: {risk}[/dim]"
if data:
scan_info += f"\n[dim]POST Data: {data}[/dim]"
self.console.print(Panel(scan_info, border_style="cyan", box=box.ROUNDED))
if raw:
# Raw mode - just show sqlmap output directly
self.console.print("[cyan]Running sqlmap...[/cyan]\n")
success, output = self.run_sqlmap_test(
url, level, risk, data=data, verbose=verbose
)
self.console.print(output)
return
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
TimeElapsedColumn(),
console=self.console,
) as progress:
task = progress.add_task(
"[cyan]Scanning for vulnerabilities...", total=None
)
success, output = self.run_sqlmap_test(
url, level, risk, data=data, verbose=verbose
)
progress.update(task, completed=True)
parsed = self.parse_results(output)
self.results["vulnerabilities"] = parsed["vulnerabilities"]
self.results["total_tests"] = 1
self.results["end_time"] = datetime.now()
self.display_summary()
def display_summary(self):
"""Display a comprehensive summary of results"""
self.console.print()
# Calculate duration
duration = 0.0
if self.results["end_time"] and self.results["start_time"]:
duration = (
self.results["end_time"] - self.results["start_time"]
).total_seconds()
# Create summary panel
summary_text = f"""
[cyan]Target:[/cyan] {self.results["target"] or "N/A"}
[cyan]Total Tests:[/cyan] {self.results["total_tests"]}
[cyan]Duration:[/cyan] {duration:.2f} seconds
[cyan]Vulnerabilities Found:[/cyan] {len(self.results["vulnerabilities"])}
"""
self.console.print(
Panel(
summary_text.strip(),
title="[bold]Scan Summary[/bold]",
border_style="green"
if len(self.results["vulnerabilities"]) == 0
else "red",
box=box.DOUBLE,
)
)
# Display vulnerabilities if found
if self.results["vulnerabilities"]:
self.console.print()
vuln_table = Table(title="⚠️ Vulnerabilities Detected", box=box.HEAVY)
vuln_table.add_column("Parameter", style="cyan")
vuln_table.add_column("Type", style="yellow")
vuln_table.add_column("Title", style="red")
for vuln in self.results["vulnerabilities"]:
vuln_table.add_row(
vuln.get("parameter", "N/A"),
vuln.get("type", "N/A"),
vuln.get("title", "N/A"),
)
self.console.print(vuln_table)
self.console.print()
self.console.print(
"[bold red]⚠️ SQL injection vulnerabilities detected! Take immediate action.[/bold red]"
)
else:
self.console.print()
self.console.print(
"[bold green]✓ No SQL injection vulnerabilities detected.[/bold green]"
)
self.console.print()
def interactive_mode(self):
"""Interactive mode for user input"""
self.console.print()
self.console.print(
Panel(
"[cyan]Interactive Mode[/cyan]\n[dim]Enter target details for SQL injection testing[/dim]",
border_style="cyan",
)
)
url = Prompt.ask("\n[cyan]Enter target URL[/cyan]")
# Ask if this is a POST request
has_data = Confirm.ask(
"[cyan]Does this request require POST data/body?[/cyan]", default=False
)
data = None
if has_data:
self.console.print("\n[dim]Examples:[/dim]")
self.console.print(
'[dim] JSON: {"email":"test@example.com","password":"pass123"}[/dim]'
)
self.console.print("[dim] Form: username=admin&password=secret[/dim]")
data = Prompt.ask("\n[cyan]Enter POST data/body[/cyan]")
scan_type = Prompt.ask(
"\n[cyan]Select scan type[/cyan]",
choices=["quick", "comprehensive"],
default="quick",
)
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"))
self.quick_scan(url, level, risk, data=data)
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")
)
self.comprehensive_scan(url, max_level, max_risk, data=data)
def main():
parser = argparse.ArgumentParser(
description="SQLMap CLI - Beautiful automated SQL injection testing",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Quick scan with default settings (GET parameter)
python sqlmapcli.py -u "https://demo.owasp-juice.shop/rest/products/search?q=test"
# Test with POST data (JSON)
python sqlmapcli.py -u "https://demo.owasp-juice.shop/rest/user/login" --data='{"email":"test@example.com","password":"pass123"}'
# Comprehensive scan (all risk and level combinations)
python sqlmapcli.py -u "https://demo.owasp-juice.shop/rest/products/search?q=test" --comprehensive
# Custom level and risk with POST data
python sqlmapcli.py -u "https://demo.owasp-juice.shop/rest/user/login" --data='{"email":"test@example.com","password":"pass123"}' --level 3 --risk 2
# Interactive mode
python sqlmapcli.py --interactive
""",
)
parser.add_argument(
"-u", "--url", help='Target URL (e.g., "http://example.com/page?id=1")'
)
parser.add_argument(
"--comprehensive",
action="store_true",
help="Run comprehensive scan with all risk/level combinations",
)
parser.add_argument(
"--level",
type=int,
default=1,
choices=[1, 2, 3, 4, 5],
help="Level of tests to perform (1-5, default: 1)",
)
parser.add_argument(
"--risk",
type=int,
default=1,
choices=[1, 2, 3],
help="Risk of tests to perform (1-3, default: 1)",
)
parser.add_argument(
"--max-level",
type=int,
default=5,
choices=[1, 2, 3, 4, 5],
help="Maximum level for comprehensive scan (default: 5)",
)
parser.add_argument(
"--max-risk",
type=int,
default=3,
choices=[1, 2, 3],
help="Maximum risk for comprehensive scan (default: 3)",
)
parser.add_argument(
"--technique",
type=str,
default="BEUSTQ",
help="SQL injection techniques to use (default: BEUSTQ)",
)
parser.add_argument(
"--data",
type=str,
help='Data string to be sent through POST (e.g., "username=test&password=test")',
)
parser.add_argument(
"--raw", action="store_true", help="Show raw sqlmap output without formatting"
)
parser.add_argument(
"--verbose",
type=int,
choices=[0, 1, 2, 3, 4, 5, 6],
help="Sqlmap verbosity level (0-6, default: 1)",
)
parser.add_argument(
"-i", "--interactive", action="store_true", help="Run in interactive mode"
)
args = parser.parse_args()
cli = SQLMapCLI()
cli.print_banner()
# Check if sqlmap exists
if not SQLMAP_PATH.exists():
console.print(
f"[bold red]Error: sqlmap.py not found at {SQLMAP_PATH}[/bold red]",
style="bold red",
)
console.print(
"[yellow]Make sure you're running this script from the sqlmap directory[/yellow]"
)
sys.exit(1)
# Interactive mode
if args.interactive:
cli.interactive_mode()
return
# Check if URL is provided
if not args.url:
console.print(
"[bold red]Error: URL is required (use -u or --interactive)[/bold red]"
)
parser.print_help()
sys.exit(1)
# Run appropriate scan
verbose_level = args.verbose if args.verbose is not None else 1
if args.comprehensive:
cli.comprehensive_scan(
args.url,
max_level=args.max_level,
max_risk=args.max_risk,
techniques=args.technique,
data=args.data,
verbose=verbose_level,
)
else:
cli.quick_scan(
args.url,
level=args.level,
risk=args.risk,
data=args.data,
raw=args.raw,
verbose=verbose_level,
)
if __name__ == "__main__":
main()