mirror of
https://github.com/yogeshojha/rengine.git
synced 2026-10-20 15:21:30 +02:00
Three tools took the scan's network config and dropped part of it. The banner prober spoke SOCKS5 to whatever proxy it was given. A scan context holding an http:// proxy made every handshake fail, and the stage reported "no banner returned by N ports" rather than a problem. It now speaks SOCKS5 or HTTP CONNECT by scheme (tools/banner/proxy.py, tested against a stub proxy), and refuses a scheme it cannot carry before any port is touched instead of dialling direct. naabu was handed the resolved URL whole. Its -proxy is socks5 only and takes no inline credentials, so http:// or any password quietly did not apply. proxy_args() renders -proxy host:port with -proxy-auth beside it, and names the reason when the proxy cannot be expressed, which port_scan and host_discovery report as partial. -passive is an HTTPS call to Shodan, so it goes through proxy_env instead, which passive_ports was not passing at all. wafw00f got the proxy but never the headers, so an authenticated surface was fingerprinted unauthenticated. It takes a file rather than a value, so the run writes one 0600 temp file and removes it after. proxy_env moves to shared/services/proxy_resolve.py now that naabu wants it too, and host_port() lands in shared/utils/net.py for the bracketed IPv6 authority CONNECT needs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X5roHYDYzSPRNaTkn8hSkJ
66 lines
2.0 KiB
Python
66 lines
2.0 KiB
Python
import ipaddress
|
|
import socket
|
|
from collections.abc import Iterable
|
|
from urllib.parse import urlsplit
|
|
|
|
|
|
def is_registry_routable(value: str) -> bool:
|
|
"""Whether an address or netblock sits in public space an RIR can hold a record for."""
|
|
try:
|
|
net = ipaddress.ip_network(value.strip(), strict=False)
|
|
except ValueError:
|
|
return False
|
|
return net.is_global and not net.is_multicast
|
|
|
|
|
|
def validate_public_https_url(raw: str, *, label: str = "URL") -> None:
|
|
parts = urlsplit(raw)
|
|
if parts.scheme != "https" or not parts.hostname:
|
|
msg = f"{label} must be an https:// URL."
|
|
raise ValueError(msg)
|
|
try:
|
|
infos = socket.getaddrinfo(parts.hostname, None)
|
|
except OSError as exc:
|
|
msg = f"Cannot resolve {label} host: {exc}"
|
|
raise ValueError(msg) from exc
|
|
for info in infos:
|
|
ip = ipaddress.ip_address(info[4][0])
|
|
if (
|
|
ip.is_private
|
|
or ip.is_loopback
|
|
or ip.is_link_local
|
|
or ip.is_reserved
|
|
or ip.is_multicast
|
|
or ip.is_unspecified
|
|
):
|
|
msg = f"{label} resolves to a disallowed address."
|
|
raise ValueError(msg)
|
|
|
|
|
|
def cert_covers(
|
|
host: str | None, subject_cn: str | None, sans: Iterable[str] = ()
|
|
) -> bool:
|
|
"""Whether a certificate names the host presenting it."""
|
|
if not host:
|
|
return False
|
|
target = host.strip().lower().rstrip(".")
|
|
for raw in (subject_cn, *sans):
|
|
if not raw:
|
|
continue
|
|
name = str(raw).strip().lower().rstrip(".")
|
|
if name == target:
|
|
return True
|
|
if (
|
|
name.startswith("*.")
|
|
and target.endswith(name[1:])
|
|
and target.count(".") == name.count(".")
|
|
):
|
|
return True
|
|
return False
|
|
|
|
|
|
def host_port(host: str, port: int | str) -> str:
|
|
"""An authority a tool can parse, with an IPv6 literal in brackets."""
|
|
literal = f"[{host}]" if ":" in host and not host.startswith("[") else host
|
|
return f"{literal}:{port}"
|