Blog / CVE explicados

La Paradoja del Asterisco: CVE-2026-123158 y el Bypass de Autenticación OpenSSH-musl

Dos sistemas sin relación entre sí eligieron el carácter "*" para significar "aquí no hay nada utilizable". CVE-2026-123158 traza exactamente cómo esa colisión evade la autenticación por contraseña de OpenSSH en Linux basado en musl, qué confirmó y qué no confirmó una ejecución de laboratorio con digest fijado, y la única pregunta que quedó abierta.

Estudios de doctorado en Seguridad de la Información completadosDefensa universitaria superada · Defensa final de la tesis pendiente

Un carácter, dos significados sin relación, un bypass de autenticación

Envié primero una contraseña incorrecta. Luego envié una contraseña de 300 bytes de longitud a una cuenta que no tenía ninguna contraseña configurada. sshd me dejó entrar.

Dos sistemas que nunca han oído hablar el uno del otro hicieron eso posible, al elegir ambos el mismo carácter para significar "aquí no hay nada utilizable". /etc/shadow ha usado durante décadas un asterisco aislado para marcar una cuenta que nunca debería aceptar una contraseña. La función crypt() de musl, en ciertos fallos internos, devuelve exactamente ese mismo carácter en lugar del valor nulo que POSIX realmente especifica. Ninguna de las dos decisiones es errónea por sí sola. Pero al colocarlas en la misma comparación de cadenas, se puede hacer creer a sshd que una contraseña coincidió cuando en realidad nunca se comparó nada.

Ese primer párrafo es también el único dato que este artículo no se permitirá exagerar: una única contraseña excesivamente larga, enviada como el primer y único intento de una conexión nueva contra la misma cuenta, sin enviar antes una contraseña incorrecta, no autenticó, en la misma ejecución de laboratorio. Una secuencia funcionó. La otra no. Eso es un dato de una sola ejecución, no una afirmación de que todo primer intento de contraseña contra una cuenta vulnerable tenga éxito. La diferencia entre esos dos resultados, y por qué ocurrió, se trata directamente y con honestidad más adelante, incluidas las partes que todavía siguen genuinamente sin resolverse.

Este artículo recorre primero el trasfondo: qué hace realmente la autenticación SSH, qué almacena /etc/shadow, qué es PAM y por qué compilar sin él importa aquí, por qué musl no es glibc, y qué debería hacer crypt() cuando falla. Solo una vez que eso quede establecido, el mecanismo de causa raíz, la evidencia de laboratorio, el parche y las preguntas abiertas cobran realmente sentido, sin pedirle al lector que acepte nada por fe.

Si administra una máquina Linux basada en musl con autenticación SSH por contraseña habilitada y sin PAM, la versión corta es esta: revise su archivo shadow en busca de un asterisco aislado, compruebe si algo más en la máquina tiene un hash $5$ o $6$, y aplique el parche. La versión larga, incluyendo exactamente por qué funciona esto y exactamente qué confirmó y qué no confirmó una ejecución de laboratorio real, es el resto de este artículo.

Un ciclo de respuesta de un día, y el detalle del aviso que la mayoría de la cobertura omite

OpenSSH 10.6p2 se publicó el 5 de noviembre de 2026 con un único punto de seguridad, y se publicó rápido. El propio anuncio de lanzamiento de Damien Miller afirma que el parche salió al día siguiente de que el equipo de OpenSSH conociera el problema, y argumenta que los casos recientes de errores similares redescubiertos de forma independiente por agentes de IA en cuestión de horas significan ahora que un embargo largo beneficia principalmente a los atacantes y no a quienes defienden. Esa es la justificación declarada del cronograma comprimido, no una suposición que este artículo haga en nombre del equipo. [2]

Las notas de la versión de OpenSSH describen que sshd ahora evita "un bypass de autenticación por contraseña que afecta a algunos sistemas Linux compilados y ejecutados con el soporte de PAM deshabilitado" (PAM, Pluggable Authentication Modules, es la capa que la mayoría de los sistemas Linux usan para decidir si un inicio de sesión tiene éxito; unas secciones más adelante se trata con propiedad), y atribuyen el reporte a Ozgun Kultekin de Wakefield. [3] El propio artículo técnico detallado del investigador vive en sshhole.com, titulado "*sshole", y declara de forma independiente la misma ventana de 257-1024 bytes y la misma colisión de caracteres que este artículo traza por completo más adelante. [4] El nombre de usuario de GitHub del sitio, oz9un, es consistente con el nombre acreditado, aunque nada en el registro confirma ese vínculo de forma explícita; no es determinante para nada técnico aquí, simplemente vale la pena nombrarlo con claridad en lugar de afirmarlo.

El propio aviso de Alpine Linux es la segunda fuente primaria en la que se apoya todo este artículo. Declara con precisión el estado de la cuenta vulnerable: un campo de shadow que es literalmente "*", y señala explícitamente que una cuenta bloqueada, cuyo campo es "!", no está afectada. Formula la precondición general como la necesidad de al menos otra cuenta local con un hash de contraseña real en la misma máquina, el formato SHA-256/SHA-512 predeterminado de Alpine, mostrado en la tabla justo más adelante, afirma que una sola de esas cuentas basta para hacer alcanzable a toda cuenta sin contraseña, y describe el propio ataque como algo que toma una sola conexión y no necesita reintentos, una frase a la que este artículo vuelve directamente más adelante. [5]

El propio CVE-2026-123158 tiene una puntuación base CVSS 3.1 de 8.1 (Alta), vector AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H, y CWE-394, Unexpected Status Code or Return Value, que es la clasificación correcta para un error que, en el fondo, consiste en confiar en un valor que señala un error sin comprobarlo. El propio enriquecimiento de CISA sobre el mismo registro califica por separado la madurez de explotación como "poc" y la automatización como "no". No se ha reportado explotación en el mundo real hasta el momento de escribir esto. [1]

La mayoría de la cobertura secundaria de este CVE lo reduce a "una contraseña excesivamente larga evade SSH". El aviso mismo es más preciso que eso en tres puntos que casi toda esa cobertura omite: la cuenta tiene que estar exactamente en el estado "*", no "!"; algo más en la misma máquina ya tiene que tener un hash utilizable; y la ventana de bytes es 257 a 1024, no "larga" en general. El resto de este artículo está construido exactamente alrededor de esos tres detalles, más el que el aviso formula con confianza pero que una ejecución de laboratorio práctica tiene motivos para comprobar en lugar de aceptar por fe.

