Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
Herramientas/GitHubGitHub/colmmacc/cve-2022-3602
Análisis de VulnerabilidadesExplotaciónCriptografíaAnálisis de BinariosPapers e InvestigaciónAprendizaje y Educación
GitHubcolmmacc/cve-2022-3602

CVE-2022-3602

Ver Repositorio
16930hace 3 añosRevisado por Kitploit

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

CVE−2022-3602

¿Qué es esto?

Este documento y repositorio es un informe técnico de CVE−2022-3602, un problema de desbordamiento de búfer en punycode dentro de OpenSSL. Es un «anti-POC» (el problema no parece explotable) pensado para quienes mantienen sus propias compilaciones de OpenSSL y para los mantenedores de compiladores.

Hay un CVE separado en la misma versión, CVE-2022-3786, que también provoca desbordamientos de búfer, pero en ese caso un atacante no puede controlar el contenido. Aquí no hay reproducción para ese problema, pero ese problema puede provocar una denegación de servicio debido a una caída.

Las caídas y los desbordamientos de búfer nunca son buenos y, si estás usando OpenSSL 3.0.x, es prudente actualizar lo antes posible.

No dudes en informar de cualquier error u omisión mediante GitHub issues o pull-requests.

¿Cuál es el problema?

Hay un error off-by-one en la forma en que ossl_punycode_decode maneja la decodificación punycode que da como resultado un desbordamiento de 4 bytes. Este problema solo es alcanzable cuando OpenSSL procesa una cadena de certificados y requiere dos condiciones. En primer lugar, un certificado CA o intermedio de una cadena debe contener un campo name-constraint que use punycode.

root@kitploit:~
nameConstraints = permitted;email:xn-maccrthaigh-n7a.com

En segundo lugar, el certificado hoja debe contener un campo otherName de SubjectAlternateName (SAN) que especifique una cadena SmtpUTF8Mailbox.

root@kitploit:~
otherName = 1.3.6.1.5.5.7.8.9;UTF8:[email protected]

Al activarse, el punycode del campo nameConstraints, pero no el del campo otherName, será procesado por el análisis de punycode vulnerable de OpenSSL.

¿Cómo de fácil es activar este problema?

David Benjamin y Matt Caswell determinaron que la comprobación de nameConstraint se produce después de la validación ordinaria de la cadena de certificados y de la verificación de firmas. Para la mayoría de las aplicaciones, esto significa que el problema no se puede activar con un certificado autofirmado o una cadena no válida.

Ten en cuenta que las aplicaciones s_client y s_server de openssl están pensadas para depuración y no detienen el procesamiento cuando una cadena no es válida.

Una CA o un intermedio de confianza tendrá que contener la carga maliciosa y también tendrá que haber firmado el certificado hoja que activa el problema.

Puede haber algunos entornos en los que las partes no confiables sean las CA o los intermedios, por ejemplo un servicio de hosting que admita CA privadas proporcionadas por el cliente, pero esto no es común.

¿El problema conduce a la ejecución remota de código?

Para muchas aplicaciones, la respuesta será «no», debido a cómo el compilador ha dispuesto la pila y a la presencia de otras protecciones como stack canaries / stack cookies, relleno, PIE, FORTIFY_SOURCE.

El problema sí provoca un desbordamiento de 32 bits en la pila. Esto no es suficiente para ejecutar directamente shellcode, pero puede ser suficiente para alterar el flujo de control de una aplicación. Por ejemplo, saltar a shellcode incrustado en una cadena de certificados X509 puede ser posible si estos datos también se copian en la pila en una ubicación ejecutable.

En todas las plataformas Linux que he probado, el desbordamiento ocurre en el relleno y es inofensivo. En teoría, un compilador puede distribuir las variables de modo que el desbordamiento ocurra en una de las otras variables de la función ossl_a2ulabel.

Dependiendo de la inserción en línea (inlining), la lista completa de variables presentes es:

root@kitploit:~
outptr, inptr, size, result, tmpptr, delta, seed, utfsize

y ninguna me parece que ofrezca una vía obvia para la escalada de privilegios o un control interesante.

