2019.6.16 发出了一则漏洞预警:
https://www.gdcert.com.cn/index/news_detail/W1BZRDEYCh0cDRkcGw最近两天刚好在学习WebService相关知识,这个 axis 组件就是一个SOAP引擎,提供创建服务端、客户端和网关SOAP操作的基本框架,没见过这类漏洞,抱着学习的心态研究下
第一次请求:
org.apache.axis.transport.http.AxisServlet#doPost
->
org.apache.axis.utils.Admin#processWSDD
->
org.apache.axis.AxisEngine#saveConfiguration
第二次请求:
org.apache.axis.transport.http.AxisServlet#doPost
->
freemarker.template.utility.Execute#exec
->
java.lang.Runtime#exec(java.lang.String)
本地环境: jdk1.8_191 、Apache Axis1 1.4 、 Tomcat6
看了几个预警,大多描述的是 “Apache Axis 中的 FreeMarker 组件/插件存在相关漏洞”,我找了半天,没见着 FreeMarker 和 Axis 的配合使用呀emmm,这就让我更好奇了,因为之前也有过 FreeMarker 模板注入 bypass 的文章( https://xz.aliyun.com/t/4846)
难道是未授权访问到了 FreeMarker的模板解析流程?
在 ‘/services/FreeMarkerService’ 这个路径上浪费了许多时间,转头看向 ‘/services/AdminService’ 的未授权
全局搜一下,如下图:
完全和预警通告中的描述吻合,跟进 org.apache.axis.utils.Admin#AdminService 查看如下:
稍微了解过 WebService 的带哥可能此时就明白了,之前的那个 service-config.wsdd 配置文件展示的 service 标签就是一个个 WebService 发布的端口,其中 allowedMethods 则是发布的函数,如上图的 AdminService 函数,我们可以通过 SOAP 的方式去调用服务端的 AdminService 函数,需要构造的请求大致如下:
POST /services/AdminService …
…
他会自动处理我们传递的xml结构参数,上图中 xml[0] 内容是我们完全可控的,跟进 process 函数,如下:
首先调用了 verifyHostAllowd 函数进行验证,其验证内容大致如下:
大意是如果服务端该 WebService 端口的 enableRemoteAdmin 属性为 false 的话,则判断当前访问ip,如果是本机访问则放行,如果是远程访问就抛错
继续跟进 processWSDD 函数,如下:
protected static Document processWSDD(MessageContext msgContext, AxisEngine engine, Element root) throws Exception { Document doc = null ; String action = root.getLocalName(); if (action.equals("passwd")) { [...] } if (action.equals("quit")) { [...] } if ( action.equals("list") ) { [...] } if (action.equals("clientdeploy")) { // set engine to client engine engine = engine.getClientEngine(); } WSDDDocument wsddDoc = new WSDDDocument(root); EngineConfiguration config = engine.getConfig(); if (config instanceof WSDDEngineConfiguration) { WSDDDeployment deployment = ((WSDDEngineConfiguration)config).getDeployment(); wsddDoc.deploy(deployment); } engine.refreshGlobalOptions(); engine.saveConfiguration(); doc = XMLUtils.newDocument(); doc.appendChild( root = doc.createElementNS("", "Admin" ) ); root.appendChild( doc.createTextNode( Messages.getMessage("done00") ) ); return doc; }
上述代码的几个 if 我都省略了,因为那不重要,不过 action 也是我们完全可控的,但是这几个if中的功能代码根本不痛不痒,不能做到 rce 的效果,此时我们能够完全掌控的就是 root 变量,它被带入了 WSDDDocument 构造函数中,跟进去如下:
诶,又对当前标签名作了一次判断,而且看见了 undeployment 和 deployment 这种和 WebService 密切相关的变量名,undeployment 不感兴趣,继续跟进 WSDDDeployment 构造函数:
public WSDDDeployment(Element e) throws WSDDException { super(e); [...] elements = getChildElements(e, ELEM_WSDD_SERVICE); for (i = 0; i < elements.length; i++) { try { WSDDService service = new WSDDService(elements[i]); deployService(service); } catch (WSDDNonFatalException ex) { // If it's non-fatal, just keep on going log.info(Messages.getMessage("ignoringNonFatalException00"), ex); } catch (WSDDException ex) { // otherwise throw it upwards throw ex; } } [...] }
还记得之前的 server-config.wsdd 文件对 service 的描述内容吗,如下:
<service name="AdminService" provider="java:MSG"> <namespace>http://xml.apache.org/axis/wsdd/</namespace> <parameter name="allowedMethods" value="AdminService"/> <parameter name="enableRemoteAdmin" value="ture"/> <parameter name="className" value="org.apache.axis.utils.Admin"/> </service>
如上配置代码,是直接用 service 标签包裹的,所以我们在 WSDDDeployment 构造函数中直接瞄准 service 关键词,上面贴出来的代码中,ELEM_WSDD_SERVICE 意义如下:
首先,这个类型为 Element 的 e 变量是我们从客户端传递的,所以完全可控,在WSDDDeployment 构造函数中,只要 e 的子节点中含有 service 标签就将其还原成 WSDDService 对象,并且调用 deployService 函数,一路跟进到 WSDDService 的 deployToRegistry 函数中,如下:
基本上就没有过多的操作了,我们先停一停,首先这个初始化过程仅仅是做了注册操作,虽然我们构造的参数被注册了,但是目前还没有下一步触发的地方,回到org.apache.axis.utils.Admin#processWSDD 中
如上,我们控制的 service 已经在 wsddDoc 中注册了,最后会调用一此 engine.refreshGlobalOptions 和 saveConfiguration ,这意味着刚刚的初始化操作很有可能会被保存进配置文件中,那么回想下 axis 的 WebService 操作似乎确实是由配置文件控制,那么到下次访问该服务的时候,是不是会刷新我们自己注册的 service 呢?
先下载 Apache Axis 1.4 版本的源码或者是安装包
源码在GitHub上有,安装包在: http://apache.fayea.com/axis/axis/java/1.4/
然后启动项目,直接访问 localhost:8080/servlet/AxisServlet
当前只有两个 WebService 端口,在 WEB-INF/server-config.wsdd 中也只有两个 service 标签,如下:
(其 enableRemoteAdmin 已经被我改成 ture,默认为 false)
那么此时我们随便构造一个POST包,发过去看看能不能新建一个 WebService 端口
访问刚刚的网页:
已经含有报错,此时看看server-config.wsdd 文件中的 service 标签:
如上图,确实是我们自己添加的内容,那么访问一下 /services/a?wsdl
虽然报错了,但是这表明,我们在 POST 自定义配置过后,是可以立刻生效的,这估计也是 Asix 基于配置文件驱动有关。
那么我们现在可以干什么,这就需要对 WebService 有一定的了解了,可以将 WebService 看作一个个微型服务端口,只要有相关配置,可以做到单个函数级别的调用。那么在我们完全可控配置文件的情况下,这就意味着我们可以调用”任意函数”
不能完全做到随心所欲,需要满足 WebService 的规则,比如 端口类 需要有一个 public 的无参构造函数,还要需要当前 ClassLoader 能够找得到指定类,最后也是最麻烦的则是指定函数的参数匹配,没有详细研究这个老框架,不知道复杂类型的传参是否允许
现在我们知道可以在有限条件下调用任意函数,那么如何利用?还有和通告中的 freemarker 有啥关系?
在腾讯云的预警中找到了一句:
Apache AXIS中的freemarker组件中调用template.utility.Execute类时存在远程命令执行攻击
查看 Execute 类:
直接将传递进来的参数作为命令执行的参数,而且参数类型是 List ,那么基本可以确定这个应该可以利用(List在xml结构中可以看作Array结构)
不过似乎少个public无参构造函数?查看一下字节码:
原来只是没有反编译出来而已
那么我们就可以构造两个POST包:
第一个包设置配置文件,将 freemarker.template.utility.Execute 作为一个 WebService 的端口,并且开放 exec 函数,第二个包访问设定的 WebService 端口,并且发送SOAP操作,调用服务端的 freemarker.template.utility.Execute#exec 函数
效果如下:
第一次请求:
第二次请求:
(以上流程我是将freemarker-2.3.23.jar 添加进 WEB-INF/lib 中的,Axis 本身没有这个jar包)
当然也可以在 Axis 自带的 libs 中搜寻利用类,所需条件上文已经阐述过
其实这个漏洞和 freemarker 没有啥必然联系,主要是因为 Axis 的未授权访问,可是默认配置下是不允许远程访问 AdminService 端口的,不过还是学习了 Axis 的处理方式,之前所了解到的 WebService 都是通过注解操作,但其实反过来想想,注解或是配置文件都一样,都需要框架底层自行实现解析流程