Qué sucede realmente cuando usted escribe una contraseña SSH

SSH existe para permitir que una máquina administre otra a través de una red en la que ninguna de las dos partes tiene que confiar necesariamente, cifrada y, idealmente, con ambas partes demostrando ser quienes dicen ser. Un cliente se conecta a sshd, el demonio del lado del servidor; las dos partes negocian primero un canal seguro, y solo una vez que ese canal existe ocurre la autenticación, por clave pública, por contraseña o por otro método configurado, antes de que se ejecute cualquier shell o comando. La autenticación por contraseña, el método que concierne a todo este CVE, es un paso específico dentro de ese protocolo más amplio, no un mecanismo separado añadido a un lado.

En un sistema Linux, la información de cuentas vive en /etc/passwd, una línea por cuenta: nombre de usuario, identificador numérico de usuario, directorio de inicio, shell. La propia contraseña, o más precisamente un hash de ella, solía vivir también ahí, hace décadas. Ahora vive en un archivo separado, /etc/shadow, legible solo por root, específicamente para que comprometer una cuenta de usuario ordinaria no entregue también todos los hashes de contraseñas de la máquina.

Ese campo de shadow no siempre es un hash, sin embargo. Lleva un pequeño número de valores especiales que los administradores, las herramientas y el propio sshd tienen todos que interpretar correctamente, resumidos a continuación.

Por diseño, una cuenta marcada con un asterisco aislado no debería poder autenticarse por contraseña con ninguna entrada en absoluto, porque "*" no es en absoluto un formato de hash. Es una convención: esta cuenta existe, pero ninguna contraseña la desbloqueará jamás. Esa convención es exactamente en la que confían los administradores para cuentas solo por clave, identidades de servicio, e imágenes base de contenedores que incluyen una cuenta root en la que nadie espera iniciar sesión con contraseña. Toda la vulnerabilidad que traza este artículo es un caso en el que esa convención dejó de cumplirse silenciosamente.

Qué significa cada valor del campo de contraseña en /etc/shadow, y si es alcanzable mediante este bypass.
Campo de contraseñaSignificadoAfectado
$1$... / $2a$/$2b$/$2y$... / $5$... / $6$...crypt() se ejecuta con normalidad aquí y produce un hash genuino con el que comparar.Un hash real de contraseña (MD5, bcrypt, SHA-256 o SHA-512)No
! o !...El aviso de Alpine lo indica explícitamente.Cuenta bloqueadaNo
*Bajo las condiciones exactas trazadas en este artículo.No hay contraseña utilizable configurada (cuenta solo por clave)Sí
(cadena vacía)Se gestiona mediante una rama separada y anterior dentro de sys_auth_passwd(), antes de la ruta que concierne a este CVE.No se requiere contraseñaNo

PAM, y por qué compilar sshd sin él lo cambia todo

PAM, Pluggable Authentication Modules, es la capa que la mayoría de las distribuciones Linux usan para decidir si un intento de inicio de sesión, incluido SSH, realmente tiene éxito. Una aplicación como sshd normalmente no implementa ella misma la verificación de contraseñas; se lo pide a PAM, y PAM ejecuta los módulos que el administrador configuró para ese servicio, siendo pam_unix para contraseñas locales el caso común, con otros disponibles para servicios de directorio o esquemas multifactor. Esa indirección es el valor predeterminado en distribuciones basadas en glibc como Debian, Ubuntu, Fedora y RHEL: en esos sistemas, sshd nunca llama directamente a una función de hashing de contraseñas. [2]

Alpine Linux, y la mayoría de las compilaciones embebidas basadas en musl (OpenWrt, Buildroot, Yocto), compilan OpenSSH con --without-pam en su lugar. En esos sistemas no hay capa PAM a la que delegar. sshd recurre a su propio código de comparación genérico, en auth-passwd.c, llamando a crypt(3) directamente contra la propia entrada de shadow de la cuenta. Esa única decisión en tiempo de compilación es la bifurcación del camino en la que este CVE vive por completo en una de las ramas: un sistema con PAM habilitado nunca llega siquiera a la comparación vulnerable, no por ninguna defensa específica que lo impida, simplemente esa ruta de código nunca se ejecuta en esa compilación. [2]

Un detalle más que vale la pena conocer antes de las dos secciones siguientes, porque la sección de causa raíz depende de él directamente: para un nombre de usuario que sshd considera inválido, la ruta sin PAM igualmente ejecuta una comparación, contra una contraseña falsa en caché, para que rechazar una cuenta inexistente tome aproximadamente el mismo tiempo que rechazar una contraseña incorrecta en una cuenta real. Esa es una defensa de temporización deliberada y de larga data contra la enumeración de nombres de usuario, y comparte exactamente el mismo código de selección de sal que desvía una cuenta "*" hacia la rama equivocada. "Código de selección de sal" aquí simplemente significa la lógica que decide contra qué valor de referencia crypt() compara una contraseña; qué es exactamente ese valor de referencia, y cómo se equivoca, es lo que retoman las secciones de mecanismo más abajo.

Un primer paso compartido, sshd recibe una solicitud de autenticación por contraseña, y luego dos ramas: una compilación con PAM habilitado que delega en la pila PAM y nunca llama a crypt() directamente, y una compilación sin PAM que ejecuta su propio código de comparación, terminando en una comparación mediante crypt() contra el hash almacenado, marcada como el único paso peligroso.

01sshd recibe una solicitud de autenticación por contraseña
Compilación con PAM habilitadodistribuciones con glibc: Debian, Ubuntu, Fedora, RHEL
02Delegado a la pila PAMpam_unix u otro módulo similar se encarga de ello
03PAM acepta o rechazasshd nunca llama a crypt() directamente
Compilación sin PAM--without-pam: Alpine, compilaciones embebidas basadas en musl
02auth-passwd.c ejecuta la propia comparación de sshdNo existe ninguna capa PAM a la que delegar
03Comparación mediante crypt() contra el hash almacenadoLa ruta exacta en la que vive este CVE
El código de verificación de contraseña de sshd se bifurca por completo ya en tiempo de compilación: una compilación con PAM delega la decisión a PAM, mientras que una compilación sin PAM ejecuta su propia comparación directamente, hasta llegar a un paso concreto y vulnerable.

musl, glibc, y por qué este error no afecta a Debian

