Volver a la wiki

Decisión: sa4-G2 cerrada — el validador de comandos vuelve a aceptar '| include' con regex anclada

{ “tags”: [“monitoring”,“network”,“security”,“command-injection”,“ssh”,“scrapli”,“django”,“audit-suprema”,“correctness”,“backup”], “sources”: [ {“type”:“commit”,“ref”:“d00f04d0267711eb5dfdbe780b52e5d1336283ff”,“last_seen”:“2026-08-29”}, {“type”:“commit”,“ref”:“9f1150c5b2efb542c175c75f63e193ef6e455f1b”,“last_seen”:“2026-08-31”}, {“type”:“code”,“ref”:“monitoring/services/command_validation.py”,“last_seen”:“2026-08-29”}, {“type”:“code”,“ref”:“network/api/backups.py”,“last_seen”:“2026-08-31”}, {“type”:“code”,“ref”:“tests/api/test_command_validation_smuggling.py”,“last_seen”:“2026-08-29”}, {“type”:“code”,“ref”:“tests/network/test_backup_command_validation.py”,“last_seen”:“2026-08-31”}, {“type”:“doc”,“ref”:“AUDIT.md”,“last_seen”:“2026-08-29”}, {“type”:“doc”,“ref”:“CHANGELOG.md”,“last_seen”:“2026-08-31”}, {“type”:“doc”,“ref”:“RELEASE_NOTES.md”,“last_seen”:“2026-08-31”} ], “related”: [“feature—network—scripts-ssh-audit-s113”,“decision—20260607—audit-suprema-sa4-baja-cleanup”,“entity—network—service—scrapli-manager”,“entity—network—endpoint—execute-script”,“concept—saas—multi-tenancy”,“feature—network—auditoria-sa4-devices-backups-groups”], “content”: ”## Contexto\n\nEn junio (Auditoría Suprema, sub-área Scripts/SSH — [[feature—network—scripts-ssh-audit-s113]]) se cerró el hallazgo A2/A5 prohibiendo TODO separador en un comando antes de mandarlo a un equipo de red por SSH (;, &, |, saltos de línea, NUL), porque un comando “seguro” tipo show ... podía llevar oculto un segundo comando destructivo detrás del separador. La prohibición total del pipe se llevó por delante el uso legítimo más común de un show: el filtro de salida (show running-config | include interface). El hallazgo quedó registrado en AUDIT.md como deuda diferida sa4-G2, con una condición explícita para tocarlo: no se admite | de nuevo sin una batería de tests que demuestre que la vía de contrabando original (sa4-A5) sigue cerrada.\n\n## Opciones consideradas\n\n1. Mantener el bloqueo total del pipe (status quo). Seguro, pero rompe un flujo de trabajo habitual de red: mirar la configuración de un equipo grande sin ahogarse en miles de líneas.\n2. Permitir | de forma genérica con una lista negra de programas peligrosos (sh, bash, python…). Frágil: cualquier programa no listado, o un alias, se cuela.\n3. Anclar una única forma de comando filtrado, de punta a punta, y verificarla con una batería de tests de contrabando dedicada — opción elegida.\n\n## Decisión elegida\n\nFILTERED_SHOW_RE: una regex anclada al principio y al final de la cadena que solo reconoce show|sh|display <base> | include|exclude|begin|section <patrón>. Ni la base ni el patrón admiten |, así que en toda la cadena solo puede existir uno; el filtro solo puede ser uno de esos cuatro verbos (nunca redirect, tee, save, request ni un nombre de programa); el patrón no admite paréntesis (ver Consecuencias). Antes de esa comprobación, el validador exige ahora ASCII imprimible en todo el comando —mata de una vez todo carácter de control y todo homoglifo unicode de un separador— y rechaza una lista explícita de metacaracteres de shell (;, &, $(, ${, backtick, >, <). Además, sh - y show - pasan a la lista de bloqueados: sh es la abreviatura Cisco de show, pero también el shell POSIX en un equipo Linux, y ninguna abreviatura real de show lleva nunca un guion.\n\nLa condición que AUDIT.md exigía para tocar el validador ya está cumplida: tests/api/test_command_validation_smuggling.py, 106 tests — 67 intentos de contrabando que deben seguir rechazados (encadenado, sustitución, redirecciones, pipe a programa, homoglifos unicode, payloads largos) y 31 comandos legítimos que deben pasar, más 8 tests que fijan las decisiones de diseño.\n\n## Cuatro agujeros preexistentes destapados por la batería\n\nEscribir los tests de contrabando, antes de tocar el validador, destapó que el bloqueo de junio no cerraba todo lo que decía cerrar:\n\n1. Sustitución de comandos: show ver $(reload) y show ver `reload` pasaban.\n2. Redirecciones: show run > /tmp/x, >> flash:x, < /tmp/x — escritura/exfiltración de configuración.\n3. Homoglifos unicode: pipe fullwidth (U+FF5C), punto y coma fullwidth (U+FF1B), signo de interrogación griego (U+037E) y punto y coma árabe (U+061B) esquivaban una lista de separadores que solo miraba ASCII.\n4. sh -c reload: sh como prefijo seguro (abreviatura de show) es también el shell POSIX en equipos Linux del driver generic.\n\nLos cuatro quedan cerrados en el mismo commit, con test dedicado cada uno.\n\n## Consecuencias\n\n- Recupera el filtro de salida legítimo (| include/exclude/begin/section) sin reabrir sa4-A5.\n- Límite honesto y deliberado: un patrón con alternativas entre paréntesis (| include (error|down)) se rechaza, aunque sea válido en un IOS real — admitirlo exige distinguir un pipe “de dentro de un grupo” de un pipe “separador”, la misma ambigüedad que reabriría el contrabando. El rodeo son dos comandos filtrados, uno por término.\n- Efecto secundario asumido: una descripción de interfaz con tilde o ñ se rechaza por la comprobación ASCII-only; el mensaje de error lo dice.\n- Deuda que queda, documentada aparte: copy running-config startup-config sigue bloqueado pese a estar en el whitelist de cisco_ios (letra muerta) — gana el bloqueo, que es el lado seguro; resolver la contradicción es una decisión de producto pendiente sin dueño.\n- Afecta a seis call-sites de validate_commands: monitoring/api/insight_execution.py, monitoring/api/itsm_runbooks.py, monitoring/services/insight_lifecycle.py, monitoring/services/ai_providers/diagnosis_validator.py, network/api/scripts.py y, desde v1.93.0, network/api/backups.py::trigger_backup.\n\n## Extensión — v1.93.0 (trigger_backup, #479, 2026-08-31)\n\nLa Auditoría Suprema 2 (Tanda 4) encontró un sexto punto donde un comando de cliente llegaba a la sesión SSH del equipo (privilegio enable) sin pasar por este validador: trigger_backup (network/api/backups.py) despachaba el config_command del payload —el comando que el editor de backups (editor/backups.js) deja elegir al usuario— VERBATIM, sin whitelist. Un admin de red podía inyectar sh -c reboot o encadenar comandos.\n\nFix: antes de abrir la sesión, trigger_backup llama a validate_commands([config_command], vendor) — el mismo validador reforzado arriba. show running-config (y las variantes del whitelist por vendor) siguen pasando; una inyección devuelve 400 {message: \"Invalid config command: ...\"} en vez de ejecutarse. 3 tests nuevos en tests/network/test_backup_command_validation.py.\n\nLa auditoría reportó un segundo hallazgo sobre esta misma superficie (sh - aceptado como prefijo seguro) que ya estaba resuelto desde esta misma decisión (v1.87.3): BLOCKED_COMMANDS ya incluye \"sh -\" y \"show -\". Verificado en código vivo — sin acción, la auditoría trabajaba sobre una foto anterior al commit d00f04d0.\n\n## Status\n\naccepted — v1.87.3 (commit d00f04d0). Extendido a network/api/backups.py::trigger_backup en v1.93.0 (commit 9f1150c5).\n\n## Véase también\n\n- [[feature—network—scripts-ssh-audit-s113]] — el hallazgo A2/A5 original que motivó el bloqueo total del pipe\n- [[decision—20260607—audit-suprema-sa4-baja-cleanup]] — limpieza BAJA de la misma sub-área\n- [[entity—network—service—scrapli-manager]] — el servicio que abre la sesión SSH donde se ejecuta el comando validado\n- [[entity—network—endpoint—execute-script]] — otro call-site de validate_commands\n- [[concept—saas—multi-tenancy]]\n- [[feature—network—auditoria-sa4-devices-backups-groups]] — documenta trigger_backup y el resto de endpoints de network/api/backups.py\n” }

Subir