mirror of
https://github.com/yogeshojha/rengine.git
synced 2026-10-20 07:11:28 +02:00
urllib.parse.SplitResult.port raises ValueError when the authority carries a non-numeric port; nine call sites read it unguarded, so one malformed URL (a stray quote, a client typo) turned into an unhandled exception in the middle of a scan, a report, an interest rule or a proxy dial. shared/utils/net.py:url_port makes the read itself the failure boundary; unreadable_port names the bogus text so the naabu and banner proxy paths can report what they refused rather than retrying against a host string that dropped its port silently. shared/definitions/ports.py:SCHEME_PORTS replaces four private copies of the same http/https default-port map (endpoints.py, endpoint_judge, scan_surface/normalize, nuclei's parser, dalfox's parser). Also renames live_hosts to answered_hosts in the report source and the interest context, and lifts the asset query's inline is:web predicate into predicates.answered() — the existing distinction between is:live (2xx) and is:web (answered at all) was already the rule; live_hosts was the one name still claiming the stronger one. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VckaDx6mBB4TsG76u61gvv
66 lines
2.2 KiB
Python
66 lines
2.2 KiB
Python
from __future__ import annotations
|
|
|
|
from reports.base import RenderContext, Section
|
|
from reports.config import SectionConfig, choice, columns, flag, limit
|
|
from shared.definitions.reports import SectionGroup
|
|
from shared.definitions.surface import SurfaceDimension
|
|
|
|
_COLUMNS = {
|
|
"status": "HTTP status",
|
|
"title": "Page title",
|
|
"ip": "Address",
|
|
"tech": "Technology",
|
|
"server": "Server",
|
|
"cdn": "CDN or WAF",
|
|
"asn": "Network",
|
|
"tls": "Certificate expiry",
|
|
"findings": "Findings",
|
|
}
|
|
|
|
|
|
class WebAssetsConfig(SectionConfig):
|
|
only_live: bool = flag(True, title="Only web assets that answered")
|
|
max_rows: int = limit(80, title="Rows shown", minimum=5, maximum=2000)
|
|
fields: list[str] = columns(
|
|
["status", "title", "ip", "tech"], title="Columns", options=_COLUMNS
|
|
)
|
|
order: str = choice(
|
|
"findings",
|
|
title="Order",
|
|
options={"findings": "Findings first", "status": "Status", "name": "Name"},
|
|
)
|
|
show_new: bool = flag(True, title="Mark new web assets")
|
|
|
|
|
|
class WebAssetsSection(Section):
|
|
name = "web_assets"
|
|
title = "Web assets"
|
|
description = "Web asset inventory."
|
|
group = SectionGroup.SURFACE.value
|
|
order = 10
|
|
launch_fields = frozenset({"max_rows"})
|
|
requires = frozenset({SurfaceDimension.WEB_ASSETS.value})
|
|
config_model = WebAssetsConfig
|
|
|
|
def build(self, ctx: RenderContext, cfg: WebAssetsConfig) -> dict | None:
|
|
rows = ctx.data.answered_hosts if cfg.only_live else ctx.data.host_rows
|
|
if not rows:
|
|
return None
|
|
if cfg.order == "findings":
|
|
rows = sorted(rows, key=lambda h: (-h.findings, h.name))
|
|
elif cfg.order == "status":
|
|
rows = sorted(rows, key=lambda h: (h.status or 999, h.name))
|
|
else:
|
|
rows = sorted(rows, key=lambda h: h.name)
|
|
total = len(rows)
|
|
return {
|
|
"rows": rows[: cfg.max_rows],
|
|
"total": total,
|
|
"hidden": max(0, total - cfg.max_rows),
|
|
"fields": [f for f in cfg.fields if f in _COLUMNS],
|
|
"labels": _COLUMNS,
|
|
"show_new": cfg.show_new,
|
|
"only_live": cfg.only_live,
|
|
"all_hosts": ctx.data.count_of(SurfaceDimension.WEB_ASSETS.value),
|
|
}
|