diff --git a/AGENTS.md b/AGENTS.md index e0c9eaf..8922c37 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -157,23 +157,23 @@ All commands run via `ciberwebscan ` or `python -m ciberwebscan`: | Command | Subcommands | Description | | ------------ | ------------------------------------------------------- | ---------------------------------------------------------- | -| `analyze` | `url`, `ssl`, `fingerprint`, `cves` | Security analysis (headers, SSL, fingerprints, CVE lookup) | -| `attack` | `test`, `xss`, `sqli` | Attack surface testing | +| `analyze` | (none) | Security analysis (headers, SSL, fingerprints, CVE lookup) | +| `attack` | (none) | Attack surface testing | | `scrape` | `url`, `batch` | Web scraping (static or dynamic) | -| `quick` | `scan` | Fast combined scan | +| `quick` | (none) | Fast combined scan | | `config` | `show`, `get`, `set`, `reset`, `keys`, `export`, `load` | Configuration management | -| `api` | `run` | Start REST API server | +| `api` | (none) | Start REST API server | | `completion` | `install`, `show`, `uninstall` | Shell completion management | | `version` | (none) | Show version info | ```bash # Examples -ciberwebscan analyze url https://example.com -ciberwebscan attack xss https://example.com/search +ciberwebscan analyze https://example.com --ssl --fingerprint --cve +ciberwebscan attack https://example.com --consent --xss ciberwebscan scrape url https://example.com --dynamic -ciberwebscan quick scan https://example.com +ciberwebscan quick https://example.com ciberwebscan config set logging.level DEBUG -ciberwebscan api run --host 0.0.0.0 --port 8000 +ciberwebscan api --host 0.0.0.0 --port 8000 ciberwebscan completion install --shell zsh ``` @@ -308,7 +308,7 @@ playwright install # Required for dynamic scraping ciberwebscan # CLI entry point python -m ciberwebscan # Alternative CLI entry python -m ciberwebscan.api.app # Direct API server start -ciberwebscan api run # API via CLI +ciberwebscan api # API via CLI # Testing pytest # All tests with coverage diff --git a/Dockerfile b/Dockerfile index c401541..594bb34 100644 --- a/Dockerfile +++ b/Dockerfile @@ -66,4 +66,4 @@ ENV PYTHONDONTWRITEBYTECODE=1 EXPOSE 8000 ENTRYPOINT ["ciberwebscan"] -CMD ["api", "run", "--host", "0.0.0.0", "--port", "8000"] +CMD ["api", "--host", "0.0.0.0", "--port", "8000"] diff --git a/README.md b/README.md index 8a9ce64..0418636 100644 --- a/README.md +++ b/README.md @@ -143,13 +143,13 @@ ciberwebscan attack --url https://testsite.example.com --enumeration ```bash # Basic analysis -ciberwebscan quick scan https://example.com +ciberwebscan quick https://example.com # Analysis + scraping -ciberwebscan quick scan https://example.com -s ".content" +ciberwebscan quick https://example.com -s ".content" # Full scan with attacks (requires consent) -ciberwebscan quick scan https://example.com --preset high --consent +ciberwebscan quick https://example.com --preset high --consent ``` ### REST API Integration @@ -159,7 +159,7 @@ ciberwebscan quick scan https://example.com --preset high --consent To start the server: ```bash -ciberwebscan api run +ciberwebscan api ``` ### Interactive Documentation diff --git a/docs/API.md b/docs/API.md index 8e88cf2..99f362f 100644 --- a/docs/API.md +++ b/docs/API.md @@ -7,7 +7,7 @@ Complete REST API reference for CiberWebScan (Beta). ### Starting the API ```bash -ciberwebscan api run +ciberwebscan api # Server runs on http://localhost:8000 # API docs: http://localhost:8000/docs # ReDoc docs: http://localhost:8000/redoc diff --git a/docs/CLI.md b/docs/CLI.md index 5ffc749..b0045fe 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -22,22 +22,21 @@ ciberwebscan --help Perform security analysis on web applications. -#### analyze url - -Analyze a single URL for security issues. - ```bash -ciberwebscan analyze url [OPTIONS] +ciberwebscan analyze [OPTIONS] ``` +Activate specific analyses with flags. At least one of `--ssl`, `--fingerprint`, `--cve`, or `--analyze-headers` must be enabled. + **Options:** -- `--ssl/--no-ssl`: Perform SSL/TLS analysis (default: enabled) -- `--fingerprint/--no-fingerprint, -fp`: Perform technology fingerprinting (default: enabled) -- `--cve/--no-cve`: Look up CVEs for detected technologies (default: enabled) -- `--analyze-headers/--no-analyze-headers`: Analyze HTTP security headers (default: enabled) +- `--ssl/--no-ssl`: Perform SSL/TLS analysis (default: disabled) +- `--fingerprint/--no-fingerprint, -fp`: Perform technology fingerprinting (default: disabled) +- `--cve/--no-cve`: Look up CVEs for detected technologies (default: disabled) +- `--analyze-headers/--no-analyze-headers`: Analyze HTTP security headers (default: disabled) - `--deep`: Enable deep scanning - `--timeout, -t `: Request timeout (default: 30.0) +- `--ssl-timeout `: SSL/TLS handshake timeout (default: 10.0) - `--cve-sources `: CVE sources (comma-separated: nvd,circl,vulners) - `--cve-limit `: Maximum CVEs to retrieve (default: 100) - `--enrich-exploits, -ee`: Enrich CVEs with exploit info from Vulners @@ -53,83 +52,17 @@ ciberwebscan analyze url [OPTIONS] **Examples:** ```bash -# Full security analysis -ciberwebscan analyze url https://example.com +# SSL + fingerprinting +ciberwebscan analyze https://example.com --ssl --fingerprint -# SSL only -ciberwebscan analyze url https://example.com --no-fingerprint --no-cve +# Full analysis +ciberwebscan analyze https://example.com --ssl --fingerprint --cve --analyze-headers -# Fingerprint and CVEs only -ciberwebscan analyze url https://example.com --no-ssl +# SSL only with custom timeout +ciberwebscan analyze https://example.com --ssl --ssl-timeout 15 # Export report -ciberwebscan analyze url https://example.com -o report.json -``` - -#### analyze ssl - -Perform SSL/TLS analysis only. - -```bash -ciberwebscan analyze ssl [OPTIONS] -``` - -**Options:** - -- `--timeout, -t `: Request timeout (default: 10.0) -- `--json`: Output raw JSON - -**Examples:** - -```bash -ciberwebscan analyze ssl https://example.com -``` - -#### analyze fingerprint - -Perform technology fingerprinting only. - -```bash -ciberwebscan analyze fingerprint [OPTIONS] -``` - -**Options:** - -- `--deep`: Enable deep scanning -- `--json`: Output raw JSON - -**Examples:** - -```bash -ciberwebscan analyze fingerprint https://example.com -ciberwebscan analyze fingerprint https://example.com --deep -``` - -#### analyze cves - -Look up CVEs for specific technologies. - -```bash -ciberwebscan analyze cves [OPTIONS] -``` - -**Options:** - -- `--sources, -s `: CVE sources: nvd,circl,vulners -- `--limit, -l `: Maximum CVEs per technology (default: 50) -- `--json`: Output raw JSON - -**Examples:** - -```bash -# Single technology -ciberwebscan analyze cves nginx:1.20 - -# Multiple technologies -ciberwebscan analyze cves wordpress:5.8 php:8.1 - -# With specific sources -ciberwebscan analyze cves apache --sources nvd,circl +ciberwebscan analyze https://example.com --ssl --fingerprint -o report.json ``` ### Scrape Command @@ -193,9 +126,15 @@ ciberwebscan scrape batch [OPTIONS] - `--selector, -s `: CSS selector to extract - `--dynamic, -d`: Use browser-based scraping - `--timeout, -t `: Request timeout (default: 30.0) +- `--user-agent, -ua `: Custom user agent +- `--headers, -H `: Custom headers (format: 'Key: Value, Key2: Value2') +- `--proxy `: Proxy server +- `--cookies `: Cookies (format: 'name1=value1; name2=value2') +- `--check-robots/--no-check-robots, -cr`: Respect robots.txt (default: enabled) - `--output, -o `: Output file path - `--format, -f `: Export format (default: jsonl) - `--json`: Output raw JSON +- `--quiet, -q`: Minimal output **Examples:** @@ -205,18 +144,20 @@ ciberwebscan scrape batch https://example.com https://example.org # With selector and export ciberwebscan scrape batch url1 url2 url3 -s "h1" -o results.jsonl + +# With proxy and custom headers +ciberwebscan scrape batch url1 url2 --proxy http://proxy:8080 -H "Authorization: Bearer xxx" + +# Dynamic scraping with proxy +ciberwebscan scrape batch url1 url2 -d --proxy http://proxy:8080 ``` ### Attack Command Perform ethical penetration testing (requires explicit consent). -#### attack test - -Test for common web vulnerabilities. - ```bash -ciberwebscan attack test --consent [OPTIONS] +ciberwebscan attack --consent [OPTIONS] ``` **Critical:** The `--consent` flag is required and confirms you have permission to test the target system. @@ -248,57 +189,19 @@ ciberwebscan attack test --consent [OPTIONS] ```bash # XSS testing with consent -ciberwebscan attack test https://example.com --consent --xss +ciberwebscan attack https://example.com --consent --xss # Multiple attack types -ciberwebscan attack test https://example.com --consent --xss --sqli +ciberwebscan attack https://example.com --consent --xss --sqli # CSRF testing -ciberwebscan attack test https://example.com --consent --csrf +ciberwebscan attack https://example.com --consent --csrf # All attacks with low intensity -ciberwebscan attack test https://example.com --consent --all --intensity low +ciberwebscan attack https://example.com --consent --all --intensity low # Custom payloads -ciberwebscan attack test https://example.com --consent --xss --payloads my_payloads.json -``` - -#### attack xss - -Test only for XSS vulnerabilities. - -```bash -ciberwebscan attack xss --consent [OPTIONS] -``` - -**Options:** - -- `--intensity, -i `: Attack intensity: low, medium, high (default: medium) -- `--json`: Output raw JSON - -**Examples:** - -```bash -ciberwebscan attack xss https://example.com --consent -``` - -#### attack sqli - -Test only for SQL injection vulnerabilities. - -```bash -ciberwebscan attack sqli --consent [OPTIONS] -``` - -**Options:** - -- `--intensity, -i `: Attack intensity: low, medium, high (default: medium) -- `--json`: Output raw JSON - -**Examples:** - -```bash -ciberwebscan attack sqli https://example.com/product?id=1 --consent +ciberwebscan attack https://example.com --consent --xss --payloads my_payloads.json ``` ### Quick Scan Command @@ -306,7 +209,7 @@ ciberwebscan attack sqli https://example.com/product?id=1 --consent Combined scan using presets: analysis + attacks + scraping in one command. ```bash -ciberwebscan quick scan [OPTIONS] +ciberwebscan quick [OPTIONS] ``` **Options:** @@ -338,55 +241,49 @@ ciberwebscan quick scan [OPTIONS] ```bash # Basic analysis (preset low) -ciberwebscan quick scan https://example.com +ciberwebscan quick https://example.com # Analysis with scraping -ciberwebscan quick scan https://example.com -s ".content" +ciberwebscan quick https://example.com -s ".content" # Medium scan with attacks (requires consent) -ciberwebscan quick scan https://example.com --preset medium --consent +ciberwebscan quick https://example.com --preset medium --consent # Full scan with dynamic scraping -ciberwebscan quick scan https://example.com --preset high --consent -d +ciberwebscan quick https://example.com --preset high --consent -d # Export combined report -ciberwebscan quick scan https://example.com --preset high --consent -o report.json +ciberwebscan quick https://example.com --preset high --consent -o report.json # JSON output for automation -ciberwebscan quick scan https://example.com --json --quiet +ciberwebscan quick https://example.com --json --quiet ``` ### API Command Manage and run the CiberWebScan REST API server. -#### api run - -Start the REST API server using Uvicorn. - ```bash -ciberwebscan api run [OPTIONS] +ciberwebscan api [OPTIONS] ``` **Options:** -- --host : Bind socket to this host (default: 0.0.0.0) - -- --port : Bind socket to this port (default: 8000) - -- --reload: Enable auto-reload (development mode) +- `--host `: Bind socket to this host (default: 0.0.0.0) +- `--port `: Bind socket to this port (default: 8000) +- `--reload`: Enable auto-reload (development mode) **Examples:** ```bash # Start the API server on default port 8000 -ciberwebscan api run +ciberwebscan api # Start on a custom port and host -ciberwebscan api run --host 127.0.0.1 --port 9000 +ciberwebscan api --host 127.0.0.1 --port 9000 # Run in development mode with auto-reload -ciberwebscan api run --reload +ciberwebscan api --reload ``` ### Completion Command @@ -639,7 +536,7 @@ The CLI provides clear error messages and exit codes: ### Complete Security Assessment ```bash -ciberwebscan analyze url https://target.com \ +ciberwebscan analyze https://target.com \ --ssl \ --fingerprint \ --headers \ @@ -661,7 +558,7 @@ ciberwebscan scrape url https://news.com \ ### Ethical Testing ```bash -ciberwebscan attack test https://testsite.com \ +ciberwebscan attack https://testsite.com \ --consent \ --xss \ --sqli \ @@ -673,11 +570,11 @@ ciberwebscan attack test https://testsite.com \ ```bash # Basic analysis -ciberwebscan quick scan https://example.com +ciberwebscan quick https://example.com # With scraping and export -ciberwebscan quick scan https://example.com -s ".data" -o quick_report.json +ciberwebscan quick https://example.com -s ".data" -o quick_report.json # Full scan with attacks -ciberwebscan quick scan https://example.com --preset high --consent -o report.json +ciberwebscan quick https://example.com --preset high --consent -o report.json ``` diff --git a/src/ciberwebscan/cli/app.py b/src/ciberwebscan/cli/app.py index 4624841..8d33fa9 100644 --- a/src/ciberwebscan/cli/app.py +++ b/src/ciberwebscan/cli/app.py @@ -9,12 +9,12 @@ import typer -from ciberwebscan.cli.commands.analyze import analyze -from ciberwebscan.cli.commands.api import api -from ciberwebscan.cli.commands.attack import attack +from ciberwebscan.cli.commands.analyze import analyze_cmd +from ciberwebscan.cli.commands.api import run_api +from ciberwebscan.cli.commands.attack import attack_cmd from ciberwebscan.cli.commands.completion import completion_app from ciberwebscan.cli.commands.config import config -from ciberwebscan.cli.commands.quick import quick +from ciberwebscan.cli.commands.quick import quick_cmd from ciberwebscan.cli.commands.scrape import scrape from ciberwebscan.cli.output import print_info @@ -29,13 +29,15 @@ # Register command groups app.add_typer(scrape, name="scrape") -app.add_typer(analyze, name="analyze") -app.add_typer(attack, name="attack") -app.add_typer(quick, name="quick") app.add_typer(config, name="config") -app.add_typer(api, name="api") app.add_typer(completion_app, name="completion") +# Register direct commands +app.command("analyze")(analyze_cmd) +app.command("api")(run_api) +app.command("attack")(attack_cmd) +app.command("quick")(quick_cmd) + @app.command("version") def version() -> None: diff --git a/src/ciberwebscan/cli/commands/__init__.py b/src/ciberwebscan/cli/commands/__init__.py index 70cec18..e70036e 100644 --- a/src/ciberwebscan/cli/commands/__init__.py +++ b/src/ciberwebscan/cli/commands/__init__.py @@ -2,16 +2,16 @@ CLI commands for CiberWebScan. """ -from ciberwebscan.cli.commands.analyze import analyze -from ciberwebscan.cli.commands.attack import attack +from ciberwebscan.cli.commands.analyze import analyze_cmd +from ciberwebscan.cli.commands.attack import attack_cmd from ciberwebscan.cli.commands.completion import completion_app from ciberwebscan.cli.commands.config import config from ciberwebscan.cli.commands.scrape import scrape __all__ = [ "scrape", - "analyze", - "attack", + "analyze_cmd", + "attack_cmd", "config", "completion_app", ] diff --git a/src/ciberwebscan/cli/commands/analyze.py b/src/ciberwebscan/cli/commands/analyze.py index a8d9717..0e70822 100644 --- a/src/ciberwebscan/cli/commands/analyze.py +++ b/src/ciberwebscan/cli/commands/analyze.py @@ -15,10 +15,7 @@ format_analysis_result, format_service_result, print_error, - print_header, print_info, - print_key_value, - print_subheader, ) from ciberwebscan.cli.validators import ( ValidationError, @@ -28,12 +25,6 @@ ) from ciberwebscan.config.loader import get_config -analyze = typer.Typer( - name="analyze", - help="Security analysis commands.", - no_args_is_help=True, -) - try: _DEFAULT_ANALYZE_TIMEOUT = get_config().http.timeout.read _DEFAULT_ANALYZE_SSL_TIMEOUT = get_config().http.timeout.connect @@ -42,14 +33,13 @@ _DEFAULT_ANALYZE_SSL_TIMEOUT = 10.0 -@analyze.command("url") -def analyze_url( +def analyze_cmd( url: Annotated[str, typer.Argument(help="URL to analyze")], - # Analysis types + # Analysis types — all disabled by default, activate with flags ssl: Annotated[ bool, typer.Option("--ssl/--no-ssl", help="Perform SSL/TLS analysis"), - ] = True, + ] = False, fingerprint: Annotated[ bool, typer.Option( @@ -57,18 +47,18 @@ def analyze_url( "-fp", help="Perform technology fingerprinting", ), - ] = True, + ] = False, cve: Annotated[ bool, typer.Option("--cve/--no-cve", help="Look up CVEs for detected technologies"), - ] = True, + ] = False, analyze_headers: Annotated[ bool, typer.Option( "--analyze-headers/--no-analyze-headers", help="Analyze HTTP security headers", ), - ] = True, + ] = False, # Options deep: Annotated[ bool, @@ -78,6 +68,13 @@ def analyze_url( float, typer.Option("--timeout", "-t", help="Request timeout in seconds"), ] = _DEFAULT_ANALYZE_TIMEOUT, + ssl_timeout: Annotated[ + float, + typer.Option( + "--ssl-timeout", + help="SSL/TLS handshake timeout in seconds", + ), + ] = _DEFAULT_ANALYZE_SSL_TIMEOUT, # CVE options cve_sources: Annotated[ str | None, @@ -143,21 +140,29 @@ def analyze_url( """ Perform security analysis on a URL. + Activate specific analyses with flags. At least one of --ssl, --fingerprint, + --cve, or --analyze-headers must be enabled. + Examples: - # Full analysis - ciberwebscan analyze url https://example.com + # SSL + fingerprinting + ciberwebscan analyze https://example.com --ssl --fingerprint - # SSL only - ciberwebscan analyze url https://example.com --no-fingerprint --no-cve + # Full analysis + ciberwebscan analyze https://example.com --ssl --fingerprint --cve --analyze-headers - # Fingerprint and CVEs only - ciberwebscan analyze url https://example.com --no-ssl + # SSL only with custom timeout + ciberwebscan analyze https://example.com --ssl --ssl-timeout 15 # Export report - ciberwebscan analyze url https://example.com -o report.json + ciberwebscan analyze https://example.com --ssl --fingerprint -o report.json """ try: + if not any([ssl, fingerprint, cve, analyze_headers]): + print_error("No analysis types selected") + print_info("Use --ssl, --fingerprint, --cve, or --analyze-headers") + sys.exit(2) + validated_url = validate_url(url) validate_timeout(timeout) if output: @@ -192,7 +197,7 @@ def analyze_url( headers=headers_dict, deep_scan=deep, timeout=timeout, - ssl_timeout=timeout, + ssl_timeout=ssl_timeout, cve_sources=sources, cve_limit=cve_limit, export=output, @@ -234,229 +239,3 @@ def analyze_url( except Exception as e: print_error(f"Unexpected error: {e}") sys.exit(1) - - -@analyze.command("ssl") -def analyze_ssl( - url: Annotated[str, typer.Argument(help="URL to analyze")], - timeout: Annotated[ - float, - typer.Option("--timeout", "-t", help="Timeout in seconds"), - ] = _DEFAULT_ANALYZE_SSL_TIMEOUT, - json_output: Annotated[ - bool, - typer.Option("--json", help="Output raw JSON"), - ] = False, -) -> None: - """ - Perform SSL/TLS analysis only. - - Examples: - - ciberwebscan analyze ssl https://example.com - """ - try: - validated_url = validate_url(url, allow_http=False) - - from ciberwebscan.services import AnalyzeService - - print_info(f"Analyzing SSL: {validated_url}") - - service = AnalyzeService() - result = service.analyze_ssl(validated_url, ssl_timeout=timeout) - - if json_output: - exit_code = format_service_result(result, json_output=True) - else: - if result.success and result.data: - ssl = result.data - print_header("SSL/TLS Analysis") - print_key_value("HTTPS", "Yes" if ssl.is_https else "No") - if ssl.protocol_version: - print_key_value("Protocol", ssl.protocol_version) - if ssl.cipher_suite: - print_key_value("Cipher", ssl.cipher_suite) - if ssl.grade: - print_key_value("Grade", ssl.grade) - if ssl.chain_valid is not None: - print_key_value("Chain Valid", "Yes" if ssl.chain_valid else "No") - if ssl.findings: - print_subheader("Findings") - for f in ssl.findings: - print_info(f" - {f}") - exit_code = format_service_result(result, json_output=False) - - sys.exit(exit_code) - - except ValidationError as e: - print_error(str(e)) - sys.exit(2) - except Exception as e: - print_error(f"Unexpected error: {e}") - sys.exit(1) - - -@analyze.command("fingerprint") -def analyze_fingerprint( - url: Annotated[str, typer.Argument(help="URL to fingerprint")], - deep: Annotated[ - bool, - typer.Option("--deep", help="Enable deep scanning"), - ] = False, - json_output: Annotated[ - bool, - typer.Option("--json", help="Output raw JSON"), - ] = False, -) -> None: - """ - Perform technology fingerprinting only. - - Examples: - - ciberwebscan analyze fingerprint https://example.com - ciberwebscan analyze fingerprint https://example.com --deep - """ - try: - validated_url = validate_url(url) - - from ciberwebscan.services import AnalyzeService - - print_info(f"Fingerprinting: {validated_url}") - - service = AnalyzeService() - result = service.fingerprint_url(validated_url, deep=deep) - - if json_output: - exit_code = format_service_result(result, json_output=True) - else: - if result.success and result.data: - fp = result.data - print_header("Technology Fingerprint") - - if fp.server: - print_key_value("Server", fp.server) - if fp.powered_by: - print_key_value("Powered By", fp.powered_by) - if fp.framework: - print_key_value("Framework", fp.framework) - if fp.cms: - print_key_value("CMS", fp.cms) - - if fp.technologies: - print_subheader(f"Technologies ({len(fp.technologies)})") - for tech in fp.technologies: - version = f" v{tech.version}" if tech.version else "" - category = f" [{tech.category}]" if tech.category else "" - print_info(f" - {tech.name}{version}{category}") - exit_code = format_service_result(result, json_output=False) - - sys.exit(exit_code) - - except ValidationError as e: - print_error(str(e)) - sys.exit(2) - except Exception as e: - print_error(f"Unexpected error: {e}") - sys.exit(1) - - -@analyze.command("cves") -def analyze_cves( - technology: Annotated[ - list[str], - typer.Argument( - help="Technology names to look up (e.g., 'nginx:1.20' or 'wordpress')" - ), - ], - sources: Annotated[ - str | None, - typer.Option("--sources", "-s", help="CVE sources: nvd,circl,vulners"), - ] = None, - limit: Annotated[ - int, - typer.Option("--limit", "-l", help="Maximum CVEs per technology"), - ] = 50, - json_output: Annotated[ - bool, - typer.Option("--json", help="Output raw JSON"), - ] = False, -) -> None: - """ - Look up CVEs for specific technologies. - - Examples: - - # Single technology - ciberwebscan analyze cves nginx:1.20 - - # Multiple technologies - ciberwebscan analyze cves wordpress:5.8 php:8.1 - - # With specific sources - ciberwebscan analyze cves apache --sources nvd,circl - """ - try: - if not technology: - print_error("At least one technology is required") - sys.exit(2) - - from ciberwebscan.export.models import ConfidenceLevel, TechnologyMatch - from ciberwebscan.services import AnalyzeService - - # Parse technologies (format: name or name:version) - tech_list = [] - for t in technology: - if ":" in t: - name, version = t.split(":", 1) - else: - name, version = t, None - tech_list.append( - TechnologyMatch( - name=name, - version=version, - category="unknown", - confidence=ConfidenceLevel.HIGH, - ) - ) - - source_list = sources.split(",") if sources else ["nvd"] - - print_info(f"Looking up CVEs for: {', '.join(technology)}") - print_info(f"Sources: {', '.join(source_list)}") - - service = AnalyzeService() - result = service.lookup_cves( - tech_list, cve_sources=source_list, cve_limit=limit - ) - - if json_output: - exit_code = format_service_result(result, json_output=True) - else: - if result.success and result.data: - cves = result.data - print_header(f"CVEs Found ({len(cves)})") - - for cve in cves: - severity = ( - f"[{cve.severity.value}]" - if hasattr(cve, "severity") and cve.severity - else "" - ) - print_info(f"\n{cve.id} {severity}") - if cve.description: - desc = cve.description[:100] - if len(cve.description) > 100: - desc += "..." - print_info(f" {desc}") - if hasattr(cve, "cvss_score") and cve.cvss_score: - print_key_value("CVSS", cve.cvss_score, indent=1) - exit_code = format_service_result(result, json_output=False) - - sys.exit(exit_code) - - except ValidationError as e: - print_error(str(e)) - sys.exit(2) - except Exception as e: - print_error(f"Unexpected error: {e}") - sys.exit(1) diff --git a/src/ciberwebscan/cli/commands/api.py b/src/ciberwebscan/cli/commands/api.py index 1ae831c..2d4a277 100644 --- a/src/ciberwebscan/cli/commands/api.py +++ b/src/ciberwebscan/cli/commands/api.py @@ -13,14 +13,7 @@ from ciberwebscan.cli.output import print_error, print_info, print_success from ciberwebscan.config.loader import get_config -api = typer.Typer( - name="api", - help="API server management commands.", - no_args_is_help=True, -) - -@api.command("run") def run_api( host: Annotated[ str | None, @@ -45,13 +38,17 @@ def run_api( ] = False, ) -> None: """Start the CiberWebScan API server. + Examples: + # Run with default config settings - ciberwebscan api run + ciberwebscan api + # Run on custom host and port - ciberwebscan api run --host 0.0.0.0 --port 9000 + ciberwebscan api --host 0.0.0.0 --port 9000 + # Disable auto-reload for production - ciberwebscan api run --no-reload + ciberwebscan api --no-reload """ try: # Import here to avoid issues if uvicorn is not installed for CLI-only installs diff --git a/src/ciberwebscan/cli/commands/attack.py b/src/ciberwebscan/cli/commands/attack.py index f59a1d4..5200cca 100644 --- a/src/ciberwebscan/cli/commands/attack.py +++ b/src/ciberwebscan/cli/commands/attack.py @@ -31,20 +31,13 @@ ) from ciberwebscan.config.loader import get_config -attack = typer.Typer( - name="attack", - help="Security attack simulation commands.", - no_args_is_help=True, -) - try: _DEFAULT_ATTACK_TIMEOUT = get_config().http.timeout.connect except Exception: _DEFAULT_ATTACK_TIMEOUT = 10.0 -@attack.command("test") -def attack_test( +def attack_cmd( url: Annotated[str, typer.Argument(help="URL to test")], # CRITICAL: User consent consent: Annotated[ @@ -185,19 +178,19 @@ def attack_test( Examples: # Test XSS with consent - ciberwebscan attack test https://example.com --consent --xss + ciberwebscan attack https://example.com --consent --xss # Test multiple attack types - ciberwebscan attack test https://example.com --consent --xss --sqli + ciberwebscan attack https://example.com --consent --xss --sqli # Run all attacks with low intensity - ciberwebscan attack test https://example.com --consent --all --intensity low + ciberwebscan attack https://example.com --consent --all --intensity low # Export findings report - ciberwebscan attack test https://example.com --consent --xss -o findings.json + ciberwebscan attack https://example.com --consent --xss -o findings.json # Custom payloads - ciberwebscan attack test https://example.com --consent --xss --payloads my_xss.json + ciberwebscan attack https://example.com --consent --xss --payloads my_xss.json """ try: # Get config first — it may pre-authorise consent @@ -400,135 +393,3 @@ def attack_test( print_error(traceback.format_exc()) sys.exit(1) - - -@attack.command("xss") -def attack_xss( - url: Annotated[str, typer.Argument(help="URL to test for XSS")], - consent: Annotated[ - bool, - typer.Option("--consent", help="Confirm permission (REQUIRED)"), - ] = False, - intensity: Annotated[ - str, - typer.Option("--intensity", "-i", help="Attack intensity: low, medium, high"), - ] = "medium", - json_output: Annotated[ - bool, - typer.Option("--json", help="Output raw JSON"), - ] = False, -) -> None: - """ - Test only for XSS vulnerabilities. - - Examples: - - ciberwebscan attack xss https://example.com --consent - """ - try: - app_config = get_config() - effective_consent = consent or app_config.attack.user_consent - if not effective_consent: - print_error("USER CONSENT REQUIRED. Use --consent flag.") - sys.exit(2) - - validated_url = validate_url(url) - - from ciberwebscan.services import AttackOptions, AttackService - - print_warning("Testing for XSS vulnerabilities") - print_info(f"Target: {validated_url}") - - options = AttackOptions( - url=validated_url, - user_consent=True, - xss=True, - intensity=intensity, - ) - - service = AttackService() - result = service.attack(options) - - if json_output: - exit_code = format_service_result(result, json_output=True) - else: - if result.success and result.data: - print_header("XSS Test Results") - print_key_value("Findings", result.data.xss_findings) - print_key_value("Payloads Tested", result.data.total_payloads_tested) - exit_code = format_service_result(result, json_output=False) - - sys.exit(exit_code) - - except ValidationError as e: - print_error(str(e)) - sys.exit(2) - except Exception as e: - print_error(f"Unexpected error: {e}") - sys.exit(1) - - -@attack.command("sqli") -def attack_sqli( - url: Annotated[str, typer.Argument(help="URL to test for SQL injection")], - consent: Annotated[ - bool, - typer.Option("--consent", help="Confirm permission (REQUIRED)"), - ] = False, - intensity: Annotated[ - str, - typer.Option("--intensity", "-i", help="Attack intensity: low, medium, high"), - ] = "medium", - json_output: Annotated[ - bool, - typer.Option("--json", help="Output raw JSON"), - ] = False, -) -> None: - """ - Test only for SQL injection vulnerabilities. - - Examples: - - ciberwebscan attack sqli https://example.com/product?id=1 --consent - """ - try: - app_config = get_config() - effective_consent = consent or app_config.attack.user_consent - if not effective_consent: - print_error("USER CONSENT REQUIRED. Use --consent flag.") - sys.exit(2) - - validated_url = validate_url(url) - - from ciberwebscan.services import AttackOptions, AttackService - - print_warning("Testing for SQL injection vulnerabilities") - print_info(f"Target: {validated_url}") - - options = AttackOptions( - url=validated_url, - user_consent=True, - sqli=True, - intensity=intensity, - ) - - service = AttackService() - result = service.attack(options) - - if json_output: - exit_code = format_service_result(result, json_output=True) - else: - if result.success and result.data: - print_header("SQLi Test Results") - print_key_value("Findings", result.data.sqli_findings) - print_key_value("Payloads Tested", result.data.total_payloads_tested) - exit_code = format_service_result(result, json_output=False) - - sys.exit(exit_code) - - except ValidationError as e: - print_error(str(e)) - sys.exit(2) - except Exception as e: - print_error(f"Unexpected error: {e}") - sys.exit(1) diff --git a/src/ciberwebscan/cli/commands/quick.py b/src/ciberwebscan/cli/commands/quick.py index af1b618..b2ce0d9 100644 --- a/src/ciberwebscan/cli/commands/quick.py +++ b/src/ciberwebscan/cli/commands/quick.py @@ -30,20 +30,13 @@ ) from ciberwebscan.config.loader import get_config -quick = typer.Typer( - name="quick", - help="Quick scan with analysis + attacks + scraping.", - no_args_is_help=True, -) - try: _DEFAULT_TIMEOUT = get_config().http.timeout.read except Exception: _DEFAULT_TIMEOUT = 30.0 -@quick.command("scan") -def quick_scan_cmd( +def quick_cmd( url: Annotated[str, typer.Argument(help="URL to scan")], # Preset preset: Annotated[ @@ -142,19 +135,19 @@ def quick_scan_cmd( Examples: # Basic analysis (preset low) - ciberwebscan quick scan https://example.com + ciberwebscan quick https://example.com # Analysis + scraping - ciberwebscan quick scan https://example.com -s ".content" + ciberwebscan quick https://example.com -s ".content" # Medium scan with attacks (requires consent) - ciberwebscan quick scan https://example.com --preset medium --consent + ciberwebscan quick https://example.com --preset medium --consent # Full scan with dynamic scraping - ciberwebscan quick scan https://example.com --preset high --consent -d + ciberwebscan quick https://example.com --preset high --consent -d # Export combined report - ciberwebscan quick scan https://example.com --preset high --consent -o report.json + ciberwebscan quick https://example.com --preset high --consent -o report.json """ try: # Validate inputs diff --git a/src/ciberwebscan/cli/commands/scrape.py b/src/ciberwebscan/cli/commands/scrape.py index 4338b48..21ae15b 100644 --- a/src/ciberwebscan/cli/commands/scrape.py +++ b/src/ciberwebscan/cli/commands/scrape.py @@ -261,6 +261,35 @@ def scrape_batch( float, typer.Option("--timeout", "-t", help="Request timeout in seconds"), ] = _DEFAULT_SCRAPE_TIMEOUT, + # Network options + user_agent: Annotated[ + str | None, + typer.Option("--user-agent", "-ua", help="Custom User-Agent string"), + ] = None, + headers: Annotated[ + str | None, + typer.Option( + "--headers", + "-H", + help="Custom headers (format: 'Key: Value, Key2: Value2')", + ), + ] = None, + proxy: Annotated[ + str | None, + typer.Option("--proxy", help="HTTP/HTTPS proxy URL"), + ] = None, + cookies: Annotated[ + str | None, + typer.Option( + "--cookies", "-c", help="Cookies (format: 'name1=value1; name2=value2')" + ), + ] = None, + check_robots: Annotated[ + bool, + typer.Option( + "--check-robots/--no-check-robots", "-cr", help="Respect robots.txt" + ), + ] = True, # Export output: Annotated[ str | None, @@ -275,6 +304,10 @@ def scrape_batch( bool, typer.Option("--json", help="Output raw JSON"), ] = False, + quiet: Annotated[ + bool, + typer.Option("--quiet", "-q", help="Minimal output"), + ] = False, ) -> None: """ Scrape multiple URLs. @@ -294,18 +327,40 @@ def scrape_batch( validated_urls = [validate_url(u) for u in urls] + # Parse headers if provided + headers_dict: dict[str, str] = {} + if headers: + for pair in headers.split(","): + if ":" in pair: + key, value = pair.split(":", 1) + headers_dict[key.strip()] = value.strip() + + # Parse cookies if provided + cookies_dict: dict[str, str] = {} + if cookies: + for pair in cookies.split(";"): + if "=" in pair: + key, value = pair.split("=", 1) + cookies_dict[key.strip()] = value.strip() + from ciberwebscan.services import ScrapeOptions, ScrapeService options = ScrapeOptions( - url=validated_urls[0], # Base options + url=validated_urls[0], dynamic=dynamic, timeout=timeout, selector=selector, + headers=headers_dict, + cookies=cookies_dict, + proxy=proxy, + user_agent=user_agent, + check_robots=check_robots, export=output, export_format=format, ) - print_info(f"Scraping {len(validated_urls)} URLs...") + if not quiet: + print_info(f"Scraping {len(validated_urls)} URLs...") service = ScrapeService() result = service.scrape_multiple(validated_urls, options) diff --git a/src/ciberwebscan/services/scrape_service.py b/src/ciberwebscan/services/scrape_service.py index 5486c5a..2430a15 100644 --- a/src/ciberwebscan/services/scrape_service.py +++ b/src/ciberwebscan/services/scrape_service.py @@ -317,6 +317,9 @@ def scrape_multiple( attributes=options.attributes, headers=options.headers, cookies=options.cookies, + proxy=options.proxy, + user_agent=options.user_agent, + check_robots=options.check_robots, ) single_result = self.scrape(opts) diff --git a/tests/integration/cli/test_attack_cli.py b/tests/integration/cli/test_attack_cli.py index b9d9866..fe1b942 100644 --- a/tests/integration/cli/test_attack_cli.py +++ b/tests/integration/cli/test_attack_cli.py @@ -122,9 +122,7 @@ class TestConsentValidation: def test_attack_without_consent_fails(self, test_server): """Test that attack without --consent flag fails.""" - result = run_cli_command( - ["attack", "test", f"{test_server}/xss?q=test", "--xss"] - ) + result = run_cli_command(["attack", f"{test_server}/xss?q=test", "--xss"]) assert result["returncode"] == 2 assert "USER CONSENT REQUIRED" in result["stderr"] @@ -132,7 +130,7 @@ def test_attack_without_consent_fails(self, test_server): def test_attack_without_attack_types_fails(self, test_server): """Test that attack without attack types fails.""" - result = run_cli_command(["attack", "test", f"{test_server}/", "--consent"]) + result = run_cli_command(["attack", f"{test_server}/", "--consent"]) assert result["returncode"] == 2 assert "No attack types selected" in result["stderr"] @@ -153,7 +151,6 @@ def test_xss_detection_basic(self, test_server, tmp_path): result = run_cli_command( [ "attack", - "test", f"{test_server}/xss?q=test", "--consent", "--xss", @@ -179,14 +176,14 @@ def test_xss_detection_basic(self, test_server, tmp_path): assert "attack" in data assert data["attack"]["target_url"] == f"{test_server}/xss?q=test" - def test_xss_command_shortcut(self, test_server): - """Test XSS-only command shortcut.""" + def test_xss_only_via_flag(self, test_server): + """Test XSS-only via attack test --xss flag.""" result = run_cli_command( [ "attack", - "xss", f"{test_server}/xss?q=test", "--consent", + "--xss", "--intensity", "low", ] @@ -195,16 +192,16 @@ def test_xss_command_shortcut(self, test_server): # CLI may return non-zero when attacks encounter issues on the test server; # accept 0 or 1 as valid exit codes for this integration test. assert result["returncode"] in [0, 1] - assert "XSS Test Results" in result["stdout"] + assert "Attack Results" in result["stdout"] def test_xss_json_output(self, test_server): - """Test XSS with JSON output.""" + """Test XSS with JSON output via attack test.""" result = run_cli_command( [ "attack", - "xss", f"{test_server}/xss?q=test", "--consent", + "--xss", "--json", ] ) @@ -228,7 +225,6 @@ def test_sqli_detection_basic(self, test_server): result = run_cli_command( [ "attack", - "test", f"{test_server}/user?id=1", "--consent", "--sqli", @@ -241,19 +237,21 @@ def test_sqli_detection_basic(self, test_server): assert result["returncode"] == 0 - def test_sqli_command_shortcut(self, test_server): - """Test SQLi-only command shortcut.""" + def test_sqli_only_via_flag(self, test_server): + """Test SQLi-only via attack test --sqli flag.""" result = run_cli_command( [ "attack", - "sqli", f"{test_server}/user?id=1", "--consent", + "--sqli", + "--intensity", + "low", ] ) assert result["returncode"] == 0 - assert "SQLi Test Results" in result["stdout"] + assert "Attack Results" in result["stdout"] # ============================================================================= @@ -269,7 +267,6 @@ def test_multiple_attack_types(self, test_server): result = run_cli_command( [ "attack", - "test", f"{test_server}/", "--consent", "--xss", @@ -289,7 +286,6 @@ def test_all_attacks_flag(self, test_server): result = run_cli_command( [ "attack", - "test", f"{test_server}/", "--consent", "--all", @@ -329,7 +325,6 @@ def test_intensity_levels(self, test_server, intensity): result = run_cli_command( [ "attack", - "test", f"{test_server}/xss?q=test", "--consent", "--xss", @@ -348,7 +343,6 @@ def test_invalid_intensity(self, test_server): result = run_cli_command( [ "attack", - "test", f"{test_server}/", "--consent", "--xss", @@ -378,7 +372,6 @@ def test_export_formats(self, test_server, tmp_path, format): result = run_cli_command( [ "attack", - "test", f"{test_server}/xss?q=test", "--consent", "--xss", @@ -409,7 +402,6 @@ def test_custom_user_agent(self, test_server): result = run_cli_command( [ "attack", - "test", f"{test_server}/", "--consent", "--xss", @@ -427,7 +419,6 @@ def test_custom_timeout(self, test_server): result = run_cli_command( [ "attack", - "test", f"{test_server}/", "--consent", "--xss", @@ -445,7 +436,6 @@ def test_custom_headers(self, test_server): result = run_cli_command( [ "attack", - "test", f"{test_server}/", "--consent", "--xss", @@ -472,7 +462,6 @@ def test_quiet_mode(self, test_server): result = run_cli_command( [ "attack", - "test", f"{test_server}/", "--consent", "--xss", @@ -491,7 +480,6 @@ def test_verbose_mode(self, test_server): result = run_cli_command( [ "attack", - "test", f"{test_server}/", "--consent", "--xss", @@ -517,7 +505,6 @@ def test_invalid_url(self): result = run_cli_command( [ "attack", - "test", "not-a-valid-url", "--consent", "--xss", @@ -531,7 +518,6 @@ def test_unreachable_url(self): result = run_cli_command( [ "attack", - "test", "http://192.0.2.1:9999/", # TEST-NET-1 (should be unreachable) "--consent", "--xss", @@ -561,7 +547,6 @@ def test_detects_reflected_xss(self, test_server, tmp_path): result = run_cli_command( [ "attack", - "test", f"{test_server}/xss?q=test", "--consent", "--xss", @@ -588,7 +573,6 @@ def test_detects_sqli_error(self, test_server, tmp_path): result = run_cli_command( [ "attack", - "test", f"{test_server}/user?id=1", "--consent", "--sqli", diff --git a/tests/unit/cli/test_commands.py b/tests/unit/cli/test_commands.py index 6695fb8..35fd6f9 100644 --- a/tests/unit/cli/test_commands.py +++ b/tests/unit/cli/test_commands.py @@ -2,6 +2,7 @@ from __future__ import annotations +import re from unittest.mock import MagicMock, patch from typer.testing import CliRunner @@ -11,6 +12,11 @@ runner = CliRunner() +def _strip_ansi(text: str) -> str: + """Remove ANSI escape codes from text.""" + return re.sub(r"\x1b\[[0-9;]*m", "", text) + + class TestAppBasic: """Tests for basic CLI app functionality.""" @@ -93,27 +99,17 @@ def test_analyze_help(self): assert result.exit_code == 0 assert "analyze" in result.stdout.lower() - def test_analyze_url_help(self): - """Test analyze url --help.""" - result = runner.invoke(app, ["analyze", "url", "--help"]) - assert result.exit_code == 0 - assert "ssl" in result.stdout.lower() - assert "fingerprint" in result.stdout.lower() - - def test_analyze_ssl_help(self): - """Test analyze ssl --help.""" - result = runner.invoke(app, ["analyze", "ssl", "--help"]) - assert result.exit_code == 0 - - def test_analyze_fingerprint_help(self): - """Test analyze fingerprint --help.""" - result = runner.invoke(app, ["analyze", "fingerprint", "--help"]) + def test_analyze_ssl_flag(self): + """Test analyze --ssl flag is available.""" + result = runner.invoke(app, ["analyze", "--help"]) assert result.exit_code == 0 + assert "--ssl" in _strip_ansi(result.stdout).lower() - def test_analyze_cves_help(self): - """Test analyze cves --help.""" - result = runner.invoke(app, ["analyze", "cves", "--help"]) + def test_analyze_fingerprint_flag(self): + """Test analyze --fingerprint flag is available.""" + result = runner.invoke(app, ["analyze", "--help"]) assert result.exit_code == 0 + assert "--fingerprint" in _strip_ansi(result.stdout).lower() @patch("ciberwebscan.services.AnalyzeService") def test_analyze_url_success(self, mock_service_class): @@ -133,7 +129,7 @@ def test_analyze_url_success(self, mock_service_class): mock_service.analyze.return_value = mock_result mock_service_class.return_value = mock_service - result = runner.invoke(app, ["analyze", "url", "https://example.com", "-q"]) + result = runner.invoke(app, ["analyze", "https://example.com", "--ssl", "-q"]) assert result.exit_code == 0