反序列化利用链挖掘思路浅析

前言

1
最近审计遇到了一个环境存在反序列化漏洞但是没有利用链,于是尝试挖了下利用链,虽然最后没挖到,但是也对利用链挖掘的思路有了一些了解,在我挖掘的过程中也找了下文章,这方面的文章确实比较少,因此打算写篇文章介绍如何挖掘反序列化利用链,希望能对后来者提供一些帮助。

确定sink

​ 因为我也是第一次接触利用链的挖掘,刚上手也是感觉到无所适从,最好的方法当然是分析已有的利用链,下面是我总结了常见利用链的sink。

1
2
3
4
5
6
7
8
9
10
11
12
AspectJWeaver:FileOutputStream#write
BeanShell1:java.lang.reflect.Method#invoke
C3P0:com.sun.jndi.rmi.registry.RegistryContext#lookup
Click1:java.lang.reflect.Method#invoke
beanutils:java.lang.reflect.Method#invoke
CC1:java.lang.reflect.Method#invoke
CC3:java.lang.reflect.Constructor#newInstance
FileUpload1:FileOutputStream#write
Groovy1:java.lang.reflect.Method#invoke
Hibernate:java.lang.reflect.Method#invoke
JavassistWeld1:java.lang.reflect.Method#invoke
JSON1:java.lang.reflect.Method#invoke

​ 下面我们对sink进行归类

  • 触发FileOuputStream#write写文件操作:实际上比较少,一般也不知道网站路径,所以相对来说也比较鸡肋。
  • 触发JNDI请求:主要是触发javax.naming.Context#lookup实现类的lookup方法,一般在程序中使用JNDI相对来说也比较少,因此只有少数的框架有这种利用链。
  • 触发Method#invoke调用:一般挖掘反序列化利用链都是基础库,多多少少都会使用反射,但是并不是所有的反射都可以利用的。一般来说要么我们可以通过控制参数或者属性才能完成调用。这里要注意,如果是通过控制参数来完成反射调用,那么这个实现类是不需要实现Serializable接口的。但是如果我们要通过控制属性来完成任意方法的调用,则实现类必须要实现Serializable接口,除非属性的填充是在反序列化的过程中完成的,比如可以通过构造方法填充属性,在反序列化的过程中会调用构造方法帮我们完成赋值。

​ 通过上面的总结不难看出大部分的利用链都是以Method#invoke为终点的,同样以Method#invoke作为sink,我觉得其实还可以再进行一些细化。

  • 通过控制属性或者方法传参调用invoke任意方法:CC1
  • 通过控制属性或方法传参invoke调用任意类的的Getter方法:beanutils、Hibernate
  • 还有一种就比较复杂,虽然本质上也是调用了invoke,但是这些框架本身提供了一些代码执行的能力,还要对框架的代码执行的原理比较清楚才能挖到调用链:BeanShell1Groovy1Click1Jpython
  • InvocationHandler的实现类中的Invoke:这里的invoke操作的method需要是我们可以从属性中获取的,而不是直接传入的方法名称。具体可以参考JDK7那条利用链。

失败的实践

InvocationHandler

InvocationHandler实现类要作为反序列化链中的sink的条件是需要实现Serializable,原因是我们这里想要通过控制属性来获取Method

​ 举个栗子,在jdk中有个java.beans.EventHandler,它的Method的获取是通过targetaction属性控制的,所以我们只要通过控制这两个属性就可以实现利用。

image-20220525201746027

image-20220525201924121

​ 但是由于EventHandler没有实现Serializable接口,因此非常遗憾的不能作为我们的利用链。

​ 下面是tabby的挖掘语法,排查了下大部分都没有实现Serializable接口

1
2
3
4
5
match (m1:Method)<-[:HAS]-(cls:Class)-[:INTERFACE|EXTENDS*]
->(cls1:Class {NAME:"java.lang.reflect.InvocationHandler"})
match (m1:Method)-[r:CALL]->(m2:Method{NAME:"invoke"})
return m1.CLASSNAME,m1.NAME,m1.PARAMETERS