libc, la biblioteca estándar de C, es la capa entre un programa y el núcleo del sistema operativo subyacente: asignación de memoria, manejo de cadenas y, para esta historia, crypt(). Todo programa Linux escrito en C se enlaza contra una de ellas. La mayoría de las distribuciones de escritorio y servidor usan glibc, la biblioteca C de GNU. Alpine Linux usa musl en su lugar, una implementación más pequeña y escrita de forma independiente del mismo conjunto de interfaces estándar.

musl y glibc no son intercambiables a nivel de implementación en todo lo que exponen. Implementan las mismas interfaces estándar, crypt() entre ellas, pero no siempre con un comportamiento interno idéntico detrás de esa interfaz. Esa brecha suele ser invisible para el código de aplicación que la llama a través de ella. Aquí dejó de ser invisible.

Alpine distribuye OpenSSH compilado contra musl. OpenSSH llama a la interfaz estándar crypt(3) exactamente igual que lo haría con cualquier otra libc. La propia implementación de musl de esa interfaz, específicamente sus rutas de código SHA-256 y SHA-512, es donde realmente vive el comportamiento de fallo específico que trazan las siguientes tres secciones, no en el propio código de OpenSSH ni en el empaquetado que hace Alpine de él.

Esto vale la pena precisarlo, porque es fácil exagerarlo: la vulnerabilidad necesita musl específicamente, no simplemente "compilado sin PAM". El código de comparación sin PAM de OpenSSH existe en cualquier compilación hecha de esa manera, incluyendo, hipotéticamente, una compilación no estándar con glibc. Pero el comportamiento de fallo con el que colisiona este CVE, que crypt() devuelva un centinela de un carácter en lugar de NULL, es una decisión propia de la implementación de musl. Esta investigación no encontró el comportamiento equivalente en glibc, y el propio anuncio de lanzamiento de OpenSSH es explícito en que la población afectada es Linux, musl y sin PAM, no sistemas no Linux, no glibc, no sistemas que usan PAM. [2]

Una pila de cinco capas, de arriba a abajo: Alpine Linux como distribución, un sshd de OpenSSH compilado sin PAM, la interfaz de biblioteca crypt(3), la propia implementación de crypt() de musl libc, y en la parte inferior su código de hashing específico por algoritmo, cada capa conectada por la llamada o convención concreta nombrada entre ellas. Ninguna capa está marcada como peligrosa: esta investigación no verificó de forma independiente el comportamiento de fallo a nivel del propio código fuente crypt_sha256.c/crypt_sha512.c de musl.

Alpine Linux (la distribución)Distribuye OpenSSH compilado contra musl, no glibc
Compilación del paquete apk: OpenSSH compilado contra musl
sshd de OpenSSH, compilado con --without-pam
Llamada a la biblioteca C: crypt(attacker_password, stored_hash)
Interfaz de libc: crypt(3)
Despachador ligero: búfer estático, __crypt_r()
La propia implementación de crypt() de musl libc
Despacho según el prefijo de la sal ($6$, $5$, ...)
Código de hashing específico por algoritmo: crypt_sha256.c / crypt_sha512.c
Dónde se sitúa musl entre el código de autenticación de OpenSSH y la implementación real de hashing de contraseñas.

Qué se supone que debe hacer crypt() cuando falla

crypt(3) es la interfaz tradicional de Unix para calcular y verificar ese hash unidireccional, no cifrado reversible, el valor de shadow almacenado que ya conoció dos secciones atrás. crypt(password, salt) calcula el hash, y esa misma función se reutiliza para verificar un intento de inicio de sesión, recalculando el hash a partir de la contraseña intentada y el propio hash almacenado de la cuenta reutilizado como sal, y luego comparando los dos resultados con una comparación de cadenas simple. Existe una sal para que dos cuentas que comparten la misma contraseña no terminen con el mismo hash almacenado.

POSIX especifica qué debe hacer crypt() cuando algo falla internamente durante ese cálculo: devolver un puntero nulo, y establecer errno para indicar por qué. Un llamador que comprueba si hay NULL sabe que el cálculo falló y nunca debería tratar el resultado como un hash válido para compararlo con nada más.

La crypt() de musl no siempre sigue ese contrato. Lo que hace en su lugar, y por qué esa desviación específica colisiona con las propias convenciones de /etc/shadow de la sección anterior, es el mecanismo que el resto de este artículo traza a nivel de código fuente, comenzando ahora.

Por qué una cuenta válida termina desviada a la ruta de sal de respaldo

Aquí está el primer detalle no obvio, y es un desliz de lógica de un solo carácter, no algo dramático. La propia comprobación de selección de sal de OpenSSH en sys_auth_passwd() no pregunta si el hash almacenado de una cuenta es el marcador de posición "*". Pregunta si ese hash tiene al menos dos caracteres de longitud.

La condición de guarda es `if (authctxt->valid && pw_password[0] && pw_password[1]) salt = pw_password; else salt = NULL;`. Para la cadena de un solo carácter "*", pw_password[0] es el propio asterisco, que es verdadero, pero pw_password[1] es el NUL terminador de la cadena, que es falso en C. Un carácter verdadero. Un terminador falso. Esa es toda la condición, y ya está equivocada para este caso. Se evalúa como falsa aunque authctxt->valid sea verdadero, es decir, incluso para una cuenta perfectamente real y existente. El código cae en la misma rama salt = NULL usada para un nombre de usuario que no existe en absoluto, no porque algo compruebe específicamente "*", sino como efecto secundario de una comprobación de longitud que, por coincidencia, se lee como "¿es esto lo bastante largo para ser plausiblemente un hash?". [6]

Cuando salt es NULL, xcrypt() llama a pick_salt(), reproducida íntegramente más abajo con su aviso de licencia BSD original preservado. pick_salt() mantiene un búfer estático de 32 bytes con alcance de un proceso y lo devuelve inmediatamente una vez ya está poblado. En su primera llamada por proceso, recorre todas las cuentas locales mediante setpwent()/getpwent(), y copia el prefijo identificador de algoritmo del primer hash que encuentra que comience con "$" y contenga un segundo "$", es decir, cualquier hash en formato modular-crypt: MD5 ($1$), bcrypt ($2a$/$2b$/$2y$), SHA-256 ($5$) o SHA-512 ($6$), no uno específico de ellos. Si nada en el sistema tiene un hash utilizable, recurre a la sal tradicional de estilo DES "xx". [7]

