作者:SuperX@雪诺凛冬实验室
8 月 24 日,Atlassian 发布安全公告,披露了 Bitbucket Server 和 Data Center 在 7.0.0 版中引入了一个严重安全漏洞。
Bitbucket 是 Atlassian 公司提供的一个基于 web 的版本库托管服务,支持 Mercurial 和 Git 版本控制系统。支持私有化部署,根据国内某资产测绘平台数据显示,近一年全球有超过 1w+ 相关服务对外开放。
官方漏洞公告中描述 Bitbucket Server 和 Data Center 多个 API 端点存在命令注入漏洞,漏洞触发条件是攻击者具备公开项目的访问权限或者私有项目的可读权限,影响版本从 7.0 到 8.3 , 官方自评是 CVSS 9.9 分。
历史漏洞回顾
猜测可能是一个 git参数注入漏洞
,往前翻了翻历史上 bitbucket 出现过的 git参数注入漏洞
, 可以熟悉一下相关的系统框架。
CVE-2019-15000 分析
这是一个很有意思的参数注入,可以模拟如下指令:
/usr/bin/git diff -C --color=never -U10000 --dst-prefix=dst:// --src-prefix=src:// <可控sinceId> <可控untilId> -- <可控待diff文件名>
- 使用参数
--no-index
时,可以指定不在目录git仓库下的文件, - 逃逸
--
,--
之后的部分不再包含 参数选项
// 使用output 生成一个 -- 文件
git diff -C --color=never -U10000 --dst-prefix=dst:// --src-prefix=src:// --output=-- edc5e66fd8789e07fbcd45ffcd5284e0ba1b426e -- README.md
// 第一个可控 -- 为选项,第二个 -- 为文件,
/usr/bin/git diff -C --color=never -U10000 --dst-prefix=dst:// --src-prefix=src:// --no-index -- -- /etc/passwd
这样可以输出 diff 结果,达成任意文件读,如果 README可控(项目写入权限),也可以控制 output 达成任意文件写,CVE-2019-20097
中使用 post-receive
方式升级利用从任意文件写到 RCE。
CVE-2019-15010 分析
CVE-2019-15000
的修复是对 commitId 进行检测,对用户输入出现 --
开头进行阻止。
CVE-2019-15010
的exp 使用 %0a
作为开头绕过了 CVE-2019-15000
的补丁,利用方式和 CVE-2019-15000
相同。
在 CVE-2019-15010
补丁中,对 commitId 进行了更为严苛的检测。
CVE-2022-36804 补丁diff
回归到本次关注漏洞的补丁,在官方通告中并没有针对性地给出单个 jar 包的热补丁,使用 8.3.0
和 8.3.1
版本,对比反编译前后的更新代码。
代码变动不算太大,很快定位到 NioProcessParameters
和 NuProcessBuilder
关键修改之处。
领悟一下其修改逻辑,删除了参数和环境变量中的 \00
字符。
继续跟进关键库 Nuprocess
NuProcess
执行命令实现类,关注 LinuxProcess
实现
构造命令关键函数 prepareProcess
private void prepareProcess(List<String> command, String[] environment, Path cwd) throws IOException {
// 需要执行 git 命令数组
String[] cmdarray = (String[])command.toArray(new String[0]);
byte[][] args = new byte[cmdarray.length - 1][];
int size = args.length;
// 取 git 命令数组参数,第0位之后,存储到 args[][]
for(int i = 0; i < args.length; ++i) {
args[i] = cmdarray[i + 1].getBytes();
size += args[i].length;
}
// 最终存储参数的 byte数组 argBlock
byte[] argBlock = new byte[size];
int i = 0;
byte[][] var9 = args;
int var10 = args.length;
for(int var11 = 0; var11 < var10; ++var11) {
byte[] arg = var9[var11];
// 使用 system.arraycopy 将 arg[][] 二维数组拷贝到 argBlock
System.arraycopy(arg, 0, argBlock, i, arg.length);
i += arg.length + 1;
}
byte[] envBlock = toEnvironmentBlock(environment);
this.createPipes();
try {
int[] child_fds = new int[]{this.stdinWidow, this.stdoutWidow, this.stderrWidow};
if (Constants.JVM_MAJOR_VERSION >= 10) {
this.pid = LibJava10.Java_java_lang_ProcessImpl_forkAndExec(JNIEnv.CURRENT, this, LinuxProcess.LaunchMechanism.VFORK.ordinal() + 1, toCString(System.getProperty("java.home") + "/lib/jspawnhelper"), toCString(cmdarray[0]), argBlock, args.length, envBlock, environment.length, cwd != null ? toCString(cwd.toString()) : null, child_fds, (byte)0);
} else {
this.pid = LibJava8.Java_java_lang_UNIXProcess_forkAndExec(JNIEnv.CURRENT, this, LinuxProcess.LaunchMechanism.VFORK.ordinal() + 1, toCString(System.getProperty("java.home") + "/lib/jspawnhelper"), toCString(cmdarray[0]), argBlock, args.length, envBlock, environment.length, cwd != null ? toCString(cwd.toString()) : null, child_fds, (byte)0);
}
} finally {
this.closePipes();
}
}
注意在 执行 数组拷贝时,目的地址是在当前参数之后 +1
的
可以看出,参数之间使用 0 来分割,那么答案呼之欲出,在 Linux 环境下,可以 \00
来实现参数注入。
EXP 构造
git diff 任意文件写
利用历史漏洞思路 git diff 在有项目内容控制权限的情况下 达成任意文件写
参数注入代码执行
官方披露的漏洞效果是仅在有只读权限情况下可以进行命令执行,通过枚举应用所有只读权限情况下可以构造的 git 指令,找到一处进行参数注入,构造恶意 url 访问即可造成任意命令执行。
参考链接
- CVE-2022-36804 : https://jira.atlassian.com/browse/BSERV-13438
- CVE-2019-15000【漏洞预警】BitBucket服务器参数注入漏洞安全预警通告第二次更新 : https://mp.weixin.qq.com/s/3J-lA0CQylrq2ZY3ZEESiQ)
- CVE-2019-15010 bitbucket 的 git 参数命令注入漏洞 : https://blog.csdn.net/qq_34101364/article/details/109952236
雪诺凛冬实验室
雪诺凛冬实验室,隶属于北京雪诺科技,聚焦零信任赛道。主要职能包括红队技术、安全狩猎等前瞻攻防技术预研、工具平台孵化。团队成员在多次参与国家级攻防演练并获得优异名次,在渗透测试、红蓝对抗、应急响应等服务方向拥有丰富的实战经验。