He adjuntado un tarball con herramientas que se pueden usar para crear reproducciones y desbordamientos con el mayor control posible sobre los cuatro bytes. La cadena de reproducción de referencia (xn--ww90271...aaaa) desborda los cuatro bytes con los valores 0xFF 0x0F 0x0F 0x0F. Si eso no hace fallar una aplicación, es posible (¿probable?) que esa aplicación no sea vulnerable.

¿Cómo puedo reproducir este problema?

El script de shell run-poc se puede usar para generar una cadena de certificados maliciosa. Un certificado CA malicioso se genera a partir de ca.cnf, y un certificado hoja desencadenante se genera a partir de leaf.cnf.

El certificado CA usa la siguiente carga de referencia:

xn--ww902716aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Se puede usar un script de Python para generar otras cadenas punycode para diferentes cargas.

Al ejecutarse, run-poc lanzará un cliente y un servidor openssl e intentará explotar el problema diez veces.

Un OpenSSL vulnerable probablemente fallará. Eso no significa que la versión de OpenSSL sea vulnerable a un RCE, ya que los stack canaries y las protecciones de stack cookies también suelen provocar una caída (más segura) de la aplicación. Ten en cuenta también que esto no cambia en nada la gravedad del otro CVE de la misma versión.

¿Cómo funciona este problema?

Obtener un control casi total de los cuatro bytes de desbordamiento es sorprendentemente delicado y requiere explotar el decodificador de punycode de OpenSSL con punycode no estándar / no válido. El tarball adjunto contiene un script que puede construir una cadena que maneja ese matiz. A continuación se explica cómo funciona.

Preparando el escenario

El problema de seguridad está en ossl_punycode_decode()

root@kitploit:~
int ossl_punycode_decode(const char *pEncoded, const size_t enc_len,
                         unsigned int *pDecoded, unsigned int *pout_length)

ossl_punycode_decode se invoca desde ossl_a2ulabel. El búfer pEncoded es un búfer de tamaño más o menos arbitrario que proviene de una cadena de certificados X509. Es la parte que viene después de cualquier "xn--" en un campo nameConstraint. Consulta la [reproduction] para ver cómo reproducir una cadena de certificados de este tipo.

pDecoded es un array de unsigned int con tamaño LABEL_BUF_SIZE. LABEL_BUF_SIZE es 512 y, en la mayoría de las plataformas, un unsigned int tendrá 4 bytes de ancho. Por lo tanto, en la mayoría de las plataformas, pDecoded tiene una longitud de 2048 bytes.

La escena

Dentro de ossl_punycode_decode(), el quid del problema es esta comprobación de longitud incorrecta:

root@kitploit:~
 if (written_out > max_out)

max_out corresponde a *pout_length, que siempre es 512. Y written_out lleva la cuenta de cuántos unsigned int se han escrito en pDecoded. Debido a que written_out se incrementa después, solo tras escribir, esta comprobación defectuosa permite escribir 513 unsigned int en pDecoded. El resultado final se parece a esto ...