El aviso de Alpine formula la precondición como la necesidad de otra cuenta con específicamente un hash $5$ o $6$. El código mismo es más amplio que eso: acepta el primer hash en formato modular-crypt en el orden en que getpwent() enumere las cuentas, lo cual también cubre MD5 y bcrypt. Esa brecha entre la redacción del aviso y el comportamiento real del código es una lectura propia de esta investigación, no algo que ninguna de las dos fuentes afirme directamente, pero vale la pena señalarla: una máquina que use en otros lugares solo hashes MD5 o bcrypt seguiría satisfaciendo la precondición real, aunque la propia redacción de Alpine pueda sugerir lo contrario.

Un límite más, y es propio de OpenSSH, sin relación con musl: auth_password() rechaza cualquier contraseña más larga que MAX_PASSWORD_LEN, definido como 1024, antes de que sys_auth_passwd() y, por lo tanto, crypt(), lleguen a ejecutarse. Por eso exactamente la ventana del aviso termina en 1024 bytes en lugar de quedar abierta; una contraseña más larga nunca se acerca siquiera a la comparación vulnerable. [8]

/* auth-passwd.c, sys_auth_passwd() -- pre-fix */
char *pw_password = authctxt->valid ?
    shadow_pw(pw) : xstrdup(pw->pw_passwd);
...
if (authctxt->valid && pw_password[0] && pw_password[1])
    salt = pw_password;
else
    salt = NULL;
encrypted_password = xcrypt(password, salt);
...
ret = strcmp(encrypted_password, pw_password) == 0;

/* openbsd-compat/xcrypt.c -- Copyright (c) 2003 Ben Lindstrom, BSD license */
static const char *
pick_salt(void)
{
    struct passwd *pw;
    char *passwd = NULL, *p;
    size_t typelen;
    static char salt[32];

    if (salt[0] != '\0')
        return salt;
    strlcpy(salt, "xx", sizeof(salt));
    setpwent();
    while ((pw = getpwent()) != NULL) {
        if (passwd != NULL)
            freezero(passwd, strlen(passwd));
        if ((passwd = shadow_pw(pw)) == NULL)
            continue;
        if (passwd[0] == '$' && (p = strrchr(passwd+1, '$')) != NULL) {
            typelen = p - passwd + 1;
            if (passwd[1] == '2' && strlen(p + 1) >= 22)
                typelen += 1 + 22;
            strlcpy(salt, passwd, MINIMUM(typelen, sizeof(salt)));
            explicit_bzero(passwd, strlen(passwd));
            goto out;
        }
    }
 out:
    if (passwd != NULL)
        freezero(passwd, strlen(passwd));
    endpwent();
    return salt;
}

Donde el "*" de /etc/shadow se encuentra con el propio "*" de musl

sshd cree una cosa específica en este punto del código: la cadena que crypt() devuelve para una contraseña genuina nunca puede ser igual al propio hash marcador de posición de la cuenta. El atacante controla exactamente la única entrada que rompe esa creencia: cuánto dura su contraseña.

El aviso de Alpine declara con claridad el estado de la cuenta vulnerable: un campo de contraseña de shadow que es literalmente "*", la convención para una cuenta sin hash de contraseña utilizable, una cuenta solo por clave. Es explícito en que una cuenta bloqueada, campo "!", no está afectada. [5]

La crypt() de musl, en sus rutas de hashing SHA-256 y SHA-512, devuelve la cadena literal "*", una constante de cadena almacenada en memoria de solo lectura, en lugar de NULL, cuando falla la verificación interna del hash. Esa es exactamente la violación de contrato que describió la sección anterior, ahora disparada por una condición concreta: una contraseña de entrada excesivamente larga. La comprobación exacta por algoritmo dentro de crypt_sha256.c y crypt_sha512.c no se leyó de forma independiente en esta investigación; lo que aquí se documenta está limitado al aviso de Alpine, al propio artículo del investigador que reportó la vulnerabilidad, y a un hilo de la lista de correo de musl de diciembre de 2024 que describe el idéntico comportamiento de "*" en caso de fallo en un contexto completamente distinto, casi dos años antes de que existiera este CVE. [5] [4] [9] El propio crypt.c de musl es un despachador ligero y no realiza ninguna de estas comprobaciones por sí mismo, confirmado mediante inspección directa del archivo. [10]

El fallo que produce "*" se dispara por una contraseña de entrada excesivamente larga. El aviso de Alpine y el registro CVE dan ambos la misma ventana: 257 a 1024 bytes. El límite inferior es el propio umbral interno de musl; el límite superior, como se trató dos secciones atrás, es el límite MAX_PASSWORD_LEN preexistente de OpenSSH, sin relación con musl en absoluto. [5] [1]

Juntando todo: un atacante que envía una contraseña en ese rango de bytes, contra una cuenta cuyo hash almacenado es "*", hace que crypt() calcule y devuelva "*". La comparación que sigue es `strcmp("*", "*")`, que es verdadera, por definición, porque ambos lados son ahora exactamente la misma cadena de un carácter. La autenticación tiene éxito, para una cuenta configurada explícitamente para nunca aceptar una contraseña en absoluto.

Ninguna de las dos mitades de esto se ve mal de forma aislada, y por eso es tan fácil pasarla por alto. Los sistemas de tipos no ayudan: ambos lados de la comparación son char*, y "*" es una cadena sintácticamente ordinaria y devuelta con éxito tanto en el lado de lectura de shadow como en el lado de crypt(). Nada aquí parece una condición de error para un analizador estático. Y PAM habría atrapado este comportamiento por accidente, mediante un error peor: en un sistema con PAM habilitado, el mismo valor de retorno de musl en cambio hace caer a pam_unix, porque pam_overwrite_string() intenta escribir en ese literal de cadena de solo lectura, una denegación de servicio en lugar de un bypass, y un problema ya reportado de forma independiente en diciembre de 2024. Los sistemas sin PAM no tienen una llamada intermedia equivalente, por lo que el mismo comportamiento subyacente de musl produce un éxito silencioso de autenticación en lugar de una caída. [9]

/* musl libc, src/crypt/crypt.c -- MIT license */
char *crypt(const char *key, const char *salt)
{
    static char buf[128];
    return __crypt_r(key, salt, (struct crypt_data *)buf);
}
/* a thin dispatcher only -- the overlong-key / "*"-on-failure behavior
   lives inside crypt_sha256.c / crypt_sha512.c, not here. That specific
   file was not independently read in this investigation; see the text. */

