影响版本
漏洞分析
简便的方法是,根据影响版本可以得到的信息是,v2.6.2是安全的,所以只需要在github上对比v2.6.2和v2.6.1的版本更迭记录即可。
Compare v2.6.1 and v2.6.2
Analizied 6 changed files with 11 additionsand 12 deletions.
uth.py&&ws.py这两个文件,前者删了一个方法,后者加了一些权限校验。接下来逐一分析。
auth.py的代码分析
根据jumpserver官方给的应急解决方案中,定位到了要封禁的接口:
在api_urls.py文件中的url路由规则如下:
UserConnectionTokenApi类对应的是auth.py,所以通过这种方式也可以找到漏洞发生的地方
该类有三个方法:一个POST方法,和一个GET方法,和一个get_permissions方法,各自对应的源码如下:
POST:
GET:
get_permissions:
首先对接口进行功能分析,POST方法实现的是:
post提交user、assest、system_user,该方法会设置生成一个token,并将该数组的对应关系缓存,然后返回token。
get方法实现的是,请求提交token,该方法用token去查user,如果存在就把token对应的user返回。
功能上看起来实现的是,用户通过jumpserver使用不同机器时,服务器按照用户提交参数签发一个20s的token,用户使用该token去访问对应的机器。
但我们使用jumpserver会发现,客户端使用机器时,向服务端提交的参数都是id,且ID随机复杂无规律,通过爆破的方式显然是不合理的,而本次未授权漏洞正是因为log中记录了这些敏感信息,并且可以被攻击者未授权访问到,所以产生了一个漏洞利用链。
ws.py文件分析
Compare connect()
如图为v1.5.9的connect函数。
如图为v2.6.2的connect函数
ws文件里对connect函数加了校验,判断只有认证过的org_admin才能访问该函数,证明这里是个未授权的入口。
继续分析函数,我们通过该未授权接口传入的task参数会传入到read_log_file中。
wait_util_log_path_exist
作用: 读取传入的logpath。并返回read_log_file
通过以上流程分析的很清楚了,我们只需向服务器发起websocket请求,并提交json格式的taskid:logpath即可获取到日志中的敏感信息。
找到该websocket的访问路径
漏洞利用过程
使用chrome插件 Websocket Client发包如下:
找到包含重要字段的日志:
[18/Jan/2021:12:58:23 +0800] \"GET /api/v1/perms/asset-permissions/user/validate/?action_name=connect&asset_id=12037419-570f-4732-b135-a8287f19f463&cache_policy=1&system_user_id=d90fe502-3263-4ed9-9540-9d5a60a39d42&user_id=adfcaf61-85bd-4102-b5a4-6f339d7f1db4
首先根据ID构造post包获取token令牌:
编写python模拟正常请求即可,用github上大佬ska的脚本[参考链接在后面]:
大佬用协程写了个交互式shell(膜).
modsecurity判定规则
在这种场景下,基本上看到这个uri请求我们就可以断言这是一个报警,因此编写规则如下:
SecRule REQUEST_URI "/ws/ops/tasks/log" "id:11111111,phase:1,id:52,t:none,t:urlDecode,t:lowercase,t:normalizePath,msg:'jump-rce'"
可以通过部署的规则起到一定的报警作用,也可以捕获一些告警流量。