昨天Jenkins发布了安全公告,修复了几个漏洞,其中有一个 CVE-2019-10352,绕过了文件名的的限制,导致任意文件写入。但是需要Job/Configure
权限(没啥用)。说是问题出在file parameter definition
的地方。
Users with Job/Configure permission could specify a relative path escaping the base directory in the file name portion of a file parameter definition. This path would be used to store the uploaded file on the Jenkins master, resulting in an arbitrary file write vulnerability.
而且说是bypass了之前对前面的一个漏洞CVE-2018-1000406
但是我不知道这个file parameter是什么,于是google搜了一下"jenkins file parameter",找到这个youtube教程
中文的形式是这样的:
参数化构建过程=》Add Parameter=》文件参数
(这次我才明白了原来jenkins隐藏着那么多参数,需要你在对应的选项上打勾,才能显示出更多。)
这个功能的说明可以参考这个
https://wiki.jenkins.io/display/JENKINS/Parameterized+Build
File parameter allows a build to accept a file, to be submitted by the user when scheduling a new build. The file will be placed inside the workspace at the known location after the check-out/update is done, so that your build scripts can use this file.
即发起构建(build)操作的时候,将这个文件名作为参数传进去。在check-out/update步骤之后,这个文件随后会被放到公共空间的一个已知的位置,这样构建脚本就可以使用这个文件了。
这样添加了文件参数之后,果然到了断点了
刚才只是指定了文件名,我开始以为是被上传的文件。原来是上传后放在这个路径下。
打开刚才配置的那个job里,选择Build with Parameters
原来选择上传文件在这个地方
POC大概长这样
然而并没有成功
还是太Naive,
看这个文件:
https://github.com/jenkinsci/jenkins/blob/dedde6195362b4aeae75f8a147cdf263b2f9854b/test/src/test/java/hudson/model/FileParameterValueTest.java
这个文件里既有对新补丁SECURITY-1424的修复验证,
@Test
@Issue("SECURITY-1424")
public void fileParameter_cannotCreateFile_outsideOfBuildFolder_SEC1424() throws Exception {
// you can test the behavior before the correction by setting FileParameterValue.ALLOW_FOLDER_TRAVERSAL_OUTSIDE_WORKSPACE to true
FilePath root = j.jenkins.getRootPath();
FreeStyleProject p = j.createFreeStyleProject();
p.addProperty(new ParametersDefinitionProperty(Collections.singletonList(
new FileParameterDefinition("dir/../../../pwned", null)
)));
assertThat(root.child("pwned").exists(), equalTo(false));
String uploadedContent = "test-content";
File uploadedFile = tmp.newFile();
FileUtils.write(uploadedFile, uploadedContent);
FreeStyleBuild build = p.scheduleBuild2(0, new Cause.UserIdCause(), new ParametersAction(
new FileParameterValue("dir/../../../pwned", uploadedFile, "uploaded-file.txt")
)).get();
assertThat(build.getResult(), equalTo(Result.FAILURE));
assertThat(root.child("pwned").exists(), equalTo(false));
// ensure also the file is not reachable by request
JenkinsRule.WebClient wc = j.createWebClient();
wc.getOptions().setThrowExceptionOnFailingStatusCode(false);
}
还有对SECURITY-1074,即CVE-2018-1000406对补丁修复之后的验证
@Test
@Issue("SECURITY-1074")
public void fileParameter_cannotCreateFile_outsideOfBuildFolder() throws Exception {
// you can test the behavior before the correction by setting FileParameterValue.ALLOW_FOLDER_TRAVERSAL_OUTSIDE_WORKSPACE to true
FilePath root = j.jenkins.getRootPath();
FreeStyleProject p = j.createFreeStyleProject();
p.addProperty(new ParametersDefinitionProperty(Collections.singletonList(
new FileParameterDefinition("../../../../../root-level.txt", null)
)));
assertThat(root.child("root-level.txt").exists(), equalTo(false));
String uploadedContent = "test-content";
File uploadedFile = tmp.newFile();
FileUtils.write(uploadedFile, uploadedContent);
FreeStyleBuild build = p.scheduleBuild2(0, new Cause.UserIdCause(), new ParametersAction(
new FileParameterValue("../../../../../root-level.txt", uploadedFile, "uploaded-file.txt")
)).get();
assertThat(build.getResult(), equalTo(Result.FAILURE));
assertThat(root.child("root-level.txt").exists(), equalTo(false));
// ensure also the file is not reachable by request
JenkinsRule.WebClient wc = j.createWebClient();
wc.getOptions().setThrowExceptionOnFailingStatusCode(false);
checkUrlNot200AndNotContains(wc, build.getUrl() + "parameters/parameter/..%2F..%2F..%2F..%2F..%2Froot-level.txt/uploaded-file.txt", uploadedContent);
// encoding dots
checkUrlNot200AndNotContains(wc, build.getUrl() + "parameters/parameter/%2E%2E%2F%2E%2E%2F%2E%2E%2F%2E%2E%2F%2E%2E%2Froot-level.txt/uploaded-file.txt", uploadedContent);
// 16-bit encoding
checkUrlNot200AndNotContains(wc, build.getUrl() + "parameters/parameter/%u002e%u002e%u2215%u002e%u002e%u2215%u002e%u002e%u2215%u002e%u002e%u2215%u002e%u002e%u2215root-level.txt/uploaded-file.txt", uploadedContent);
// double encoding
checkUrlNot200AndNotContains(wc, build.getUrl() + "parameters/parameter/%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252froot-level.txt/uploaded-file.txt", uploadedContent);
// overlong utf-8 encoding
checkUrlNot200AndNotContains(wc, build.getUrl() + "parameters/parameter/%c0%2e%c0%2e%c0%af%c0%2e%c0%2e%c0%af%c0%2e%c0%2e%c0%af%c0%2e%c0%2e%c0%af%c0%2e%c0%2e%c0%afroot-level.txt/uploaded-file.txt", uploadedContent);
}
放到Burp Comparer里对比一下
主要就是从之前的Naive的payload:
../../../../../root-level.txt
改成了这样的payload
dir/../../../pwned