Dos lados, cada uno marcado con un glifo de asterisco: a la izquierda, la convención de /etc/shadow según la cual un asterisco aislado significa que ninguna contraseña será jamás válida; a la derecha, el centinela de fallo de crypt() en musl, que también devuelve un asterisco literal en ciertos fallos. Ambos convergen en un punto resaltado que dice strcmp de asterisco y asterisco igual a cero, con el veredicto de que la autenticación tiene éxito.

La propia convención de /etc/shadow

Un asterisco aislado significa: ninguna contraseña será jamás válida.

El centinela de fallo de crypt() en musl

Devuelve el mismo carácter en lugar de NULL cuando falla.

strcmp("*", "*") == 0

COINCIDENCIA: LA AUTENTICACIÓN TIENE ÉXITO

Dos convenciones sin relación entre sí, ambas escritas con un único asterisco, convergen en la misma comparación.

Lo que realmente se construyó, fijó y ejecutó

Todo en las dos secciones siguientes proviene de una ejecución real, no de un experimento mental: una imagen Alpine 3.24.4 con digest fijado, construida a partir de una copia modificada de un repositorio público de prueba de concepto, ejecutada el 7 de noviembre de 2026 y reverificada de forma independiente contra su propia evidencia registrada por hash antes de escribir nada de lo que sigue.

La imagen base fue alpine:3.24, fijada a su digest de manifiesto linux/amd64 en lugar de confiar en una etiqueta flotante. Nada aquí quedó al capricho de una etiqueta flotante. La propia imagen de laboratorio se construyó deliberadamente con OpenSSH 10.6p1, compilado con --without-pam: la versión previa al parche, vulnerable, no la 10.6p2 ya parcheada. Las versiones de musl, libcrypto3, shadow y zlib se capturaron directamente de los propios metadatos de paquete de la imagen construida, no se aproximaron a partir de un índice de rama en vivo.

Las entradas de shadow para root, alice, alpine y victim se establecieron en "*"; a bob se le dio un hash SHA-512 real y determinista, construido con una sal fija alimentada mediante un archivo en lugar de un argumento de línea de comandos, como control de prueba negativa deliberado. sshd_config estableció PasswordAuthentication yes y, cambiado deliberadamente respecto al valor predeterminado propio de la prueba de concepto pública, PermitRootLogin no, ya que root nunca formó parte del alcance de prueba aprobado de este laboratorio.

El aislamiento fue más estricto, no más laxo, que los valores predeterminados de Docker: el contenedor se ejecutó en una red bridge configurada como --internal, sin ruta predeterminada de salida, Privileged false, CapDrop ALL, y solo se volvieron a añadir CAP_NET_BIND_SERVICE, CAP_SETGID, CAP_SETUID y CAP_SYS_CHROOT, cada una justificada contra el propio código fuente de separación de privilegios de OpenSSH 10.6p1 en lugar de asumida, además de no-new-privileges. Ninguna contraseña o secreto apareció en la línea de comandos de ningún proceso; cada fixture se leyó desde un archivo.

Solo alice y bob formaron parte del plan de prueba aprobado. Lo que ese límite sí sostuvo, y lo que no, tiene su propia sección, colocada justo después de los resultados en lugar de mezclada silenciosamente en ellos.

Base image     alpine:3.24, pinned to linux/amd64 digest
               sha256:0f49f331d9212aff11b932863be4eb01cdf962969
               aa86dab7d1ee21ac6f5f52b  (resolves to Alpine 3.24.4)
Lab image      sshole-lab:revision3
               sha256:b973cd33dfd98a767b93cd5f95987d817f7b8eeda0
               6e804e173b431ae6540e45
OpenSSH        10.6p1, built --without-pam (the pre-fix version,
               deliberately -- this run tests the vulnerable build)
musl           1.2.6-r3
Capabilities   --cap-drop=ALL
               --cap-add=NET_BIND_SERVICE --cap-add=SETUID
               --cap-add=SETGID --cap-add=SYS_CHROOT
               --security-opt=no-new-privileges
Network        sshole-lab-net, --internal (no default route out)
sshd_config    PasswordAuthentication yes
               PermitRootLogin no

El caso A falló. El caso B tuvo éxito. Bob no se movió en ningún sentido.

Tres hipótesis entraron en esta ejecución, formuladas explícitamente como cosas por comprobar, no cosas por confirmar. Verdadero, falso o no concluyente eran, cada uno, un resultado legítimo y reportable desde el inicio.

El caso A probó una única contraseña excesivamente larga sin preparación previa: 300 bytes, enviados como la primera y única solicitud de autenticación por contraseña de una conexión nueva, contra alice, cuyo campo de shadow es "*". No tuvo éxito. El registro del servidor muestra una conexión, una línea "Failed password for alice", y el cierre de la conexión. No aparece ninguna línea "Accepted" en ninguna parte para esa conexión. Esto se registra en el dossier como no reproducido.

El caso B probó la misma contraseña, preparada: una contraseña corta y deliberadamente incorrecta enviada primero, seguida inmediatamente por la misma contraseña excesivamente larga de 300 bytes, como dos solicitudes de autenticación secuenciales dentro de una conexión SSH contra alice. Tuvo éxito. El registro del servidor muestra "Failed password for alice ... port 33394 ssh2" seguido inmediatamente por "Accepted password for alice ... port 33394 ssh2", ambos en el mismo puerto de origen idéntico, de principio a fin, una sola conexión. La transcripción del cliente confirmó el marcador esperado de ejecución de comandos como alice. Esto se registra como reproducido.

Los casos C1 y C2 usaron a bob, el control con hash real. C1, la contraseña real de bob, tuvo éxito como comprobación de sanidad de control positivo. C2, la misma contraseña excesivamente larga de 300 bytes que acababa de funcionar contra alice, falló contra bob: "Accepted password for bob ... ssh2" para la contraseña correcta, "Failed password for bob ... ssh2" para la excesivamente larga. Juntos, confirman que la contraseña excesivamente larga por sí sola no evade la autenticación en general; solo coincide con el marcador de posición degenerado "*", exactamente como predice el mecanismo de causa raíz anterior.

Estos cuatro resultados son un dato de una construcción específica con digest fijado, ejecutada una vez, el 7 de noviembre de 2026. No son una afirmación sobre cómo se comporta CVE-2026-123158 en todas las combinaciones de musl y OpenSSH en todas partes; son lo que esta imagen exacta, esta construcción exacta de OpenSSH 10.6p1, y estas entradas exactas realmente hicieron, en esa única ejecución. El dossier los registra deliberadamente con ese alcance adjunto, y este artículo mantiene el mismo encuadre en lugar de generalizarlo.

