Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 94c8bbede9 |
@@ -61,6 +61,9 @@ def scan(
|
||||
out: Path = typer.Option(Path("/data/out"), "--out", help="Output directory"),
|
||||
enable_nuclei: bool = typer.Option(False, "--enable-nuclei", help="Run nuclei (needs network/time)"),
|
||||
passive_only: bool = typer.Option(False, "--passive-only", help="Passive enumeration only"),
|
||||
ports: str | None = typer.Option(None, "--ports", help="Explicit naabu port spec, e.g. '22,80,5000' or '1-10000'. Overrides --top-ports."),
|
||||
top_ports: int = typer.Option(1000, "--top-ports", help="Scan naabu's top-N ports when --ports is unset (default 1000)."),
|
||||
full_ports: bool = typer.Option(False, "--full-ports", help="Full 1-65535 sweep (slow). Shorthand for --ports '-'."),
|
||||
rate_limit: int | None = typer.Option(None, "--rate-limit", help="Per-tool rate limit"),
|
||||
timeout: float = typer.Option(600.0, "--timeout", help="Per-tool timeout (seconds)"),
|
||||
templates_dir: str | None = typer.Option(None, "--templates-dir", help="nuclei templates cache dir"),
|
||||
@@ -71,6 +74,7 @@ def scan(
|
||||
typer.echo(AUTH_NOTICE)
|
||||
sc = Scope.load(scope)
|
||||
run_id = f"scan-{_utcnow()}"
|
||||
port_spec = "-" if full_ports else ports
|
||||
report = run_scan(
|
||||
run_id=run_id,
|
||||
target=target,
|
||||
@@ -80,6 +84,8 @@ def scan(
|
||||
rate_limit=rate_limit,
|
||||
timeout=timeout,
|
||||
templates_dir=templates_dir,
|
||||
ports=port_spec,
|
||||
top_ports=top_ports,
|
||||
)
|
||||
report.started_at = report.started_at or run_id.replace("scan-", "")
|
||||
report.finished_at = _utcnow()
|
||||
|
||||
@@ -261,6 +261,8 @@ def run_scan(
|
||||
timeout: float = 600.0,
|
||||
templates_dir: str | None = None,
|
||||
search_fn: SearchFn | None = None,
|
||||
ports: str | None = None,
|
||||
top_ports: int | None = 1000,
|
||||
) -> ReconReport:
|
||||
"""Run the full live pipeline. Scope is enforced before any active probing.
|
||||
|
||||
@@ -292,9 +294,12 @@ def run_scan(
|
||||
stages.append(StageResult(name=name, status=StageStatus.SKIPPED, detail="passive-only"))
|
||||
return _finalize(run_id, target, scope, table, stages, [], search_fn)
|
||||
|
||||
# Stage 3: naabu connect-scan (unprivileged)
|
||||
# Stage 3: naabu connect-scan (unprivileged). Port breadth widened beyond
|
||||
# naabu's top-100 default so high/uncommon-port services aren't missed.
|
||||
scan_targets = _scope_targets(table, scope)
|
||||
nb, nb_run = naabu.run(scan_targets, timeout=timeout, rate_limit=rate_limit)
|
||||
nb, nb_run = naabu.run(
|
||||
scan_targets, timeout=timeout, rate_limit=rate_limit, ports=ports, top_ports=top_ports
|
||||
)
|
||||
nb = [h for h in nb if _hit_in_scope(h, scope)]
|
||||
table.add_naabu(nb)
|
||||
stages.append(_stage("naabu", nb_run, count=len(nb)))
|
||||
@@ -308,8 +313,12 @@ def run_scan(
|
||||
table.add_nmap(nm)
|
||||
stages.append(_stage("nmap", nm_run, count=len(nm)))
|
||||
|
||||
# Stage 5: httpx probing
|
||||
hx, hx_run = httpx.run(_scope_targets(table, scope), timeout=timeout, rate_limit=rate_limit)
|
||||
# Stage 5: httpx probing. Feed it every discovered open port — otherwise
|
||||
# httpx only checks 80/443 and misses web apps on ports like 5000/8080.
|
||||
open_for_http = sorted({p.number for h in table.hosts() for p in h.ports})
|
||||
hx, hx_run = httpx.run(
|
||||
_scope_targets(table, scope), timeout=timeout, rate_limit=rate_limit, ports=open_for_http
|
||||
)
|
||||
table.add_httpx(hx)
|
||||
stages.append(_stage("httpx", hx_run, count=len(hx)))
|
||||
|
||||
|
||||
@@ -82,8 +82,18 @@ def _looks_like_ip(v) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def run(targets: list[str], *, timeout: float = 180.0, rate_limit: int | None = None) -> tuple[list[HttpProbe], object]:
|
||||
def run(
|
||||
targets: list[str],
|
||||
*,
|
||||
timeout: float = 180.0,
|
||||
rate_limit: int | None = None,
|
||||
ports: list[int] | None = None,
|
||||
) -> tuple[list[HttpProbe], object]:
|
||||
cmd = [BINARY, "-json", "-td", "-silent", "-title", "-tech-detect", "-web-server", "-status-code"]
|
||||
# Without -p, httpx only probes 80/443. Feed it the ports discovered by
|
||||
# naabu/nmap so web apps on non-standard ports (e.g. 5000, 8080) are probed.
|
||||
if ports:
|
||||
cmd += ["-p", ",".join(str(p) for p in sorted(set(ports)))]
|
||||
if rate_limit:
|
||||
cmd += ["-rate-limit", str(rate_limit)]
|
||||
res = run_tool(cmd, timeout=timeout, input_text="\n".join(targets) + "\n")
|
||||
|
||||
@@ -57,11 +57,18 @@ def run(
|
||||
timeout: float = 300.0,
|
||||
rate_limit: int | None = None,
|
||||
ports: str | None = None,
|
||||
top_ports: int | None = 1000,
|
||||
) -> tuple[list[PortHit], object]:
|
||||
# connect scan keeps the container unprivileged (no NET_RAW needed).
|
||||
cmd = [BINARY, "-json", "-scan-type", "connect", "-silent"]
|
||||
# naabu's own default is only the top-100 ports, which silently misses
|
||||
# services on higher/uncommon ports (a frequent THM/HTB/VulnHub case).
|
||||
# An explicit `ports` spec wins; otherwise widen to top-N (default 1000).
|
||||
# Pass ports="-" for a full 1-65535 sweep.
|
||||
if ports:
|
||||
cmd += ["-p", ports]
|
||||
elif top_ports:
|
||||
cmd += ["-top-ports", str(top_ports)]
|
||||
if rate_limit:
|
||||
cmd += ["-rate", str(rate_limit)]
|
||||
res = run_tool(cmd, timeout=timeout, input_text="\n".join(targets) + "\n")
|
||||
|
||||
@@ -102,8 +102,14 @@ def run(
|
||||
*,
|
||||
timeout: float = 600.0,
|
||||
unprivileged: bool = True,
|
||||
version_all: bool = True,
|
||||
) -> tuple[list[NmapHost], object]:
|
||||
cmd = [BINARY, "-sV", "-oX", "-", "-Pn"]
|
||||
if version_all:
|
||||
# Max version-detection intensity. Default (7) often yields only a
|
||||
# low-confidence service name with null product/version on services
|
||||
# that don't volunteer a banner; intensity 9 probes harder.
|
||||
cmd.append("--version-all")
|
||||
if unprivileged:
|
||||
cmd.append("-sT") # TCP connect scan; no raw sockets required
|
||||
if ports:
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
"""The recon stages must scan wider than the tools' narrow built-in defaults.
|
||||
|
||||
These guard the regressions behind the 'single open port / no banner' report:
|
||||
- naabu must not silently fall back to its top-100 default.
|
||||
- httpx must be told the discovered open ports (else it only probes 80/443).
|
||||
- nmap must probe version detection at full intensity.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from recon_triage.tools import httpx, naabu, nmap
|
||||
from recon_triage.tools.base import ToolRun
|
||||
|
||||
|
||||
def _capture(monkeypatch, module):
|
||||
"""Patch a tool module's run_tool to record the command and return empty output."""
|
||||
seen: dict = {}
|
||||
|
||||
def fake_run_tool(cmd, *, timeout=120.0, input_text=None):
|
||||
seen["cmd"] = cmd
|
||||
return ToolRun(cmd=cmd, returncode=0, stdout="", stderr="")
|
||||
|
||||
monkeypatch.setattr(module, "run_tool", fake_run_tool)
|
||||
return seen
|
||||
|
||||
|
||||
def test_naabu_widens_beyond_top_100_by_default(monkeypatch):
|
||||
seen = _capture(monkeypatch, naabu)
|
||||
naabu.run(["10.0.0.1"])
|
||||
cmd = seen["cmd"]
|
||||
# Default must explicitly request top-1000, not rely on naabu's top-100 default.
|
||||
assert "-top-ports" in cmd
|
||||
assert cmd[cmd.index("-top-ports") + 1] == "1000"
|
||||
|
||||
|
||||
def test_naabu_explicit_ports_override_top_ports(monkeypatch):
|
||||
seen = _capture(monkeypatch, naabu)
|
||||
naabu.run(["10.0.0.1"], ports="-") # full sweep
|
||||
cmd = seen["cmd"]
|
||||
assert "-p" in cmd and cmd[cmd.index("-p") + 1] == "-"
|
||||
assert "-top-ports" not in cmd # explicit ports win
|
||||
|
||||
|
||||
def test_httpx_probes_discovered_ports(monkeypatch):
|
||||
seen = _capture(monkeypatch, httpx)
|
||||
httpx.run(["10.0.0.1"], ports=[5000, 8080])
|
||||
cmd = seen["cmd"]
|
||||
assert "-p" in cmd
|
||||
assert cmd[cmd.index("-p") + 1] == "5000,8080"
|
||||
|
||||
|
||||
def test_httpx_without_ports_omits_flag(monkeypatch):
|
||||
seen = _capture(monkeypatch, httpx)
|
||||
httpx.run(["10.0.0.1"])
|
||||
assert "-p" not in seen["cmd"]
|
||||
|
||||
|
||||
def test_nmap_uses_full_version_intensity(monkeypatch):
|
||||
seen = _capture(monkeypatch, nmap)
|
||||
nmap.run(["10.0.0.1"], ports=[5000])
|
||||
cmd = seen["cmd"]
|
||||
assert "-sV" in cmd
|
||||
assert "--version-all" in cmd
|
||||
Reference in New Issue
Block a user