image-20220525202420043

JNDI

​ 下面我们寻找JNDI的sink

  • javax.naming.Context#lookup
  • java.naming.InitialContext#lookup
  • javax.naming.Context#bind
1
2
3
4
match (m2:Method)<-[:HAS]-(cls:Class)-[:INTERFACE|EXTENDS*]
->(cls1:Class {NAME:"javax.naming.Context"})
match (m1:Method)-[r:CALL]->(m2:Method{NAME:"lookup"})
return m1.CLASSNAME,m1.NAME,m1.PARAMETERS

image-20220520141521808

​ 调用JNDI操作的本来就比较少,基本上都不可控或者找不到调用,因此JNDI的利用也只能放弃。

Invoke

​ Invoke的调用比较多,可以直接通过下面的语法挖调用链,然后一步步排除加黑名单排除。

1
2
3
4
5
 match (source:Method) where source.NAME in ["compare","compareTo","hashCode"]
match (m1:Method)<-[:HAS]-(cls:Class)-[:INTERFACE|EXTENDS*]->(cls1:Class {NAME:"java.io.Serializable"}) where m1.NAME="invoke"
call apoc.algo.allSimplePaths(m1, source, "<CALL|ALIAS",10) yield path
where none(p in nodes(path) where p.CLASSNAME in ["cn.hutool.core.util.ReflectUtil"])
return * limit 10

​ 也可以先通过下面的语法找到合适的sink再去看调用。

1
2
3
4
match (m1:Method)<-[:HAS]-(cls:Class)-[:INTERFACE|EXTENDS*]
->(cls1:Class {NAME:"java.io.Serializable"})
match (m1:Method)-[r:CALL]->(m2:Method{NAME:"invoke"})
return m1.CLASSNAME,m1.NAME,m1.PARAMETERS

image-20220525203402659

​ 下面是几个失败的调用链挖掘。

JFinalJson#beanToJson

​ 直接控制model为TemplatesImpl对象就可以完成调用。

image-20220525112010908

​ 下面我们找beanToJson的调用

image-20220525112423398

​ 最终定位到com.jfinal.json.JFinalJson#toJson,但是JFinalJson不可序列化,所以要么找到可以在反序列化过程中实例化JfinalJson的点,要么就不可以利用了。

image-20220525113442054

GetterMethodFieldGetter#get

target可以通过参数直接控制但是method需要通过属性控制,所以需要找调用链中会不会初始化GetterMethodFiledGetter

image-20220525122552266

​ 继续看上层调用,只有在com.jfinal.template.expr.ast.Field#eval中调用了。而target也是由eval的返回结果决定的。

image-20220525122947803

​ 再通过tabby找调用,分析了一大圈,感觉好像不可控。

image-20220525125400871

ReflectUtil#invoke

​ 可以直接通过控制传参调用任意方法,所以不需要实现Serializable

image-20220525204758842

​ 只有execute和clone两个点,但是clone不能控制函数名称。execute似乎是计划任务,也不太好调用

1
2
3
4
match (source:Method) where source.NAME in ["compare","compareTo","hashCode"]
match (m1:Method) where m1.NAME="invoke" and m1.CLASSNAME="cn.hutool.core.util.ReflectUtil"
call apoc.algo.allSimplePaths(m1, source, "<CALL|ALIAS",10) yield path
return * limit 10

image-20220525170520529

cn.hutool.core.bean.DynaBean

​ 实现了Serializable接口,并且可以反射调用Getter方法。

image-20220520160708338

​ 我们构造一个TemplatesImpl进行测试发现获取不到outputProperties的getter方法。

image-20220520160755090

​ 经过调试发现获取getter方法时没有去掉_所以匹配不到,这个点真是最可惜的点了。

image-20220520170249207

总结

​ 找source的过程就不讲了,因为我连合适的sink都没找到。