La autenticación del Catalyst SD-WAN Manager no se rompió. Funcionaba. Lo que se rompió fue la lista que decide qué peticiones tienen que pasar por ella. La lista decía /j_security_check; la petición decía /%6a_security_check, que es la misma ruta con la jota escrita en hexadecimal. Para la lista son dos cadenas distintas. Para el servidor que hay detrás, el mismo recurso. En ese hueco de un carácter cabe un administrador.
Operamos SD-WAN multisede y red propia con BGP y túneles WireGuard, así que un aviso sobre un orquestador nos toca de cerca y lo leímos ayer por obligación profesional. Pero este post no va de Cisco, y sirve igual aunque no tengas un solo equipo suyo. Va de una forma de romper que no pertenece a ningún producto: existe en cualquier sitio donde la capa que decide si hace falta autenticarse y la capa que resuelve el recurso leen la misma petición de manera distinta. Nosotros también tenemos reglas escritas así.
Lo que se publicó ayer, y en qué orden pasó
El aviso cisco-sa-sdwan-webauth-xr8beuuU salió el 30 de septiembre de 2026 a las 13:00 GMT. CVE-2026-76504, CVSS base 9,8, vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H. Leído en castellano: por red, complejidad baja, sin credenciales previas, sin que nadie tenga que pinchar en nada, y con impacto alto en las tres patas. La clasificación es CWE-177, tratamiento indebido de la codificación de la URL.
El orden de los hechos importa más que la puntuación. Cisco escribe que su PSIRT tuvo conocimiento de explotación activa en septiembre de 2026. Es decir: primero se explotó, después se publicó. Y CISA lo añadió a su catálogo de vulnerabilidades explotadas el mismo 30 de septiembre, con fecha límite de corrección el 3 de octubre. Tres días. Y ese plazo no sale de la nota del CVSS: sale del árbol de decisión de la directiva BOD 26-04, que cruza cuatro variables —si el activo está expuesto a internet, si el CVE está en el catálogo, si la explotación es automatizable y si da control total o parcial—. Este caso marca las cuatro casillas malas, y esa combinación es la única que lleva aparejado tres días y triaje forense, no sólo tres días.
| Rama del Manager | Primera versión corregida |
|---|---|
| Anterior a 20.9 | No hay parche: toca migrar a una rama con corrección |
| 20.9 | 20.9.10.1 |
| 20.12 | 20.12.8.2 |
| 20.15 | 20.15.6.1 |
| 20.18 | 20.18.4.1 |
| 26.1 | 26.1.2.1 |
| 26.2 | 26.2.1 |
Dos detalles de esa tabla que conviene no pasar por encima. El primero: el fallo afecta al producto independientemente de la configuración, así que no hay un «nosotros no usamos esa parte». El segundo: Cisco dice, con todas las letras, que no hay ningún workaround que lo resuelva. Lo único que hay es el parche de tu rama — y ojo con la columna de la izquierda, porque la rama manda sobre la antigüedad: estar en la última 20.12 publicada antes de ayer no te deja en terreno seguro si esa última no es la 20.12.8.2.
De dónde sale j_security_check, y por qué esa ruta tenía que estar exenta
Ese nombre tan raro no se lo inventó Cisco. j_security_check es la acción que la especificación de Jakarta Servlet —la antigua Java EE— reserva desde hace más de veinte años para el formulario de inicio de sesión: el formulario tiene que hacer POST contra una URL que termine en /j_security_check, con los campos llamados j_username y j_password, y es el contenedor —no la aplicación— quien recoge eso y decide. Cualquier aplicación Java con autenticación por formulario tiene esa ruta. No es una puerta de servicio ni un resto de depuración: es la puerta principal, y está donde la norma dice que esté.
Esa ruta tiene que estar exenta de la comprobación de sesión. Es el sitio donde se consigue la sesión: si exigieras una sesión válida para llegar al formulario de login, nadie podría iniciar sesión nunca. La excepción no sobra, no es una dejadez, no es deuda técnica. Es obligatoria. Todo sistema con autenticación tiene al menos una ruta que se evalúa sin autenticar, y esa ruta es, por construcción, la más expuesta que tiene.
El fallo, por tanto, no está en tener la excepción. Está un escalón más abajo: en cómo se comprueba si una petición concreta cae dentro de ella. Y ahí se comparó texto.
Dos lecturas de la misma petición
%6a es la jota minúscula escrita como su código hexadecimal, y eso es perfectamente legal en una URL: el estándar permite escribir cualquier carácter así, y los navegadores y servidores llevan décadas deshaciéndolo sin que nadie se entere. De modo que /%6a_security_check y /j_security_check son la misma ruta escrita de dos maneras. La diferencia es quién la mira y cuándo la deshace.
La capa de delante compara la cadena que ha llegado contra su lista de rutas y no encuentra coincidencia, así que la petición no recibe el tratamiento que le tocaba. La capa de detrás deshace el porcentaje y sirve el mismo recurso de siempre. Ninguna de las dos hace nada ilegal por separado; lo que no existe es un acuerdo entre ellas sobre cuándo se deshace la codificación. La recomendación de MITRE para CWE-177 cabe en una línea y es exactamente la que se incumple: decodifica y canonicaliza a tu representación interna antes de validar, y no decodifiques dos veces.
Dicho así suena a problema de quien escribe productos. No lo es. El patrón es proteger una ruta por su nombre desde una capa distinta de la que la resuelve, y esa frase describe mucha infraestructura corriente: el location de un nginx que exige auth_basic sobre /admin, una regla de WAF escrita como expresión regular sobre el camino, un balanceador al que le dijiste «publica sólo /api», la cadena de filtros de un framework que protege por prefijo. Cada una de esas piezas normaliza, y lo hace bien y documentado. El hueco no aparece dentro de una pieza: aparece entre dos, cuando sus normalizaciones no son idénticas y nadie lo ha comprobado nunca porque, con la ruta escrita en claro, las dos dan el mismo resultado.
La prueba de treinta segundos, en tu stack
Coge una ruta que tu proxy proteja y pídela dos veces: una tal cual y otra con la primera letra en hexadecimal. %61 es la «a».
curl -s -o /dev/null -w '%{http_code}\n' https://tu.dominio/admin/
curl -s -o /dev/null -w '%{http_code}\n' https://tu.dominio/%61dmin/
Variantes que merecen la pena en la segunda vuelta: la ruta en mayúsculas, con barra final y sin ella, con la codificación doble (%2561dmin, que es el porcentaje escrito a su vez en hexadecimal), con un punto y coma de por medio. Y ahora la parte que hace útil la prueba: lo que buscas no es un 200. Un 200 puede significar simplemente que ese recurso era público y no pasaba nada. Lo que buscas es una diferencia de criterio entre las dos capas, y eso no lo dice el código de respuesta: lo dicen los dos registros puestos uno al lado del otro. Si el proxy anotó que no aplicó la regla y la aplicación anotó que sirvió /admin/, ya tienes la respuesta y no hace falta seguir.
Adelantamos el resultado más probable, porque es el que confunde: contra un nginx a secas no vas a ver ninguna diferencia, y eso es buena señal —deshace el porcentaje antes de decidir qué location aplica, así que las dos peticiones son la misma para él—. El hueco sale cuando hay dos piezas y la segunda vuelve a tocar la ruta. Por eso la prueba que interesa no es contra tu proxy a solas, sino contra la cadena entera tal y como está montada en producción. Y lo obvio, dicho en voz alta porque cuesta poco: esto se hace contra sistemas propios, con permiso y avisando a quien esté mirando los paneles. Lanzado contra un tercero no es una prueba, es otra cosa con otro nombre.
Los dos grep que el parche no sustituye
Si se explotaba antes del aviso, parchear cierra la puerta pero no contesta la pregunta que importa, que es si alguien pasó por ella mientras estaba abierta. Aquí Cisco hizo algo que no siempre hace y que se agradece: publicó dónde mirar, con la ruta entera y con líneas de ejemplo reales. En /var/log/nms/containers/service-proxy/serviceproxy-access.log, peticiones a j_security_check desde direcciones desconocidas o no autorizadas; la línea que publica el aviso empieza así: [2026-09-29T23:11:13.948-05:00] "POST /%6a_security_check HTTP/1.1" 200 - 48 0 4. Y en /var/log/nms/vmanage-server.log, el otro lado de la misma petición, con el usuario a la vista: Request Stored in Map is (/%6a_security_check) for user (viptela-reserved-..).
grep -F '_security_check' /var/log/nms/containers/service-proxy/serviceproxy-access.log | grep -F '%' grep -F 'viptela-reserved-' /var/log/nms/vmanage-server.log
El primero no busca %6a en concreto a propósito: cualquier signo de porcentaje en una línea de _security_check merece una mirada, porque el formulario legítimo no codifica nada en esa ruta. Dicho con precisión, porque importa: el grep es de línea y no de ruta, así que te va a sacar también porcentajes que vengan del agente de usuario o de la cadena de consulta. Son falsos positivos y se descartan de un vistazo, pero conviene saberlo antes de contar. Y el nombre del segundo fichero cuenta por sí solo una parte de la historia: se llama vmanage-server.log porque el producto se llamó vManage. El nombre comercial cambió; el del fichero no, y por eso en el grep hay que escribir el nombre viejo.
Las dos preguntas operativas que vienen detrás de esos dos comandos no son de seguridad, son de operación, y suelen tener peor respuesta. La primera: ¿esos ficheros salen del aparato? Si sólo viven en el disco del equipo que estás investigando, lo estás investigando con sus propias notas, y la acción que exige CISA para este CVE no dice únicamente «parchea»: remite a su directiva BOD 26-04 y a sus requisitos de triaje forense. Es difícil hacer triaje sobre un registro que el propio sospechoso podría haber editado. La segunda: ¿desde qué fecha los tienes? Si la rotación se lleva treinta días, la ventana de septiembre de la que habla el aviso puede que ya no exista. Escribimos justo sobre esto cuando le tocó al Firewall Management Center: parchear cierra la puerta, pero no borra lo que alguien ya se llevó.
No había workaround, pero sí había diferencia
Las dos cosas son ciertas a la vez y conviene no mezclarlas. No hay workaround para el fallo: nada que toques en la configuración del Manager lo arregla. Pero la recomendación que Cisco repite para los despliegues en casa es la de siempre —impedir el acceso desde redes no seguras y limitarlo con un filtrado a hosts conocidos y de confianza—, y esa sí decide cuánta gente podía intentarlo. Un 9,8 sin autenticación por red es devastador cuando el puerto está donde cualquiera llega, y es una tarea de mantenimiento cuando al 443 del orquestador sólo llega una red de gestión con origen filtrado.
Que no se lea al revés: eso no sustituye al parche, y el plazo de CISA son tres días que se cumplen. Lo que cambia es si esta semana has tenido un incidente o una ventana de mantenimiento. Ya escribimos sobre por qué el orquestador de tu SD-WAN es la llave de todas tus sedes, y sobre el reparto de responsabilidades cuando sale un fallo de un fabricante de red: la vulnerabilidad es suya, la excepción del cortafuegos es tuya. Este caso es la misma frase con otro producto; si ya la leíste allí, el párrafo te sobra.
Los límites de lo que acabamos de decir
No hemos visto el exploit ni lo hemos reproducido, y no tenemos ningún Manager afectado que contar: la mecánica que hemos descrito es la que publica el aviso de Cisco y recoge la ficha de CISA, y hasta ahí llegamos. Los dos grep salen de la descripción de indicadores de ese aviso, pero el formato exacto de cada línea depende de tu versión, así que mira una línea real antes de fiarte del recuento. La prueba del %61 es ilustrativa y no es un escáner: una diferencia entre las dos capas no es automáticamente un agujero, es un sitio donde hay que mirar. Y las versiones corregidas son las que figuraban en el aviso del 30 de septiembre de 2026: Cisco revisa sus avisos, y lo que manda es el documento, no este post.
El conflicto de interés, por delante: no somos revendedores de Cisco ni de ninguna plataforma SD-WAN, pero operar orquestadores ajenos, decidir desde dónde se alcanzan y aplicar parches con plazo es trabajo que facturamos.
Fuentes (verificadas el 1 de octubre de 2026): el identificador del aviso, su fecha y hora de publicación (30-09-2026, 13:00 GMT), el CVSS base 9,8 con su vector, la clasificación CWE-177, la frase sobre que afecta al producto con independencia de la configuración, la tabla de ramas y primeras versiones corregidas, la declaración de que no existe workaround, la recomendación de restringir el acceso a hosts conocidos, la mención de que el PSIRT conoció explotación activa en septiembre de 2026 y los indicadores de compromiso con los nombres de fichero serviceproxy-access.log y vmanage-server.log — aviso de seguridad de Cisco cisco-sa-sdwan-webauth-xr8beuuU; la fecha de alta en el catálogo (30-09-2026), el plazo de corrección del 3 de octubre de 2026 y la acción requerida con referencia a la BOD 26-04 y a los requisitos de triaje forense — catálogo de vulnerabilidades explotadas conocidas de CISA; la definición de CWE-177 y la recomendación de decodificar y canonicalizar antes de validar — CWE-177, MITRE; que j_security_check, j_username y j_password son los nombres que la especificación reserva para la autenticación por formulario — documentación de Jakarta EE; el detalle del carácter codificado en la ruta y la lectura de los indicadores — análisis de Rapid7. La lectura de que el patrón «proteger por nombre de ruta desde otra capa» es el problema de fondo, los dos grep y la prueba del %61 son nuestros, no de ninguna de esas fuentes.
¿Desde cuántos sitios se alcanza tu orquestador?
Si la respuesta es «está en la DMZ» y no una lista de orígenes, ese dato lo decide hoy un aviso de fabricante y no tú. Diseñamos y operamos SD-WAN multisede y el perímetro de seguridad que la rodea, incluido el aburrimiento de llevar la lista de quién alcanza el plano de gestión y de que los registros salgan del aparato antes de necesitarlos. Si quieres, lo miramos sobre tu red y te decimos qué está publicado que no debería.
Hablar con everyWAN