JumpServer20210115远程执行漏洞

JumpServer20210115远程执行漏洞

漏洞概述:由于JumpServer的websocket接口未做授权限制,允许攻击者通过精心构造的请求获取日志文件,从日志文件中可获取敏感信息生成token,再利用生成的token通过相关操作API在资产主机上执行任意命令。

JumpServer的github首页能看到官方的紧急BUG修复通知

影响版本:

1
2
3
4
5
< v2.6.2
< v2.5.4
< v2.4.5
= v1.5.9
>= v1.5.3

安全版本:

1
2
3
4
5
>= v2.6.2
>= v2.5.4
>= v2.4.5
= v1.5.9 (版本号没变)
< v1.5.3

修复方案:

将JumpServer升级至安全版本;

临时修复方案:

修改 Nginx 配置文件屏蔽漏洞接口

1
2
/api/v1/authentication/connection-token/
/api/v1/users/connection-token/

Nginx 配置文件位置

1
2
3
4
5
6
7
8
# 社区老版本
/etc/nginx/conf.d/jumpserver.conf

# 企业老版本
jumpserver-release/nginx/http_server.conf

# 新版本在
jumpserver-release/compose/config_static/http_server.conf

修改 Nginx 配置文件实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
### 保证在 /api 之前 和 / 之前
location c {
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

1
2
3
4
5
6
docker方式: 
docker restart jms_nginx

nginx方式:
systemctl restart nginx

修复验证

1
2
3
4
5
$ 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
漏洞已修复

入侵检测

下载脚本到 jumpserver 日志目录,这个目录中存在 gunicorn.log,然后执行

1
2
3
4
5
6
7
8
9
$ pwd
/opt/jumpserver/core/logs

$ ls gunicorn.log
gunicorn.log

$ wget 'https://github.com/jumpserver/jumpserver/releases/download/v2.6.2/jms_check_attack.sh'
$ bash jms_check_attack.sh
系统未被入侵

WS未授权访问日志

源码分析

url:https://githistory.xyz/jumpserver/jumpserver/blob/db6f7f66b2e5e557081cb561029f64af0a1f80c4/apps/ops/ws.py
官方修复时改动的代码段:

1
2
3
4
5
6
def connect(self):
user = self.scope["user"]
if user.is_authenticated and user.is_org_admin:
self.accept()
else:
self.close()

修复前:

1
2
def connect(self):
self.accept()

该类的路由可从源码得知:

1
path('ws/ops/tasks/log/', ws.CeleryLogWebsocket, name='task-log-ws'),

利用

目前看来利用方式限于读log。
task_list
task_id
WS插件:安装地址
图源:JumpServer远程执行漏洞 【只会读取日志】

–update20210119
利用方式:jumpserver的日志目录在/opt/jumpserver/logs/,通过查看gunicorn.log日志,可以获取到user_id、asset_id、system_user_id。

Token获取

源码分析

url:https://githistory.xyz/jumpserver/jumpserver/blob/db6f7f66b2e5e557081cb561029f64af0a1f80c4/apps/authentication/api/auth.py
官方修复时被删除的代码段:

1
2
3
4
def get_permissions(self):
if self.request.query_params.get('user-only', None):
self.permission_classes = (AllowAny,)
return super().get_permissions() # 调用APIView的get_permissions()

可以理解为如果get/post请求带有user-only参数,则该接口任意用户可访问。

利用

漏洞接口:

1
2
xxx.xxx.xxx.xxx/api/v1/users/connection-token/?user-only=1
xxx.xxx.xxx.xxx/api/v1/authentication/connection-token/?user-only=1

这两个接口都可以使用,因为最终都是调用漏洞点UserConnectionTokenApi。
利用方式:
通过日志中获取user_id、asset_id、system_user_id,再构造post请求获取token。
jumpserver相关源码:
UserConnectionTokenApi

代码执行

connection-token接口生成的token作为rest_api凭证使用会报错,需要利用koko完成代码执行。
koko对connection-token的使用方式:
https://github.com/jumpserver/koko/blob/master/pkg/service/urls.go#L13
关于koko

1
2
2019年9月30日,Jumpserver堡垒机发布V1.5.3版本。自V1.5.3版本起,Koko(即基于Go语言开发的SSH客户端)将担任Coco(即基于Python语言开发的SSH客户端)在Jumpserver项目中的角色,后续版本将不会再对Coco进行维护。
Koko是使用Go重构的Unix资产连接组件。

koko利用链分析

  1. urls.go
    1
    TokenAssetURL      = "/api/v1/authentication/connection-token/?token=%s" // Token name
  2. assets.go
    1
    2
    3
    4
    5
    6
    7
    8
    func GetTokenAsset(token string) (tokenUser model.TokenUser) {
    Url := fmt.Sprintf(TokenAssetURL, token)
    _, err := authClient.Get(Url, &tokenUser)
    if err != nil {
    logger.Error("Get Token Asset info failed: ", err)
    }
    return
    }
  3. webserber.go
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    func (s *server) processTokenWebsocket(ctx *gin.Context) {
    // http://*****/koko/ws/token/?target_id=xxxxxxxxxx
    tokenId, _ := ctx.GetQuery("target_id")
    tokenUser := service.GetTokenAsset(tokenId)
    if tokenUser.UserID == "" {
    logger.Errorf("Token is invalid: %s", tokenId)
    ctx.AbortWithStatus(http.StatusBadRequest)
    return
    }
    currentUser := service.GetUserDetail(tokenUser.UserID)
    if currentUser == nil {
    logger.Errorf("Token userID is invalid: %s", tokenUser.UserID)
    ctx.AbortWithStatus(http.StatusBadRequest)
    return
    }
    targetType := TargetTypeAsset
    targetId := strings.ToLower(tokenUser.AssetID)
    systemUserId := tokenUser.SystemUserID
    // 根据token传递的信息开启终端
    s.runTTY(ctx, currentUser, targetType, targetId, systemUserId)
    }
  4. webserber.go
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    func (s *server) websocketHandlers(router *gin.RouterGroup) {
    wsGroup := router.Group("/ws/")

    wsGroup.Group("/terminal").Use(
    s.middleSessionAuth()).GET("/", s.processTerminalWebsocket)

    wsGroup.Group("/elfinder").Use(
    s.middleSessionAuth()).GET("/", s.processElfinderWebsocket)

    wsGroup.Group("/token").GET("/", s.processTokenWebsocket)
    }

Update

20210119-根据JumpServer 从信息泄露到远程代码执行漏洞分析对文章进行bug修复。