
Exploite la vulnérabilité de désérialisation rememberMe de Shiro via CVE-2020-2883 sur WebLogic, et enregistre en un clic un memory shell de type filtre pour AntSword.
Ce partage aborde les points suivants :
La sérialisation Java est le processus de conversion d'un objet Java en une séquence d'octets, facilitant ainsi son stockage en mémoire, dans un fichier ou dans une base de données. La méthode writeObject() de la classe ObjectOutputStream permet d'effectuer la sérialisation, c'est-à-dire de convertir un objet Java en séquence d'octets.
La désérialisation Java est le processus de restauration d'une séquence d'octets en un objet Java. La méthode readObject() de la classe ObjectInputStream est utilisée pour la désérialisation.
Prenons un exemple simple, voir le code SerializeAndDeserialize ps : portez une attention particulière au transtypage forcé dans le code```java
package org.chabug.demo;
import org.chabug.entity.Dog; import org.chabug.entity.Person; import org.chabug.util.Serializables;
/* 这个例子是为了证明只要实现了Serializable接口的类都可以被序列化 并且Java内置的几大数据类型也可被序列化,因为他们都继承了Object类 */
public class SerializeAndDeserialize {
public static void main(String[] args) throws Exception {
byte[] bytes;
String s1 = "I'm a String Object....";
bytes = Serializables.serializeToBytes(s1);
Object o1 = Serializables.deserializeFromBytes(bytes);
System.out.println(o1);
String[] s2 = new String[]{"tom", "bob", "jack"};
bytes = Serializables.serializeToBytes(s2);
String[] o2 = (String[])Serializables.deserializeFromBytes(bytes);
System.out.println(o2);
int i = 123;
bytes = Serializables.serializeToBytes(i);
int o3 = (Integer) Serializables.deserializeFromBytes(bytes);
System.out.println(o3);
// 一只名叫woody的狗
Dog dog = new Dog();
dog.setName("woody");
// tom
Person tom = new Person();
tom.setAge(14);
tom.setName("tom");
tom.setSex("男");
tom.setDog(dog);
bytes = Serializables.serializeToBytes(tom);
Person o = (Person) Serializables.deserializeFromBytes(bytes);
System.out.println(o);
}
}
Les types de données intégrés à Java tels que String, Integer, les tableaux, les objets Object, etc. peuvent tous être sérialisés. Nos propres classes Person et Dog peuvent être sérialisées et désérialisées dès lors qu'elles implémentent l'interface Serializable.
## Pourquoi des vulnérabilités apparaissent-elles lors de la désérialisation ?
Regardons un morceau de code : il existe maintenant une classe d'entité malveillante EvilClass```java
package org.chabug.entity;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class EvilClass implements Serializable {
String name;
public EvilClass() {
System.out.println(this.getClass() + "的EvilClass()构造方法被调用!!!!!!");
}
public EvilClass(String name) {
System.out.println(this.getClass() + "的EvilClass(String name)构造方法被调用!!!!!!");
this.name = name;
}
public String getName() {
System.out.println(this.getClass() + "的getName被调用!!!!!!");
return name;
}
public void setName(String name) {
System.out.println(this.getClass() + "的setName被调用!!!!!!");
this.name = name;
}
@Override
public String toString() {
System.out.println(this.getClass() + "的toString()被调用!!!!!!");
return "EvilClass{" +
"name='" + getName() + '\'' +
'}';
}
private void readObject(ObjectInputStream in) throws Exception {
//执行默认的readObject()方法
in.defaultReadObject();
System.out.println(this.getClass() + "readObject()被调用!!!!!!");
Runtime.getRuntime().exec(new String[]{"cmd", "/c", name});
}
}
Dans son readObject, il y a du code exécutant des commandes : Runtime.getRuntime().exec(new String[]{"cmd", "/c", name}), le paramètre name étant la commande à exécuter. Nous pouvons donc construire un objet malveillant, affecter à sa propriété name la commande à exécuter, et lorsque la désérialisation déclenche readObject, cela provoque une RCE. Comme suit```java
package org.chabug.demo;
import org.chabug.entity.EvilClass; import org.chabug.util.Serializables;
public class EvilSerialize { public static void main(String[] args) throws Exception { EvilClass evilObj = new EvilClass(); evilObj.setName("calc"); byte[] bytes = Serializables.serializeToBytes(evilObj); EvilClass o = (EvilClass) Serializables.deserializeFromBytes(bytes); System.out.println(o); } }

Maintenant que nous savons comment la désérialisation peut mener à une RCE, il faut garder à l'esprit qu'en conditions réelles de développement, on n'écrirait pas directement ce genre de code. C'est pourquoi il s'agit de trouver des chaînes d'exploitation. Une vulnérabilité de désérialisation nécessite trois éléments :
1. Point d'entrée de la désérialisation (source)
2. Méthode cible (sink)
3. Chaîne d'exploitation (gadget chain)
Si l'on observe attentivement la sortie de l'image ci-dessus, on constate que non seulement la méthode `readObject` est déclenchée, mais aussi `toString()`, le constructeur sans argument, les méthodes `set` et `get`. Dans la recherche réelle de chaînes d'exploitation, il ne faut donc pas se limiter à la méthode `readObject()`.
C'est alors que nous devons comprendre la **réflexion**. Plus haut, nous avons évoqué le problème de la **conversion de type forcée**. Dans le développement réel, un traitement logique est effectué dans `readObject` ; lorsque le type de données concret de l'objet reçu est inconnu, la réflexion est utilisée pour déterminer et invoquer la méthode appropriée. La réflexion est précisément un moyen essentiel pour nous d'aboutir à une RCE.
## Réflexion Java
Qu'est-ce que la réflexion ? Le terme « réflexion » contient l'idée de « retour sur soi ». Pour l'expliquer, il faut donc partir de son opposé, l'« appel direct ». Regardons le code. Voici ma classe d'entité :```java
package org.chabug.entity;
import java.io.IOException;
public class ReflectionClass {
String name;
public ReflectionClass(String name) {
this.name = name;
}
public ReflectionClass() {
}
public String say() {
return this.name;
}
private void evil(String cmd) {
try {
Runtime.getRuntime().exec(new String[]{"cmd","/c",cmd});
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return "ReflectionClass{" +
"name='" + name + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Écriture normale```java package org.chabug.demo;
import org.chabug.entity.ReflectionClass;