0×01 前言
放假前看到很多文章對這個漏洞進行分析復現,又因為過年期間的特殊情況,實在是無聊至極,所以自己也來學習一下,順便惡補一下反序列化漏洞的知識。這篇文章記錄了自己的一些想法以及相關的知識點,方便自己日後忘記可以重新拾起。第一次寫文章有不好的,希望大家見諒。
0×02 環境搭建
由於部分cas版本的加密函數不同有相應的變化,因此想要按照此文章來複現漏洞的話還是選擇和我一樣的版本。
<code>jdk8u144(不一定完全一樣)ApereoCas-4.1.5/<code>
下載CAS-Overlay-Template
github鏈接:https://github.com/apereo/cas-overlay-template/tree/4.1
github上有詳細的部署操作,這裡要注意要修改pom.xml文件cas的版本:
<code><cas.version>4.1.5/<cas.version>/<code>
編譯完後,會在target目錄生成一個cas.war的war包,將該war包放在tomcat的web目錄上,啟動tomcat即可通過http://localhost/cas訪問example。
成功部署後:
0×03 漏洞分析
該漏洞存在於登錄的execution參數,抓包發現該參數值應該是加密過的,故要知道對應的加密方法以及處理過程才行。
web.xml
查看登錄url對應的servlet可知道交給了Spring的DispatcherServlet處理了,配置文件為/WEB-INF/cas-servlet.xml
從springmvc的執行流程圖(網上找的)可以知道只要找到對應的處理器適配器,就能找到對應的處理器。
cas-servlet.xml
全局搜索login字眼,看到loginHandlerAdapter適配器,處理器的類名為org.jasig.cas.web.flow.SelectiveFlowHandlerAdapter
org.jasig.cas.web.flow.SelectiveFlowHandlerAdapter該類繼承FlowHandlerAdapter類,登錄時調用繼承類的handler方法:
<code>//org.springframework.webflow.mvc.servlet.FlowHandlerAdapter#handlepublic ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { FlowHandler flowHandler = (FlowHandler)handler; this.checkAndPrepare(request, response, false); String flowExecutionKey = this.flowUrlHandler.getFlowExecutionKey(request); if (flowExecutionKey != null) { try { ServletExternalContext context = this.createServletExternalContext(request, response); FlowExecutionResult result = this.flowExecutor.resumeExecution(flowExecutionKey, context); this.handleFlowExecutionResult(result, context, request, response, flowHandler); } catch (FlowException var11) { this.handleFlowException(var11, request, response, flowHandler); } } else { try { String flowId = this.getFlowId(flowHandler, request); MutableAttributeMap<object> input = this.getInputMap(flowHandler, request); ServletExternalContext context = this.createServletExternalContext(request, response); FlowExecutionResult result = this.flowExecutor.launchExecution(flowId, input, context); this.handleFlowExecutionResult(result, context, request, response, flowHandler); } catch (FlowException var10) { this.handleFlowException(var10, request, response, flowHandler); } } return null; }/<object>/<code>
其中flowExecutionKey通過getFlowExecutionKey方法獲取參數execution的值
<code>String flowExecutionKey = this.flowUrlHandler.getFlowExecutionKey(request);/<code>
flowExecutionKey作為參數傳入resumeExecution方法,跟進函數。在第91行對flowExecutionKey值的格式進行判斷,通過”-”分割字符串為兩部分uuid以及base64編碼flowstate,因此格式不滿足的話是無法繼續走下去的。
跟進第96行getFlowExecution。
<code>public FlowExecution getFlowExecution(FlowExecutionKey key) throws FlowExecutionRepositoryException { if (!(key instanceof ClientFlowExecutionKey)) { throw new IllegalArgumentException("Expected instance of ClientFlowExecutionKey but got " + key.getClass().getName()); } else { byte[] encoded = ((ClientFlowExecutionKey)key).getData(); try { ClientFlowExecutionRepository.SerializedFlowExecutionState state = (ClientFlowExecutionRepository.SerializedFlowExecutionState)this.transcoder.decode(encoded); FlowDefinition flow = this.flowDefinitionLocator.getFlowDefinition(state.getFlowId()); return this.flowExecutionFactory.restoreFlowExecution(state.getExecution(), flow, key, state.getConversationScope(), this.flowDefinitionLocator); } catch (IOException var5) { throw new ClientFlowExecutionRepositoryException("Error decoding flow execution", var5); } } }/<code>
在第105行對之前base64解碼後的encoded進行解密,跟進解密函數this.transcoder.decode(encoded)
可以看出在第83行對密文進行解密,經過一系列的操作後在99行進行反序列化,觸發漏洞。可以看出調用的decode方法屬於EncryptedTranscoder類,該類還定義的加密方法encode,這裡可以直接生成惡意對象,直接調用org.jasig.spring.webflow.plugin.EncryptedTranscoder#encode生成加密字節數組後base64,加上”uuid-”構成execution的值。
整個調用棧
0×03 構造payload
默認環境的jar包中有commons-collections4-4.0.jar,直接使用ysoserial生成payload,這裡記得要將payload的特殊符號進行url編碼。
演示結果
成功執行系統命令
0×04 構造回顯payload
看了大佬的分析,知道可以回顯,文章提及到org.springframework.webflow.context.ExternalContextHolder.getExternalContext()方法可以獲取到上下文關聯信息,然後通過getNativeRequest()方法獲取request對象通過getNativeResponse()方法獲取response對象。同時提及到org.springframework.cglib.core.ReflectUtils.defineClass().newInstance();加載payload。我的猜測大佬的想法是通過defineClass從byte[]還原出一個Class對象,該惡意對象主要是執行命令,獲取response對象,將執行命令後的結果通過response對象的輸出流輸出。在利用commons-collections1是發現ReflectUtils利用不了,因為構造方法為private,要設置setAccessible為true。因此使用commons-collections2的話,實際就不需要這麼麻煩用defineClass來加載payload了,直接在利用類裡面寫就好了。
<code>//org.springframework.cglib.core.ReflectUtilsprivate ReflectUtils() { }//org.springframework.cglib.core.ReflectUtils#defineClass public static Class defineClass(String className, byte[] b, ClassLoader loader) throws Exception { Object[] args = new Object[]{className, b, new Integer(0), new Integer(b.length), PROTECTION_DOMAIN}; Class c = (Class)DEFINE_CLASS.invoke(loader, args); Class.forName(className, true, loader); return c; }/<code>
這裡看看ysoserial的commons-collections2的構造惡意對象的主要方法。這裡使用javassist,第66行獲取要操作的類,第75行在該類的構造方法中插入代碼,因此這裡只要修改該類ysoserial.payloads.util.Gadgets.StubTransletPayload的構造方法為執行系統命令,並修改response的輸出流。大家可以直接修改ysoserial的源碼並重新編譯,我這裡為了方便直接用了網上的payload改了一下。
演示結果
0×04 最後
文章中存在錯誤的地方希望大佬們斧正,多多指點。通過這次的分析,其實學到很多東西的,包括commons-collections利用鏈、ysoserial源碼分析。最後,在新的一年裡希望大家身體健康,萬事如意。
0×05參考鏈接:
https://www.00theway.org/2020/01/04/apereo-cas-rce/
https://xz.aliyun.com/t/2041
https://xz.aliyun.com/t/4711
https://xz.aliyun.com/t/7031
https://xz.aliyun.com/t/7032
http://www.vuln.cn/6295
https://www.freebuf.com/vuls/170344.html
https://mp.weixin.qq.com/s/FSMNIkVws3eqDdaheiCviA
https://www.cnblogs.com/gxc6/p/9544563.html
閱讀更多 網絡安全晴雨表 的文章