최근 Jenkins에서 공격에 취약한 서버의 데이터에 액세스하고 특정 조건에서 임의의 CLI 명령을 실행할 수 있는 두가지 취약점이 발견됐다.
Jenkins는 소프트웨어 개발, 특히 지속적 통합 (CI) 및 지속적 배포(CD)에 널리 사용되는 오픈소스 자동화 서버로, 애플리케이션 구축, 테스트, 배포 등 소프트웨어 개발 프로세스의 다양한 부분을 자동화하는데 중요한 역할을 한다.
첫번째 취약점인 CVE-2024-23897은 인증되지 않은 공격자가 제한된 양의 임의 파일 데이터를 읽을 수 있으며, 공격자가 Jenkins 서버에서 전체 임의 파일에 대해 "읽기 전용" 권한을 부여받을 수 있는 취약점이다. 그리고 공격자는 "읽기" 권한을 부여받는 취약성을 활용하여, 관리자에게 권한을 에스컬레이션하고 결국 서버에서 임의의 코드를 실행할 수 있게 된다.
읽기 전용 권한이 없는 공격자도 파일의 처음 몇 줄은 읽을 수 있으며, 그 수는 사용 가능한 CLI 명령어에 따라 달라진다.
두번째 취약점인 CVE-2024-23898은 CSWSH 취약점을 통해 피해자가 링크를 클릭하도록 조작하여 임의의 CLI 명령을 실행하도록 한다.
배경
Jenkins는 "anyone can do anything" , "legacy" , "logged-in users can do anything" 과 같은 여러가지 권한 부여 방법을 제공한다. "logged-in users can do anything" 와 "legacy" 옵션은 익명 읽기 옵션을 허락하고, 모든 사람들에게 읽기 권한을 부여한다.
이러한 읽기 전용 액세스를 통해 사용자는
- 기본 Jenkins API 및 액세스 권한이 있는 모든 개체의 API에 액세스
- 사용자 디렉토리에 액세스하여 표시되는 프로젝트에 관련된 모든 사람의 사용자 계정 및 ID를 나열
하는 식의 작업이 가능하게 된다.
그리고 관리자는 Jenkins 인스턴스에서 거의 모든 작업을 수행할 수 있는데, 공격자의 관점에서보면 관리자는 Jenkins 서버에서 임의의 코드를 수행하는 작업까지 가능하다.
Jenkins-CLI 기능
Jenkins-CLI 는 Jenkins Git 리포지토리의 hudson/cli 디렉토리에 구현된 사용자 지정 명령을 실행할 수 있는 기본 제공 명령줄 인터페이스를 사용자에게 제공한다.
Jenkins-CLI을 호출할 때는 일반적인 방법 외에도, WebSocket을 사용하는 jenkins-cli.jar나 SSH를 사용할 수 있다.
Stapler(method와 endpoint의 상관관계를 지정하는 Jenkins의 구성요소) 가 /cli 경로의 관련 메소드를 가져올 때, endpoint는 PlaincliEndpointResponse() 예외를 발생시키며, generateResponse 함수로 끝난다.
public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object node) throws IOException, ServletException {
try {
UUID uuid = UUID.fromString(req.getHeader("Session"));
//...
if (req.getHeader("Side").equals("download")) {
FullDuplexHttpService service = createService(req, uuid);
//...
try {
service.download(req, rsp);
}
//...
} else {
FullDuplexHttpService service = services.get(uuid);
//...
try {
service.upload(req, rsp);
}
//...
}
}
이 CLI 기능을 사용하려면, 다운로더와 업로더가 필요하다.
다운로더는 명령의 응답을 반환하고, 업로더는 요청 본문에서 지정된 명령을 호출한다. Jenkins는 Session header의 UUID를 사용하여 다운로더와 업로더를 연결한다.
Data Leak Vulnerability (CVE-2024-23897)
: 인수와 함께 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()]);
}
이 함수는 인수가 @로 시작하는지 확인하고, @로 시작한다면 @ 뒤 경로에서 파일을 읽고, 각 줄에 대해 새로운 인수를 확장한다.
즉, 공격자가 인수를 제어할 수 있는 경우 Jenkins 인스턴스의 임의의 파일에서 임의의 수로 확장할 수 있게 되는데, 이 때 공격자가 임의의 수의 인수를 가져와서 사용자에게 다시 표시하는 명령을 찾는 것이다.
인수는 파일 내용에서 채워지기 때문에 공격자는 이러한 방식으로 파일 내용을 유출할 수 있다.
-> 인수가 '@' 문자로 시작할 때, 파일 내용을 명령 인수로 자동 확장하여 Jenkins 컨트롤러 파일 시스템에서 임의의 파일을 무단으로 읽을 수 있게 하는 Jenkins의 args4j 명령 파서의 기본 동작에서 비롯되는 취약점이다.
이러한 특정 취약점을 악용하면 관리자 권한 및 임의의 원격 코드 실행으로 이어질 수 있다.
Patch
Jenkins 보안팀은 "expandAtFiles" 기능을 비활성화하는 보안 구성을 추가하여 CVE-2024-23897을 패치했다.
+ public static boolean ALLOW_AT_SYNTAX = SystemProperties.getBoolean(CLICommand.class.getName() + ".allowAtSyntax");
//...
- return new CmdLineParser(this);
+ ParserProperties properties = ParserProperties.defaults().withAtSyntax(ALLOW_AT_SYNTAX);
+ return new CmdLineParser(this, properties);
CSWSH Vulnerability (CVE-2024-23898)
Jenkins-CLI 명령을 호출하는 방법에는 WebSocket을 사용하는 방법이 있다
.
브라우저는 WebSocket에 SOP 및 CORS 정책을 적용하지 않는 것으로 알려져 있다.
WebSocket은 WS (WebSocket) 또는 WSS (WebSocketSecure) 프로토콜을 통해 작동하는 동안 HTTP 응답에 제한이 적용되기 때문에 SOP 및 CORS 정책에 의해 부과 된 교차 출처 제한은 WebSocket에 적용되지 않는다.
따라서 WebSocket 요청에는 CSRF토큰 또는 Origin 헤더 검사가 없으므로, 웹 사이트에서 WebSocket을 사용하여 CSRF 취약점과 유사한 방식으로 피해자의 ID를 통해 Jenkins-CLI 명령을 호출할 수 있다,.
Patch
WebSocket endpoint에 Origin 확인을 추가했다. 이 매개변수는 toggle 역할을 하여 관리자에게 기본 동작을 재정의할 수 있는 기능을 부여한다. Origin에 관계없이 WS CLI에 대한 액세스를 일관되게 허용하거나 거부하는 옵션을 제공한다.
public HttpResponse doWs(StaplerRequest req) {
if (!WebSockets.isSupported()) {
return HttpResponses.notFound();
}
+ if (ALLOW == null) {
+ final String actualOrigin = req.getHeader("Origin");
+ final String expectedOrigin = StringUtils.removeEnd(StringUtils.removeEnd(+Jenkins.get().getRootUrlFromRequest(), "/"), req.getContextPath());
+
+ if (actualOrigin == null || !actualOrigin.equals(expectedOrigin)) {
+ LOGGER.log(Level.FINE, () -> "Rejecting origin: " + actualOrigin + "; expected was from request: " + +expectedOrigin);
+ return HttpResponses.forbidden();
+ }
+ } else if (!ALLOW) {
+ return HttpResponses.forbidden();
+ }
Authentication authentication = Jenkins.getAuthentication2();
WebSocket은 Origin 헤더의 문제와 관련한 CSWSH 취약점이 다수 발생한다.
그 이유로는 WebSocket 이 일반 HTTP 요청과는 다르게 SOP에 의해 제한되지 않는 프로토콜이기 때문이라고도 볼 수 있다. 따라서, WebSocket을 사용하는 경우에는 서버 측에서 출처 검사 및 인증을 통해 취약점을 방지해야 한다.만약 출처 검사를 거치지 않고 WebSocket 연결을 하게 되면, 공격자가 다른 출처에서 WebSocket 연결을 통해 공격할 수 있다.
[참고 자료]
과도한 확장: Jenkins의 중요한 보안 취약점 발견 | 수중 음파 탐지기 (sonarsource.com)
Excessive Expansion: Uncovering Critical Security Vulnerabilities in Jenkins
This blog uncovers two vulnerabilities, a Critical and High severity, recently discovered by our research team. Exploiting these vulnerabilities, attackers have the potential to gain Remote Code Execution on a Jenkins instance.
www.sonarsource.com
중요한 Jenkins RCE 결함에 대한 익스플로잇 릴리스, 지금 패치 (bleepingcomputer.com)
Exploits released for critical Jenkins RCE flaw, patch now
Multiple proof-of-concept (PoC) exploits for a critical Jenkins vulnerability allowing unauthenticated attackers to read arbitrary files have been made publicly available, with some researchers reporting attackers actively exploiting the flaws in attacks.
www.bleepingcomputer.com
'INCOGNITO 2023' 카테고리의 다른 글
[WebSocket] TLS HandShake (0) | 2024.03.10 |
---|---|
[WebSocket] WAF / IDS / IPS (0) | 2024.02.26 |
[WebSocket] HTTPS / SSL / TLS (0) | 2024.02.26 |
[WebSocket] CSWSH 취약점 방어 기법- Oauth 인증 프레임워크를 통한 AccessToken 인증 (0) | 2024.02.18 |
[WebSocket] CSWSH 취약점 방어 기법 - 혼합 암호화 기반 일회성 무작위 토큰 (0) | 2024.02.16 |