요약
Jenkins-CLI의 인자속 '@' 문자가 존재하면 해당 경로의 파일을 읽어들이는 버그이다. 해당 버그는 권한 설정 中 "Allow anonymous read access"와 같이 비인가 사용자에게 읽기 권한을 부여하는 상황에서 발생한다. 이러한 버그는 Jenkins 서버의 중요한 파일들을 읽어올 수 있게하며, 심각한 피해로 이어질 수 있다. 해당 취약점은 Jenkins versions 2.442 와 LTS 2.426.3. 버전부로 조치되었다.
취약점 상세
Jenkins는 다양한 방식의 인증방식을 제공한다. "Anyone can do anything", "Legacy mode", "Logged-in users can do anything" 등 아래 그림을 참고하자. "Logged-in users can do anything" 과 같은 경우, 선택 시 "Allow anonymous read access" 옵션을 추가로 선택할 수 있다.

또한, 다른 사용자의 계정 생성을 가능하게 하는 옵션도 존재한다. 이러한 경우 다른 모든 사람들에게 최소한 read-only 권한을 주는 것과 다름없다.

공식 문서에 의하면, read-only 권한을 획득한 사용자는 다음과 같은 접근이 가능하다.
- Access the basic Jenkins API and the API of any object they have access to.
- Access the people directory listing user accounts and known committer identities of anyone involved in visible projects.
- List and view all agents configured in Jenkins and access their summary pages.
이어, Jenkins-CLI 는 사용자에게 built-in command line interface를 제공한다. 해당 CLI에서는 Jenkins Git 의 hudson/cli 디렉터리에 있는 커스텀 명령들을 수행할 수 있다.

CLI 명령 수행 시, Jenkins는 아래 expandAtFiles로 이어지는 args4j의 parseArgument를 이용한다.
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()]);
}
expandAtFiles 함수는 인자가 '@'문자로 시작된다면, 해당 경로의 파일을 읽어들여 각 line을 새로운 인자로 반환한다. 이는 공격자가 인자를 관리할 수만 있다면 임의의 서버 파일속 내용을 전부 확인 할 수 있게 된다는 뜻이기도 하다. 그에대한 예시는 아래에서 살펴보도록 하자.

이러한 공격은 connect-node 명령으로 접근이 가능하다. 아래 코드에서 확인할 수 있듯 해당 명령은 문자열로 이루어진 배열(노드)을 인자로 받아, 각각의 연결을 시도한다. 중요한 점은 각 연결이 실패할 경우 error 메세지가 생성되는데 이 때 메세지에는 연결에 실패한 node의 이름을 포함한다.
public class ConnectNodeCommand extends CLICommand {
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
@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;
@Option(name = "-f", usage = "Cancel any currently pending connect operation and retry from scratch")
public boolean force = false;
private static final Logger LOGGER = Logger.getLogger(ConnectNodeCommand.class.getName());
@Override
public String getShortDescription() {
return Messages.ConnectNodeCommand_ShortDescription();
}
@Override
protected int run() throws Exception {
boolean errorOccurred = false;
final HashSet<String> hs = new HashSet<>(nodes);
for (String node_s : hs) {
try {
Computer computer = Computer.resolveForCLI(node_s);
computer.cliConnect(force);
} catch (Exception e) {
if (hs.size() == 1) {
throw e;
}
final String errorMsg = node_s + ": " + e.getMessage();
stderr.println(errorMsg);
errorOccurred = true;
continue;
}
}
if (errorOccurred) {
throw new AbortException(CLI_LISTPARAM_SUMMARY_ERROR_TEXT);
}
return 0;
}
}
connect-node 명령을 정상적으로 수행하기 위해선 기본적으로 cliConnect 메서드에서 검증하는 CONNECT 권한이 있어야 한다. 하지만 권한 검증 이전에 resolveForCLI 함수에서 예외가 발생하기 때문에 현재로선 권한을 고려할 필요가 없게된다. 위 코드를 보면 알 수 있는 사실이다.

이러한 CVE-2024-23897 취약점은 SSH keys, /et/passwd, /etc/shadow, Project secrets and credentials 등 수많은 민감한 파일에 접근을 허용한다. 아래 사진은 공격자가 서버 파일을 읽어들인 예시 상황이다.
