JumpServer20210115远程执行漏洞
JumpServer20210115远程执行漏洞
漏洞概述:由于JumpServer的websocket接口未做授权限制,允许攻击者通过精心构造的请求获取日志文件,从日志文件中可获取敏感信息生成token,再利用生成的token通过相关操作API在资产主机上执行任意命令。
JumpServer的github首页能看到官方的紧急BUG修复通知
影响版本:
1 | < v2.6.2 |
安全版本:
1 | >= v2.6.2 |
修复方案:
将JumpServer升级至安全版本;
临时修复方案:
修改 Nginx 配置文件屏蔽漏洞接口
1 | /api/v1/authentication/connection-token/ |
Nginx 配置文件位置
1 | # 社区老版本 |
修改 Nginx 配置文件实例
1 | ### 保证在 /api 之前 和 / 之前 |
修改完成后重启 nginx
1 | docker方式: |
修复验证
1 | $ wget https://github.com/jumpserver/jumpserver/releases/download/v2.6.2/jms_bug_check.sh |
入侵检测
下载脚本到 jumpserver 日志目录,这个目录中存在 gunicorn.log,然后执行
1 | $ pwd |
WS未授权访问日志
源码分析
url:https://githistory.xyz/jumpserver/jumpserver/blob/db6f7f66b2e5e557081cb561029f64af0a1f80c4/apps/ops/ws.py
官方修复时改动的代码段:
1 | def connect(self): |
修复前:
1 | def connect(self): |
该类的路由可从源码得知:
1 | path('ws/ops/tasks/log/', ws.CeleryLogWebsocket, name='task-log-ws'), |
利用
目前看来利用方式限于读log。
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 | def get_permissions(self): |
可以理解为如果get/post请求带有user-only
参数,则该接口任意用户可访问。
利用
漏洞接口:
1 | xxx.xxx.xxx.xxx/api/v1/users/connection-token/?user-only=1 |
这两个接口都可以使用,因为最终都是调用漏洞点UserConnectionTokenApi。
利用方式:
通过日志中获取user_id、asset_id、system_user_id,再构造post请求获取token。
jumpserver相关源码:
代码执行
connection-token接口生成的token作为rest_api凭证使用会报错,需要利用koko完成代码执行。
koko对connection-token的使用方式:https://github.com/jumpserver/koko/blob/master/pkg/service/urls.go#L13
关于koko:
1 | 2019年9月30日,Jumpserver堡垒机发布V1.5.3版本。自V1.5.3版本起,Koko(即基于Go语言开发的SSH客户端)将担任Coco(即基于Python语言开发的SSH客户端)在Jumpserver项目中的角色,后续版本将不会再对Coco进行维护。 |
koko利用链分析
- urls.go
1
TokenAssetURL = "/api/v1/authentication/connection-token/?token=%s" // Token name
- assets.go
1
2
3
4
5
6
7
8func 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
} - webserber.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21func (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)
} - webserber.go
1
2
3
4
5
6
7
8
9
10
11func (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修复。