Case A -- single-shot, alice, no priming -- not reproduced
  Failed password for alice ... ssh2
  [connection closes; no Accepted line for this connection]

Case B -- primed (short wrong, then overlong, one connection), alice -- reproduced
  Failed password for alice ... port 33394 ssh2
  Accepted password for alice ... port 33394 ssh2

Case C1 -- bob, real password, positive control -- reproduced
  Accepted password for bob ... ssh2

Case C2 -- bob, same overlong password, negative control -- reproduced
  Failed password for bob ... ssh2

Una tabla con cuatro casos de prueba de una ejecución de laboratorio. El caso A, una única contraseña excesivamente larga sin preparación previa contra alice, registró una contraseña fallida y el cierre de la conexión: no reproducido. El caso B, la misma contraseña precedida primero por un intento corto incorrecto, registró una contraseña fallida seguida de una contraseña aceptada: reproducido. El caso C1, la contraseña real de bob, registró una contraseña aceptada como línea base. El caso C2, la misma contraseña excesivamente larga contra bob, registró una contraseña fallida como control negativo. Una pregunta abierta, rastreada por separado, señala que por qué la preparación previa importó en esta ejecución sigue sin resolverse a nivel del código fuente.

CasoIntentoResultado registradoLectura
Caso AUn solo intento, sin preparaciónFailed password → conexión cerradaNo reproducido
Caso BPreparado: incorrecto y luego excesivamente largoFailed password → Accepted passwordReproducido
Caso C1La contraseña real de bobAccepted passwordReproducido (línea base)
Caso C2La misma contraseña excesivamente larga contra bobFailed passwordReproducido (control negativo)
Pregunta abierta (C23)

Por qué la preparación previa importó en esta ejecución sigue sin resolverse a nivel del código fuente: pick_salt() devuelve una sal de forma síncrona en cualquier llamada, y nada en su propia lógica exige evidentemente una preparación previa.

Cuatro resultados de laboratorio de una sola ejecución con digest fijado, en el propio lenguaje de registro del servidor, no como un marcador de aprobado/reprobado.

Lo que esta ejecución no prueba, y los intentos contra root que nadie reveló

Una ejecución de laboratorio es tan digna de confianza como lo que admite que no comprobó. Esta admite tres cosas específicas con claridad, en lugar de dejar que un conjunto limpio de resultados dentro de alcance sustituya a un proceso limpio.

La primera es un hallazgo de alcance, no un hallazgo de mecanismo. El mismo archivo de evidencia que registró la prueba de un solo intento del caso A contra alice también contiene, más temprano en la misma ventana de captura, una secuencia no revelada contra root: una línea "Failed none for root", luego dos líneas consecutivas "Failed password for root ... ssh2" dentro de una conexión, y luego el cierre de la conexión. No aparece ninguna línea "Accepted" para root en ninguna parte. El valor enviado detrás de esos dos intentos es desconocido, y este informe no lo adivinará. root nunca formó parte del alcance aprobado de este laboratorio, solo alice y bob, en cada capa de la autorización de esta tarea, y el propio resumen del operador de la ejecución sobre lo que se probó nunca lo mencionó.

Nada se comprometió: ambos intentos contra root fallaron, y PermitRootLogin no se confirmó en la propia configuración efectiva del demonio para esta ejecución. Una desviación de alcance que de todos modos falla sigue siendo una desviación de alcance. Es exactamente por eso que el estado de laboratorio de este dossier se registra como "parcial" en lugar de "reproducido", una distinción que vale la pena mantener incluso donde la evidencia dentro de alcance resulta limpia por lo demás. Por qué ocurrió esa secuencia en absoluto, si se ejecutó una lista más amplia de nombres de usuario predeterminados integrada en la herramienta en lugar de los comandos personalizados y acotados del plan, o algo completamente distinto, es una pregunta para que responda directamente quien ejecutó el laboratorio, no algo sobre lo que especular aquí.

La segunda brecha es una limitación de medición, no de alcance. La prueba del caso B ejecutó un bucle concurrente de sondeo de ID de proceso específicamente para capturar evidencia directa de si el mismo proceso del sistema operativo sirvió tanto el intento de preparación como el intento excesivamente largo, evidencia que incidiría directamente en la pregunta abierta de la siguiente sección. Recogió exactamente una muestra utilizable durante el intercambio. Eso es consistente con continuidad de un solo proceso a través de ambos intentos, no una comparación continua de antes y después a través del intervalo entre ellos. Sugestivo. No concluyente.

La tercera brecha concierne a la propia ruta de red del laboratorio, no a la vulnerabilidad en sí. El plan especificaba publicar el laboratorio en 127.0.0.1:8222; el operador de la ejecución reporta que esa vinculación no proporcionó conectividad de host funcional una vez que el contenedor también estaba conectado a una red --internal sin salida, y en su lugar usó un segundo contenedor de cliente de prueba, desechable, en esa misma red interna, alcanzando el laboratorio por su nombre DNS de contenedor. La propia salida de inspección del contenedor sigue mostrando que la vinculación de puerto solicitada fue aceptada en HostConfig.PortBindings, mientras que NetworkSettings.Ports de ese mismo archivo, que registra lo que Docker realmente estableció en tiempo de ejecución en lugar de lo que simplemente se solicitó, aparece vacío: `{"22/tcp": []}`. Eso corrobora el reporte. No lo prueba de forma concluyente. Nadie en esta ejecución intentó realmente conectarse a 127.0.0.1:8222 desde fuera del contenedor para confirmar que verdaderamente no estaba activo.

Nada de esto cambia lo que mostraron los casos A, B, C1 o C2. Cambia cuánto peso puede tener esta única ejecución como precedente para la siguiente, y la respuesta honesta es menos del que daría una ejecución totalmente conforme con el alcance, específicamente en el punto de la cuenta root.

El parche no toca musl. Añade una condición a una comparación.

El commit que corrige el problema, 4353fdc43ef646c43a8dda7025cc1a898caf1482, "avoid password authn bypass", escrito por Damien Miller el 5 de noviembre de 2026, no toca musl, no toca pick_salt(), y no toca el comportamiento de contraseña excesivamente larga que dispara todo esto. [11]

Añade `#define MIN_PASSWORD_HASH_LEN 4` junto al MAX_PASSWORD_LEN existente en auth-passwd.c, y cambia la decisión final de autenticación para exigir `strlen(pw_password) >= MIN_PASSWORD_HASH_LEN` además de la comprobación strcmp() sin cambios, donde pw_password es el propio hash almacenado de la cuenta, nunca el lado de la comparación influido por el atacante. [11]

