65 lines
2.4 KiB
Python
65 lines
2.4 KiB
Python
from __future__ import annotations
|
|
|
|
import time
|
|
|
|
from redflare.core.models import Finding, ModuleResult, Target
|
|
from .base import Module, ModuleContext
|
|
from .http import request
|
|
|
|
|
|
SECURITY_HEADERS = {
|
|
"strict-transport-security": "HSTS",
|
|
"content-security-policy": "Content Security Policy",
|
|
"x-content-type-options": "MIME sniffing protection",
|
|
"referrer-policy": "Referrer Policy",
|
|
"permissions-policy": "Permissions Policy",
|
|
}
|
|
|
|
|
|
class HeaderModule(Module):
|
|
name = "http_headers"
|
|
description = "Inspect HTTP status, redirects, server metadata, and security headers"
|
|
|
|
def run(self, target: Target, context: ModuleContext) -> ModuleResult:
|
|
started = time.monotonic()
|
|
result = ModuleResult(self.name, target.url)
|
|
try:
|
|
context.emit(target.url, self.name, "progress", f"Requesting {target.url}")
|
|
response = request(target.url, context.timeout, method="GET", max_body=200_000)
|
|
missing = [header for header in SECURITY_HEADERS if header not in response.headers]
|
|
result.observations.update(
|
|
{
|
|
"status": response.status,
|
|
"final_url": response.url,
|
|
"headers": response.headers,
|
|
"body_bytes_sampled": len(response.body),
|
|
"missing_security_headers": missing,
|
|
}
|
|
)
|
|
context.emit(
|
|
target.url,
|
|
self.name,
|
|
"info",
|
|
f"HTTP {response.status}; final={response.url}; sampled={len(response.body)} bytes",
|
|
)
|
|
if missing:
|
|
result.findings.append(
|
|
Finding(
|
|
context.run_id,
|
|
target.url,
|
|
self.name,
|
|
"security-headers",
|
|
"Common security headers are missing",
|
|
"low",
|
|
0.95,
|
|
"The response omitted: " + ", ".join(missing),
|
|
{"status": response.status, "missing": missing},
|
|
["headers", "hardening"],
|
|
)
|
|
)
|
|
except Exception as exc:
|
|
result.status = "error"
|
|
result.errors.append(f"{type(exc).__name__}: {exc}")
|
|
result.duration_seconds = round(time.monotonic() - started, 4)
|
|
return result
|