Files

51 lines
3.5 KiB
Python

from __future__ import annotations
import json
import re
import time
from urllib.parse import urljoin
from redflare.core.models import Finding, ModuleResult, Target
from .base import Module, ModuleContext
from .http import request
PATHS = ("/", "/login", "/admin", "/dashboard", "/panel", "/console", "/manage", "/manager",
"/status", "/api/status", "/health", "/metrics", "/config", "/config.json", "/api/config",
"/graphql", "/swagger-ui.html", "/openapi.json", "/actuator/env", "/debug/vars", "/.env")
SENSITIVE = re.compile(r"(?:config|secret|credential|token|\.env|actuator|debug|metrics|console|admin|dashboard)", re.I)
AUTH = re.compile(r"(?:type=[\"']?password|/(?:login|signin|auth)(?:/|\?|$)|sign\s*in|log\s*in)", re.I)
class NativeNoAuthModule(Module):
name = "authorization_surface"
description = "Natively triage bounded in-scope web surfaces for missing authentication barriers"
def run(self, target: Target, context: ModuleContext) -> ModuleResult:
started = time.monotonic(); result = ModuleResult(self.name, target.url); hits = []
for path in PATHS:
url = urljoin(target.url.rstrip("/") + "/", path.lstrip("/"))
try:
response = request(url, context.timeout, max_body=200_000, allowed_origin=(target.host, target.port))
except Exception:
continue
body = response.body.decode("utf-8", errors="replace")
authenticated = response.status in {401, 403} or "www-authenticate" in response.headers or bool(AUTH.search(body))
context.surface_graph.add_endpoint(target.url, response.url, method="GET", source=self.name,
content_type=response.headers.get("content-type"), status=response.status,
authentication="required" if authenticated else "not-observed")
if response.status == 200 and not authenticated and (path != "/" or SENSITIVE.search(body)) and SENSITIVE.search(path + " " + body[:5000]):
evidence = {"url": response.url, "path": path, "status": response.status,
"content_type": response.headers.get("content-type", ""), "authentication_barrier": "not observed"}
hits.append(evidence)
result.findings.append(Finding(context.run_id, target.url, self.name, "unauthenticated-service",
f"Potential unauthenticated surface at {path}", "high" if SENSITIVE.search(path) else "medium", .85,
"A bounded GET request reached a potentially sensitive interface without an observed authentication barrier.",
evidence, ["authorization", "native", "noauth"],
remediation="Require server-side authentication and authorization before returning sensitive interface content."))
context.emit(target.url, self.name, "finding", f"Potential unauthenticated surface: {response.url}")
if context.rate > 0: time.sleep(min(1.0 / context.rate, 1.0))
result.observations = {"engine": "native-redflare", "paths_checked": len(PATHS), "candidate_surfaces": len(hits)}
directory = context.artifact_dir / self.name / target.host; directory.mkdir(parents=True, exist_ok=True)
artifact = directory / "findings.json"; artifact.write_text(json.dumps({"target": target.url, "findings": hits}, indent=2), encoding="utf-8")
result.artifacts.append(str(artifact)); result.duration_seconds = round(time.monotonic() - started, 4); return result