距离上次写博客转眼已过去快三年,“空白”的这三年中也曾遇到很多有趣可成文的东西,但是因各种原因未能在此畅快抒怀。而在忙中偷闲的时候,也曾反省自己,以免迷失。回想最初入行安全的时候,全因一本早期的黑客杂志而对漏洞研究有了独特的热情,再后来在Wooyun看过前辈们精彩的漏洞之后更加坚定的将漏洞研究作为自己未来努力的方向。但是在入行5,6年之后回头来看,却也常因外部嘈杂的声音淹没自己内心真实的想法,慢慢于繁重、枯燥的工作中磨灭了自己。深夜辗转之时,不免在难过之余暗自唏嘘。《周易》有卦,外险内动名之为屯,如何把握未来的路,或许只能先做好当下了罢。
官方仓库维护了各种版本的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之后,经过几轮调试发现:
因此我为了调试本漏洞,首先开启注册功能,正如官方的缓解措施中告诉我们要关闭注册的那样,我们先将它打开 _
注册用户,使用burp修改空格为NULL空字符:
发包之后注册成功,登录的时候也要把这个字符修改掉,发包之后断点命中(com.zaxxer.nuprocess.linux.LinuxProcess#toEnvironmentBlock):
跟进toEnvironmentBlock这个函数:
这里有两个需要重点关注,也是这个漏洞能利用成功的关键:
接着我让循环走两轮来证明以上的分析:
可以看到两轮复制之后数据中间明显有一个空字节。这样我们创建的 用户名+NULL+环境变量,就很自然会注入一个新的环境变量:
将字节数组转化为字符串后,放到文本工具中将<0x00>替换为换行符方便查看:
但是执行过后并没有在目标/tmp/创建文件。因为触发这个命令执行的必要条件是Git连接SSH主机,而在Bitbucket中却很难找寻到这样的场景。
正当我搜索枯肠且感觉距离真相不远的时候,朋友直接丢来了一篇文章,顿时兴趣全无(╥╯^╰╥),虽然这篇文章中的越南文字我不认识,但是看到了关键性的GIT_EXTERNAL_DIFF。manual手册的说明如下:
大意是当设置了该环境变量,git diff的时候将会调用该程序。要比GIT_SSH_COMMADN触发条件要容易得多了。