Dado que "*" y "!" tienen ambos exactamente un carácter, `strlen(pw_password) >= 4` es falso para toda cuenta que este error podría haber alcanzado alguna vez, y el `&&` corta el circuito antes de que strcmp() siquiera se ejecute, sin importar qué devuelva crypt() para cualquier entrada, larga o corta.

El mismo cambio de propiedad de memoria, una copia en el heap mediante xstrdup() y freezero() en lugar de un puntero que el llamador no posee, se aplicó en auth-passwd.c, openbsd-compat/port-uw.c (la ruta equivalente específica de UnixWare), y pick_salt()/shadow_pw() de xcrypt.c. La nueva comprobación de longitud en sí, sin embargo, llegó solo a la ruta genérica de auth-passwd.c, no a la comparación separada de port-uw.c. UnixWare no es un objetivo de musl, por lo que esta colisión específica no puede ocurrir por esa ruta, lo cual puede ser la razón por la que la comprobación no se consideró necesaria ahí; esa es una lectura propia y plausible de esta investigación, no algo que el commit afirme directamente. [11]

El propio mensaje del commit afirma que el bypass "afecta a algunas configuraciones y configuraciones en tiempo de ejecución", y que el equipo "no considera que la causa raíz esté dentro de OpenSSH" aunque está "bien equipado para evitarla". Ese encuadre coincide con lo que este parche realmente hace: bloquea exactamente la colisión descrita arriba, un centinela de un carácter colisionando con un marcador de posición de shadow de un carácter, poniendo un piso a la longitud del hash almacenado en lugar de validar que el hash calculado esté bien formado para su algoritmo declarado. Si musl, o cualquier software compatible con musl libc contra el que se enlace, alguna vez devolviera alguna otra cadena corta y que no fuera el marcador de posición en una ruta de fallo distinta, de cuatro caracteres o más, esta comprobación específica no la detectaría. Esta investigación no encontró que tal ruta exista actualmente; es un límite de lo que garantiza el parche, no un error residual conocido. [11]

/* auth-passwd.c, commit 4353fdc -- post-fix */
#define MIN_PASSWORD_HASH_LEN 4
...
ret = encrypted_password != NULL &&
    strlen(pw_password) >= MIN_PASSWORD_HASH_LEN &&
    strcmp(encrypted_password, pw_password) == 0;

Alpine no llevó todas las ramas de un salto a la 10.6p2

Alpine no movió todas las ramas soportadas a OpenSSH 10.6p2. Retroportó el parche a la versión que cada rama ya estaba ejecutando, lo cual es práctica estándar de distribución: conservar la versión de paquete existente de la rama, parchear el defecto específico.

El rastreador de seguridad de Alpine enumera siete versiones corregidas específicas de rama en lugar de un único número para toda la flota. Cada rama actualmente soportada recibió el retroporte. [12]

Si está revisando una flota, haga coincidir la rama con la tabla de abajo en lugar de buscar la cadena literal "10.6p2" en cualquier parte. Una máquina de la rama 3.21-main ejecutando la 9.9_p2-r1 retroportada está parcheada, aunque `sshd -V` nunca imprima 10.6.

Las versiones de paquete de OpenSSH, corregidas por rama, que Alpine publicó por su cuenta.
RamaVersión de paquete corregida
edge-main10.6_p2-r0
3.24-main10.3_p1-r2
3.23-main10.2_p1-r1
3.22-main10.0_p1-r11
3.21-main9.9_p2-r1
3.20-main9.7_p1-r6
3.19-main9.6_p1-r3

Qué revisar en sus registros ahora mismo

El propio aviso de Alpine da una orientación de detección y mitigación lo bastante específica para actuar esta misma noche, no un consejo genérico de "revise sus registros".

La lógica de detección es: identifique cada cuenta cuyo campo de contraseña en shadow sea exactamente "*", luego busque en los propios registros del demonio un inicio de sesión por contraseña exitoso contra exactamente esas cuentas, la cadena "Accepted password for" es el marcador a buscar. Junto a eso, revise el uso de doas, usuarios recién creados, archivos nuevos, y cambios en authorized_keys. Si no se puede descartar un inicio de sesión por contraseña contra una cuenta sin contraseña durante la ventana de exposición, la propia postura del aviso es tratar la máquina como comprometida. [5]

La mitigación es, o bien una actualización de paquete seguida de un reinicio del demonio, ya que actualizar el paquete por sí solo no reemplaza el proceso que ya está en ejecución, o bien deshabilitar por completo la autenticación por contraseña como solución provisional, validada antes de que surta efecto el reinicio. [5]

Un matiz específico de root que vale la pena precisar: el PermitRootLogin prohibit-password predeterminado de Alpine ya protege a root de este bypass a menos que un administrador haya establecido explícitamente PermitRootLogin yes, aunque root mismo se distribuya como root:* en la imagen base. Ese valor predeterminado no cubre, sin embargo, todas las rutas hacia root: cualquier otra cuenta capaz de iniciar sesión con contraseña que pueda escalar privilegios, por ejemplo mediante doas, sigue creando exposición real incluso mientras el propio inicio de sesión directo por contraseña de root permanece protegido. [5]

# Package upgrade, then restart -- upgrading alone does not
# replace the already-running daemon
apk upgrade -U -a
rc-service sshd restart

# Or: disable password authentication entirely
#   PasswordAuthentication no   (in sshd_config)
sshd -t
rc-service sshd restart

# Detection: look for a successful password login on an account
# that should never have one
grep "Accepted password for" /var/log/messages
# then cross-reference each username against /etc/shadow for a
# password field that is exactly "*"

¿Por qué falló la misma contraseña en el primer intento y funcionó en el segundo?

¿Por qué falló exactamente la misma contraseña excesivamente larga la primera vez y tuvo éxito la segunda, contra la misma cuenta, en la misma ejecución?

