这是一篇拖更一年的文章。生活所迫 懒驴拉磨。去年实战遇到该系统,由于这套系统大多数据通信方式都是采用了Java的序列化数据传输,所以就导致了关于它的反序列化漏洞层出不穷,这次我们要考古的是一个另类的反序列化利用,一波三折,折了又折。
首先看一个熟悉的反序列化漏洞的利用接口:
/servlet/~aert/com.ufida.zior.console.ActionHandlerServlet
代码位置:
代码处会将传入的数据流进行GZIP的解码然后调用readObject
方法进行反序列化,经典的反序列化漏洞。但是这个位置很早就被披露出来并且当时目标已经修复该漏洞。
顺着这个接口往下看,会注意到一个关于ActionExecutor.exec()
这个方法的调用
跟进去大概看了一下关于exec
方法的代码,有关于反射调用的动作。
上面invoke
方法所需要的 类、方法、参数、都是我们传参可控的。
参数对应如下:
String 类型的msg 对应的就是actionName也就是类名
String 类型的methodName 对应的是 methodName 方法名
Object 类型的paramter 对应的是 paramter 参数对象
我们本地构造的时候同样需要按照反序列化的顺序进行序列化,也就是:
oos.writeObject(msg);
oos.writeObject(methodName);
oos.writeObject(parameter);
oos.writeObject(currentLanguage);
oos.writeObject(logModule);
当我们传入的类名和方法名不为空的时候就会实例化我们传入的类对象。
这里会判断我们传入的paramter
是否为数组类型 ,最后会和类名 方法名 参数数量拼接存到map_method
这个map中,然后通过这个key
去map中取
这里有个问题就是,如果ActionExecutor.exec
从来没有调用过,那么map_method
就是空的,这里可以看到他是一个静态属性
那么这里m
一定是null的
如果方法没有在map中,进入if
判断,如果pararmter
是数组类型的就会动态赋值给paramClzs
,获取方法对应参数类型数组,如果paramter
不是数组类型的会直接获取到一个参数类型为Object.class
的方法,由于Java特性,也就会导致乱调用
,只要是参数数量为 1 的方法都有可能被调用然后存到method_map
这个静态map中。恰巧这个map中的key存的是类名+方法名+方法参数数量
.
这里假设 A类有两个方法exec(String string)
,另一个方法exec(int int)
,那么此时我们如果传入的paramter
参数不是数组类型的,就有可能调用两个方法的任意一个,这就有点看运气了,由于反射调用之前会在method_map
中查找对应的方法,所以一个类中同名方法且参数数量相同只能加载一个。
如果获取不到我们传入的方法就会遍历我们传入类对象所有的方法,直到找到为止,否则抛出异常
如果我们传入的方法可以被正确找到就会put 到上面提到的map_method
这个静态的map中
此时m
方法不为null,就会来到后面的 if 判断,如果这个方法是有参的就会调用if里面的有参方法,反之就会调用无参的方法
到这里调用链就分析结束了
结合上面的分析我们在构造payload的时候需要注意,我们需要一个类中存在可以RCE的方法,并且该方法不能有同名如果同名参数数量不能为1
的情况
在这套系统的源码中找到freemarker.template.utility.Execute
这个类的exec
方法传入的参数是一个,正好满足我们前面的条件要求。
POC测试效果如下:
其实直到上一步可以调用到Runtime.exec
执行命令就可以了,但是实战环境情况比较复杂。
一种是延续这种利用方式,命令执行写文件落地JSP到Web目录,这个漏洞会默认把文件落地到网站根目录可以直接解析JSP问题不大。但是如果不知道文件落地位置和Web的绝对路径,再或者目标机器不出网+杀毒软件&RASP拦截命令执行函数,这种利用方式就捉襟见肘了
恰巧作者在项目中就遇到一个杀软拦截命令执行函数,机器还不出网的环境,exec这个函数就满足不了了。
回头重新看代码发现问题所在点
之所以出现一个类中同数量参数的方法乱调用
的问题是来自于这里Class[] paramClzs = new Class[]{Object.class}
,如果我们可以控制paramter
的数组类型,我们就可以控制方法的参数类型。介于上面杀毒软件&RASP会拦截命令执行函数的问题,这次选择代码执行的函数。
这套系统恰好存在bsh,直接调用bsh.Interpreter
的eval
方法执行JS代码。但是存在两个不同参数类型的eval,如果直接把paramter
设置为String
类型,最后可能会加载到参数为Reader
的方法。
先利用String数组将参数为String 的eval方法存到method_map
中,然后第二个包正常将paramter
设置String类型即可,如果不想这么麻烦的话,可以直接加载String类型的参数,万一调用到Reader参数的eval方法可以直接把paramter
换成new StringReader("xxxxx")
以下是测试加载回显。
作者:Ha1ey@深蓝攻防实验室