fix(security): TrustedHostMiddleware (anti DNS-rebinding) + escape iCal en _build_vcalendar
- TrustedHostMiddleware (allowed_hosts 127.0.0.1/localhost/testserver): cierra el vector por el que una web maliciosa rebindea su dominio a 127.0.0.1 y alcanza /api/query desde el navegador del usuario (el service no tiene auth por ser local). - _build_vcalendar escapaba nada: UID/SUMMARY/LOCATION/RRULE crudos permitían iCal injection. Ahora _ical_escape (summary/location) + _ical_sanitize (uid/rrule, quita saltos de línea sin tocar los separadores legítimos de la regla). Auditoría de seguridad: el fallo CRÍTICO (LFI/escritura via /api/query) se cierra con el sandbox de duckdb_query_readonly en el registry; este commit cubre los hallazgos ALTA (DNS-rebinding) y MEDIA (iCal injection).
This commit is contained in:
+30
-6
@@ -396,6 +396,30 @@ def _safe_resource(uid: str) -> str:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _ical_escape(value) -> str:
|
||||
"""Escapa un valor de texto para una propiedad iCalendar (RFC 5545).
|
||||
|
||||
Evita inyección de propiedades/componentes: un summary/location con saltos de
|
||||
línea o `;`/`,` no puede cerrar el VEVENT ni abrir otro. El `\\r` se elimina
|
||||
(el folding lo aporta el `\\r\\n` de la serialización).
|
||||
"""
|
||||
return (
|
||||
str(value)
|
||||
.replace("\\", "\\\\")
|
||||
.replace("\r", "")
|
||||
.replace("\n", "\\n")
|
||||
.replace(",", "\\,")
|
||||
.replace(";", "\\;")
|
||||
)
|
||||
|
||||
|
||||
def _ical_sanitize(value) -> str:
|
||||
"""Quita saltos de línea de un valor estructurado (UID, RRULE) para evitar
|
||||
que se inyecten propiedades nuevas. No escapa `;`/`,` porque son separadores
|
||||
legítimos en RRULE."""
|
||||
return str(value).replace("\r", "").replace("\n", "")
|
||||
|
||||
|
||||
def _build_vcalendar(uid: str, fields: dict) -> str:
|
||||
"""Compone un VCALENDAR mínimo con un VEVENT desde los campos del evento."""
|
||||
dtstart = (fields.get("dtstart") or "").replace("-", "").replace(":", "")
|
||||
@@ -405,17 +429,17 @@ def _build_vcalendar(uid: str, fields: dict) -> str:
|
||||
"VERSION:2.0",
|
||||
"PRODID:-//osint_db//events//EN",
|
||||
"BEGIN:VEVENT",
|
||||
f"UID:{uid}",
|
||||
f"SUMMARY:{fields.get('summary') or ''}",
|
||||
f"UID:{_ical_sanitize(uid)}",
|
||||
f"SUMMARY:{_ical_escape(fields.get('summary') or '')}",
|
||||
]
|
||||
if dtstart:
|
||||
lines.append(f"DTSTART:{dtstart}")
|
||||
lines.append(f"DTSTART:{_ical_sanitize(dtstart)}")
|
||||
if dtend:
|
||||
lines.append(f"DTEND:{dtend}")
|
||||
lines.append(f"DTEND:{_ical_sanitize(dtend)}")
|
||||
if fields.get("location"):
|
||||
lines.append(f"LOCATION:{fields['location']}")
|
||||
lines.append(f"LOCATION:{_ical_escape(fields['location'])}")
|
||||
if fields.get("rrule"):
|
||||
lines.append(f"RRULE:{fields['rrule']}")
|
||||
lines.append(f"RRULE:{_ical_sanitize(fields['rrule'])}")
|
||||
lines += ["END:VEVENT", "END:VCALENDAR"]
|
||||
return "\r\n".join(lines) + "\r\n"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user