Auditoría Suprema Etapa 3 network sub-área 3: Vendor/MIB/OUI (s114)
Descripción
Tercera sub-área de network (sesión 114, 2026-06-08): el catálogo de fabricantes y MIBs (las “fichas técnicas” que usa el sistema para reconocer dispositivos SNMP/HTTP) y los endpoints de perfiles auto-descubiertos. Auditoría identificó 25 hallazgos confirmados (8 ALTA, 5 MEDIA, 12 BAJA) en:
network/api/vendor.py(endpoints MIB y asistente IA)network/api/profiles.py(endpoints de perfil de dispositivo)network/services/mib_manager.py(compilación, extracción OIDs)network/services/mib_assistant.py(análisis con Gemma 4)network/services/vendor_registry.py(detección de vendor)- Comandos IEEE/IANA import (descarga de catálogos)
Esta PR merge todos los hallazgos viables en una tanda (Regla 22), diferiendo a PRs propias:
- Rate-limit en upload/IA
upload_mib→ async con Huey- HttpReader remoto (toca frontend/infra)
- Trocear
profiles.py/mib_manager.py(>500 LOC cada uno) - Decisión de diseño: campo
organizationdeCustomMib(catálogo global vs tenant-scoped)
Hallazgos resueltos
Raíz A: Autorización ausente en endpoints MIB (6 ALTA)
Problema: upload_mib, delete_custom_mib, apply_custom_oids, mib_assistant_analyze, mib_assistant_apply no exigían rol admin. El catálogo de MIBs/OIDs es GLOBAL y compartido por todas las orgs del SaaS. Un usuario normal de cualquier org podía:
- Subir/borrar MIBs (mutar catálogo compartido)
- Escribir/compilar ficheros en disco del servidor
- Gastar IA de pago en análisis
Solución: Los 5 endpoints anteponen if not is_admin(request.user): return 403 + 403 declarado en el response del decorador.
Raíz B: Path traversal / arbitrary file write en upload_mib (ALTA)
Problema: module_name (nombre interno del MIB) se extraía del contenido del fichero con regex \S+ sin validación. Un MIB malicioso podía incluir ../ en el nombre → escritura fuera de custom_dir (potencial RCE).
Solución:
- Validación strict:
module_namedebe coincidir con^[A-Za-z0-9._-]+$ - Verificación
realpath: el destino no escapa del directorio permitido
Raíz C: Broken Access Control en profiles.py → require_perm (ALTA)
Problema: Ningún endpoint del router profiles gateaba por rol (solo get_current_org). Un usuario readonly podía:
- Lanzar escaneos SNMP-walk, SSH activos (
refresh-interfaces,read-port-config) - Mutar DeviceProfile/Device.model_data (
update,delete,configure_observatory,propagate_ports)
Solución: Cada endpoint exige:
require_perm(request, "network", "edit")para mutaciones y activosrequire_perm(request, "network", "view")para GET
Patrón coherente con discovery.py/scripts.py.
ReDoS en regex editables del vendor (MEDIA)
Problema: Las expresiones de detección de marca (sys_descr_patterns, sys_descr_model_regex, sys_descr_version_regex) son campos editables del VendorProfile. Un patrón con backtracking catastrófico + un sysDescr largo colgaba el worker.
Solución: Capar sysDescr a 2048 caracteres antes de aplicar patrones en:
detect_vendor_from_sys_descrget_model_from_sys_descrget_version_from_sys_descr
Paridad con el body[:2048] HTTP.
SSRF defense-in-depth en refresh-interfaces/read-port-config (MEDIA)
La IP del profile se valida con check_scan_ip (rechaza loopback/metadata) antes de abrir SNMP/SSH.
Prompt injection OID→LLM (MEDIA)
Problema: Los nombres/sintaxis de OIDs subidos se enviaban al LLM sin sanitización.
Solución: Wrapping con wrap_untrusted() (reutiliza sanitize.py de CNS) + redacción de secretos antes de llegar a Gemma 4.
Code-exec latente documentado (MEDIA)
get_mib_dir() deja constancia: los .py compilados por pysmi derivan de input no confiable y NUNCA deben importarse (hoy solo se leen como texto).
Cambios de robustez / pulido
- Errores genéricos al cliente:
mib_assistant_*yprofiles.pydejan de devolverstr(e), detalles → log - Borrado de MIB por nombre exacto:
delete_custom_mibborra por set exacto de extensiones, nostartswith - Validación de formato OID: Regex numérico antes de persistir
- Mass-assignment acotado:
create_vendor_profilefiltra por lista blanca (constanteVENDOR_UPDATABLE_FIELDS) - Cap de descarga IEEE/IANA: 64 MB (evita agotar RAM ante upstream comprometido)
- Docstrings honestos: “Gemini Flash” → “Gemma 4 (google_genai)”
- Dead code:
ParseSshVlanRequesteliminado apply_proposalrobusto: omite entries sinoiden vez deKeyError
Tests
Nuevos tests en tests/api/test_network_sa3.py (12 tests):
- Gates de autorización de los 5 endpoints MIB (viewer/operator 403, admin pasa)
- Path traversal rechazado (400)
require_permenprofiles.py(readonly 403 en mutaciones, view pasa GET)
CI improvements
gitleaksdownload resilience: descargar a fichero antes de pipe-tar (evita “stdin: not in gzip format” por body truncado)- Reintentos + extracción desde fichero (
--fail --retry 5 --retry-delay 3 --retry-all-errors --retry-max-time 90)
Véase también
- [[entity—network—model—vendor-profile]]
- [[entity—network—model—device-profile]]
- [[entity—network—service—mib-manager]]
- [[entity—network—service—mib-assistant]]
- [[entity—network—service—vendor-registry]]
- [[concept—saas—require-perm-network]]