El propio aviso de Alpine afirma que el ataque "toma una sola conexión... y no necesita reintentos". Ambos scripts de prueba de concepto escritos de forma independiente que se revisaron para este dossier, el proporcionado para la construcción del laboratorio y uno separado referenciado directamente en las propias referencias CNA del registro CVE, usan sin embargo dos intentos de autenticación por contraseña dentro de una conexión: una contraseña corta e incorrecta primero, luego la excesivamente larga, atribuyendo ambos esto en sus propios comentarios de código a preparar una sal en caché antes del intento real. Eso no es necesariamente una contradicción. Ambos siguen usando un único transporte SSH, y "sin reintentos" probablemente describe que el ataque es determinista, que no necesita adivinación probabilística repetida, en lugar de afirmar que un único mensaje de autenticación es suficiente. Pero tampoco resuelve la pregunta en el otro sentido. [5] [13] [14]

Trazar el propio código de pick_salt() tampoco lo resuelve. La función busca y devuelve una sal utilizable de forma síncrona, dentro de la misma llamada, la primera vez que se ejecuta. Nada en esa lógica exige evidentemente que ya haya ocurrido una llamada anterior antes de que una contraseña larga pueda compararse con una sal recién encontrada. Según la propia lógica del código, una única contraseña excesivamente larga sin preparación, enviada en la primera solicitud de autenticación de una conexión, también debería llegar a pick_salt(), recibir una sal de vuelta, y hacer que crypt() se ejecute contra ella, exactamente como hace el segundo intento en ambas pruebas de concepto conocidas.

Lo que añadió la ejecución de laboratorio es real, y es exactamente un dato: fallo sin preparación (caso A), éxito con preparación (caso B), en esta construcción específica. Eso es evidencia respaldada por hash de que la preparación importó en esta ejecución. No es una explicación de por qué. El enigma de pick_salt() de arriba queda intacto por ese resultado. "Preparado funciona, sin preparación no" es consistente con varias explicaciones que esta investigación no confirmó de forma independiente: un detalle de la vida del propio proceso de separación de privilegios de OpenSSH (sshd divide el trabajo de autenticación de cada conexión entre un proceso monitor privilegiado y un proceso trabajador sin privilegios; esta investigación no trazó cuánto tiempo vive realmente cada uno), específicamente cuál de esos dos procesos ejecuta pick_salt() y si su tiempo de vida abarca un solo intento de autenticación o toda la conexión; algún otro comportamiento con estado que ni los autores de ninguna de las dos pruebas de concepto ni esta investigación identificaron; o algo específico de esta construcción y temporización exactas que podría no generalizarse en absoluto.

Entonces: esta pregunta sigue abierta a nivel de mecanismo de código fuente después de esta ejecución de laboratorio, exactamente como lo estaba antes de ella. Trate la división de éxito preparado y fallo sin preparación como un dato que vale la pena tomar en serio, no como una explicación resuelta en ningún sentido. El siguiente paso útil, si alguien lo persigue, es trazar el modelo exacto de procesos de separación de privilegios de OpenSSH para esta construcción específica, o volver a ejecutar el caso sin preparación con una temporización distinta para ver si el resultado se mantiene.

Dos cabos sueltos menores, señalados en lugar de adivinados: el propio código fuente de musl para la comprobación exacta de clave excesivamente larga dentro de crypt_sha256.c y crypt_sha512.c nunca se leyó de forma independiente en esta investigación, solo lo describieron el aviso y el reporte de la lista de correo de 2024. Y si el nombre de usuario de GitHub detrás del artículo de sshhole.com es la misma persona a la que las notas de la versión de OpenSSH acreditan por nombre es plausible, por el contenido técnico coincidente, pero no lo confirma nada en el registro. Ninguno de los dos es determinante para ninguna afirmación hecha aquí.

La lección trata de contratos de interfaz, no de musl ni de OpenSSH

Parchee musl cuando publique una corrección. Parchee OpenSSH, que ya tiene una. Ninguna de las dos instrucciones es la lección de ingeniería real aquí.

El contrato de interfaz de crypt() promete NULL en caso de fallo. La propia implementación de musl de ese contrato no siempre mantiene esa promesa, y la promesa de la que depende por separado la convención "*" de /etc/shadow, que ese carácter específico nunca aparezca como un resultado genuino de crypt(), resultó depender por completo de que la primera promesa se mantuviera. Ningún subsistema está equivocado de forma aislada: la convención "*" de /etc/shadow es anterior a musl y no tiene nada que ver con ella, y la elección de centinela de fallo de musl no tiene nada que ver con OpenSSH ni con los archivos shadow. El error vive enteramente en la brecha entre ambos, en una suposición que nadie había escrito porque simplemente siempre se había cumplido.

CWE-394, la clasificación que realmente lleva este CVE, es "Unexpected Status Code or Return Value", y eso es precisamente lo que es esto. También es un error "de apariencia inofensiva" en dos sentidos distintos a la vez: un campo de shadow "*" se ve inerte por diseño, y el comportamiento de "*" en caso de fallo de musl ya había sido reportado, de forma independiente, en diciembre de 2024, en un contexto completamente distinto, haciendo caer a pam_unix, casi dos años antes de que alguien lo conectara con un bypass de OpenSSH. La misma violación de contrato subyacente, dos consecuencias muy distintas, dependiendo enteramente de qué llamador la tocara.

La versión práctica para cualquiera que mantenga una comparación como esta: un contrato de interfaz es tan fuerte como la disposición de cada llamador a comprobarlo, no tan fuerte como la propia redacción del contrato. Si compara un valor calculado contra uno almacenado, y cualquiera de los dos lados tiene un centinela documentado de "aquí salió algo mal", compruebe si ese centinela podría alguna vez coincidir con el valor de "aquí no hay nada que ver" del otro lado, antes de confiar en absoluto en la comparación. Eso no es una comprobación específica de musl ni de OpenSSH. Es el invariante real que, según resultó, le faltaba a todo este CVE.

Esto es exactamente el tipo de frontera que una revisión de código segura está hecha para encontrar: no el punto obvio de inyección, sino la comparación que a nadie se le ocurrió cuestionar, porque ambos lados siempre se veían limpios por separado. Si su propia infraestructura tiene una ruta de autenticación o comparación a nivel de C que nunca se ha trazado función por función como se trazó esta aquí, esa es precisamente la brecha que una revisión de código a ese nivel existe para cerrar.

Fuentes

¿Necesita que se compruebe esto en su propio producto?

Revisión de seguridad del código

¿Quiere aprender a encontrar vulnerabilidades como esta por su cuenta?

Curso individual de revisión de código seguro
Todos los artículos

08Contacto

¿Va a lanzar algo que trata datos de usuarios?

Envíeme un breve resumen. Respondo en un plazo de tres días con preguntas o un esbozo de propuesta, y el precio se acuerda por escrito antes de empezar.