root@kitploit:~
pDecoded = [ ... , 'X , 'Y' , 'Z' ] 'P'
// Indices         509   510   511

Aquí, según la convención de C, los índices están basados en cero, por lo que la ranura número 511 es el elemento 512 del array. 'P' es una carga de cuatro bytes que se ha colocado fuera de los límites, más allá del espacio asignado en la pila para el búfer buf en ossl_a2ulabel(), que es a lo que apunta pDecoded.

Cuatro bytes son un desbordamiento pequeño, y no son suficientes para llevar un nop-sled ni para ejecutar directamente shellcode, pero sí para alterar el flujo de control de una aplicación. Por ejemplo, saltar a shellcode incrustado en una cadena de certificados x509 puede ser posible, dependiendo de cómo se almacenen estos datos (o fragmentos copiados de estos datos) y de si esa memoria es ejecutable. Sin embargo, todavía hay más dificultades para un posible atacante.

En primer lugar, el relleno y la alineación de la pila por parte del compilador, o defensas como los stack canaries, pueden hacer que cualquier explotación sea completamente imposible.

En segundo lugar, solo hay una ruta hacia ossl_punycode_decode() y esa ruta usa un búfer en la pila. Esto hace poco probable que el problema pueda usarse para desbordamientos concurrentes de 4 bytes en distintas ubicaciones de memoria.

Decodificación de punycode

Las cadenas punycode básicamente tienen dos formas. Una es xn--c1yn36f (點看) y otra es xn--maccrthaigh-n7a (maccárthaigh). La parte que viene después del último delimitador - es una codificación bootstring en base 36 de cualquier punto de código Unicode que no sea ascii ordinario básico, junto con la posición de la cadena en la que insertarlos. Lo importante por ahora es que el proceso de decodificación en ossl_punycode_decode() produce dos valores. Uno es 'n', que es el valor de punto de código unsigned int que se insertará, y el otro es 'i', que es la posición en el búfer donde insertarlo.

La escritura puede ocurrir de dos maneras diferentes. Si i está en algún lugar del medio de la cadena, hay un memmove() que primero «hace espacio» copiando todo una posición a la derecha:

root@kitploit:~
memmove(pDecoded + i + 1, pDecoded + i,
       (written_out - i) * sizeof *pDecoded);

y luego escribe n en el espacio que acaba de hacer:

root@kitploit:~
 pDecoded[i] = n;

si i está al final de la cadena, el memmove() no tiene efecto porque el último parámetro será 0. La otra línea se convierte en una simple adición al final.

Ahora veremos las tres formas diferentes de hacer llegar una carga 'P' a la posición de desbordamiento y por qué surgen las restricciones.

Método 1 - desbordamiento ascii

La forma más sencilla de activar el desbordamiento es crear una cadena punycode que tenga 511 caracteres ascii y dos caracteres no ascii. La codificación punycode de una cadena de 513 caracteres, como "ÁÁAAAAAAAA...AAA", serviría. En este caso, lo que sucederá es que, cuando written_out sea 510, tendremos un búfer distribuido así ...

root@kitploit:~
pDecoded = [ 'A' , 'A' ,  ... , 'A' , 'A',     ]
// Indices    0     1     ...   509   510  511

Estos son solo los caracteres ascii básicos que se han copiado. Luego analizamos la bootstring de punycode e insertamos un 'Á' en la posición 0. Aunque podría ser cualquier posición entre 0 y 511 inclusive.

root@kitploit:~
pDecoded = [ 'Á' , 'A' ,  ... , 'A' , 'A', 'A' ]
// Indices    0     1     ...   509   510  511

luego repetimos esto:

root@kitploit:~
pDecoded = [ 'Á' , 'Á' ,  ... , 'A' , 'A', 'A' ] 'A'
// Indices    0     1     ...   509   510  511   512

esto hará que el 'A' ascii ordinario se desborde al ser «movido». La carga de cuatro bytes en este caso pasa a ser 0x00 0x00 0x00 0x41. Como veremos, debido a cómo funciona punycode, esta es la única forma de expresar cualquier valor con un byte final en el rango ascii.

Necesitamos usar dos caracteres no ascii porque hay una comprobación de límites correcta sobre el número de caracteres básicos, por lo que este tiene que ser menor que 512.

Una restricción adicional de que el valor del último byte no pueda ser 46 surge porque ossl_punycode_decode() se llama sobre la parte de una cadena que precede a un carácter literal .. Punycode está pensado para etiquetas de dominio, que no pueden tener puntos.

Método 2 - desbordamiento no ascii directo

La siguiente forma más simple de activar el desbordamiento es crear una cadena de 513 caracteres con un carácter no ascii al final del todo. Algo como "AAAAAAAAAA...AAÁ". En este caso, para nuestros dos últimos pasos tendremos:

pDecoded = [ 'A' , 'A' , ... , 'A' , 'A' ] // Indices 0 1 ... 510 511

y

root@kitploit:~
pDecoded = [ 'A' , 'A' ,  ... , 'A' , 'A' ] 'Á'
// Indices    0     1     ...   510   511

el carácter no ascii irá directamente a la posición de desbordamiento. El analizador de punycode de OpenSSL no exige que el valor de desbordamiento sea realmente un carácter Unicode válido. Es más o menos un proceso de decodificación binario. Pero los matices de la decodificación de punycode hacen que el método 2 no sea tan flexible como podría parecer a primera vista.

En punycode, los valores n e i se codifican ambos como un único entero de longitud variable que luego se codifica en ascii usando base36. Puede parecer imposible codificar dos números no relacionados como un solo entero, pero el truco inteligente de punycode es usar la longitud de la cadena (hasta ahora) como un campo oculto.

Por ejemplo, supongamos que tenemos una cadena punycode con 4 caracteres básicos y uno no básico, como AAÁAA. Primero se representará solo con los caracteres básicos ... AAAA. El valor Unicode de 'Á' es 225 y su posición en la cadena es 2. El truco es multiplicar el valor por la longitud más uno y luego sumar la posición. Así que se convierte en ((225 * (4 +1)) + 2), que es 1127, y así es como se codifica (en base 36 de longitud variable).

Para decodificar, se hace al revés. 1127 / 5 es 225 y 1127 % 5 es 2. Así es como se recuperan dos números a partir de uno. Pero observa que cuanto más larga se vuelve la cadena, más restringido está el tamaño que puede tener el valor; de lo contrario, el múltiplo no cabrá en un unsigned int. En general, si la cadena tiene M caracteres de longitud, se pierden log M bits de ancho del valor.

Para cuando se maneja el entero 512, se pierden 9 bits de ancho. Con el método 2, el valor más alto que podría tener una carga aparentemente de 32 bits es en realidad 2^23. Ni siquiera tres bytes completos. El método 2 es subóptimo.

Método 3 - stuffing

Para recuperar 4 bytes de control, la forma más eficiente es repetir el carácter de la carga una y otra vez. Hasta ahora he omitido otros dos detalles relevantes sobre cómo se maneja punycode.

El primer detalle es que los caracteres no ascii no se codifican en el orden de la cadena, sino que se codifican en orden ascendente de valor. La cadena "ÉÁ" terminará codificándose como «Á en la posición 1, É en la posición 0» porque Á tiene un valor más bajo (225) que É (233).

El segundo detalle es que los caracteres no ascii no se codifican como sus valores literales, sino como un delta relativo al valor decodificado más recientemente. Dado que el primer valor no tiene un valor previo con el que ser relativo, hay un punto de partida fijo (hard-coded) de 128.

Estos pequeños matices hacen que punycode sea muy eficiente en espacio, pero también significan que un carácter no ascii simplemente no se puede decodificar a un valor menor que 128. El delta más pequeño es 0 y no hay forma de expresar un delta negativo. Así que, si quieres un número menor que 128, tienes que usar el método 1.

También significa que la mejor estrategia para tener el mayor control posible sobre la carga es hacer que la carga sea el único valor de toda la cadena, ya que así obtenemos todo el ancho disponible desde su lugar en la posición 0 de la codificación. La cadena que codificas termina teniendo este aspecto;

root@kitploit:~
 [ 'P', 'P', ... 'P', 'P', 'P' ]
    0    1       510  511  512

que OpenSSL decodificará como ...

root@kitploit:~
pDecoded = [ 'P', 'P', ... 'P', 'P' ] 'P'
              0    1       510  511   512

con P en la posición de desbordamiento, y capaz de representar cualquier valor entre 128 y (2^32 - 1).

Todo esto requiere un codificador de punycode no estándar, y he incluido un script que puede crear una carga usando el método 1 o el método 3 según sea necesario.

Mini-FAQ:

Aparte de actualizar OpenSSL, ¿hay otras mitigaciones?

Las cadenas de certificados se transmiten en texto claro en la mayoría de los entornos, y una cadena maliciosa podría bloquearse rechazando las conexiones TCP que contengan un NID 1.3.6.1.5.5.7.8.9 codificado en DER en un campo OtherName de SubjectAlternateName.

Desafortunadamente, este campo podría dividirse arbitrariamente entre dos o más paquetes, y realmente se necesita algún tipo de buscador de patrones con estado para bloquearlo. Los certificados también pueden comprimirse, pero OpenSSL 3.0.x no admite la compresión de certificados en este momento.

Además, con TLS1.3, las cadenas de certificados de cliente se cifran en el cable, y las versiones anteriores de TLS admiten cadenas de certificados cifradas al renegociar una conexión existente. Esto a veces se hace para la autenticación de certificados iniciada por el servidor. Un filtro de red no será efectivo en esos casos.

¿Cómo puedo saber si estoy usando openssl 3 en un binario enlazado estáticamente?

root@kitploit:~
 readelf -a [binary] | grep -i ossl_punycode_decode

buscará la función vulnerable en un binario enlazado estáticamente. Solo OpenSSL >= 3.0 contiene esta función.

Descargar herramienta