前言
距离上次写文章居然过去两年多了,这两年多忙于各种事务,
环境搭建
官方仓库维护了各种版本的Bitbicket,我们根据漏洞描述选择一个可以触发漏洞的版本即可。
然后根据镜像启动一个容器即可:
docker run --name="bitbucket" -d -p 7990:7990 -p 7999:7999 atlassian/bitbucket-server:8.0.4-ubuntu-jdk11
补丁对比
这次漏洞的更新并没有直接给出一个类似之前Confluence的bash脚本,因此给漏洞位置的确定带来了一定的困难。我本地选择了8.0.4和8.0.5的版本进行比对,得到的结果如下图:
其中licenses目录忽略,osgi-framework-bundles目录为osgi的bundle 所在目录,打开可以看到对比结果:
大小相差只有1字节,排除。接着是 atlassian-bundled-plugins目录为插件目录,根据大小和jar包的名称过一圈,最终锁定为app/WEB-INF/lib/bitbucket-process-8.0.x.jar:
然后diff对比一下:
然后使用Idea的对比工具查看:
可以看到在8.0.5的版本新增了environmentPut和environmentPutIfAbsent两个函数,其中核心的判断逻辑是对传入值是否为0x00进行了检查,并且在com.atlassian.bitbucket.internal.process.RemoteUserNioProcessConfigurer
中在将用户名放入环境变量时使用environmentPut代替了原先直接put的操作(代码上边为8.0.5,下边为8.0.4):
而漏洞的描述信息说是由用户控制用户名从而控制环境变量导致命令注入,至此推断基本吻合:
漏洞触发
搞清楚漏洞原理之后,开始寻找能够触发命令执行的Git环境变量,搜索到了Git官方的文档:
但是该命令不支持命令行参数,文档中也说如果需要支持参数,最好自己去封装一个脚本,然后我开始看Git的详细手册:
其中GIT_SSH_COMMAND是支持参数的!
然后开始研究能够触发漏洞的路由,根据NioCommand在网络上搜索找到了这样的一个报错堆栈:
找到RepositoryController之后,经过几轮调试发现:
- 注册用户必须有仓库的访问权限,否则没办法触发Git相关的操作
- 注册用户需要拥有创建或者访问仓库的权限
因此我为了调试本漏洞,首先开启注册功能,正如官方的缓解措施中告诉我们要关闭注册的那样,我们先将它打开 _
注册用户,使用burp修改空格为NULL空字符:
发包之后注册成功,登录的时候也要把这个字符修改掉,发包之后断点命中(com.zaxxer.nuprocess.linux.LinuxProcess#toEnvironmentBlock):
跟进toEnvironmentBlock这个函数:
这里有两个需要重点关注,也是这个漏洞能利用成功的关键:
- 环境变量初始化的时候使用了byte数组,数组初始值全为0,从调试窗口可以看到
- 每次复制一个数据,都会跳过一个位置将0字节(NULL)放到该值的末尾。这个从for循环最后一行可以体现出来
接着我让循环走两轮来证明以上的分析:
可以看到两轮复制之后数据中间明显有一个空字节。这样我们创建的 用户名+NULL+环境变量,就很自然会注入一个新的环境变量:
将字节数组转化为字符串后,放到文本工具中将<0x00>替换为换行符方便查看:
但是执行过后并没有在目标/tmp/创建文件。因为触发这个命令执行的必要条件是Git连接SSH主机,而在Bitbucket中却很难找寻到这样的场景。
正当我搜索枯肠且感觉距离真相不远的时候,朋友直接丢来了一篇文章,顿时兴趣全无(╥╯^╰╥),虽然这篇文章中的越南文字我不认识,但是看到了关键性的GIT_EXTERNAL_DIFF。manual手册的说明如下:
大意是当设置了该环境变量,git diff的时候将会调用该程序。要比GIT_SSH_COMMADN触发条件要容易得多了。
完整复现步骤
- 注册一个用户名为 字符串+NULL+GIT_EXTERNAL_DIFF=命令 的用户
- 访问任何一个public的仓库
- 访问Commit记录有变更过的文件触发git diff命令
- 命令执行成功