2013-10-22 14:27:37 +00:00
|
|
|
##
|
2017-07-24 13:26:21 +00:00
|
|
|
# This module requires Metasploit: https://metasploit.com/download
|
2013-10-22 14:27:37 +00:00
|
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
|
|
##
|
|
|
|
|
2013-09-17 14:47:59 +00:00
|
|
|
##
|
|
|
|
# Some of this code was taken from the "jboss_vulnscan" module by: Tyler Krpata
|
|
|
|
##
|
|
|
|
|
|
|
|
require 'rex/proto/http'
|
2013-10-21 18:18:12 +00:00
|
|
|
require 'rexml/document'
|
2013-09-17 14:47:59 +00:00
|
|
|
|
2016-03-08 13:02:44 +00:00
|
|
|
class MetasploitModule < Msf::Auxiliary
|
2013-10-22 14:26:44 +00:00
|
|
|
include Msf::Exploit::Remote::HttpClient
|
|
|
|
include Msf::Auxiliary::Scanner
|
|
|
|
include Msf::Auxiliary::Report
|
2013-09-17 14:47:59 +00:00
|
|
|
|
2013-10-22 14:26:44 +00:00
|
|
|
def initialize(info = {})
|
|
|
|
super(update_info(info,
|
2015-09-04 17:25:51 +00:00
|
|
|
'Name' => 'Jenkins-CI Enumeration',
|
2013-10-22 14:28:15 +00:00
|
|
|
'Description' => %q{
|
2015-09-04 17:25:51 +00:00
|
|
|
This module enumerates a remote Jenkins-CI installation in an unauthenticated manner, including
|
2017-08-27 01:01:10 +00:00
|
|
|
host operating system and Jenkins installation details.
|
2013-10-22 14:26:44 +00:00
|
|
|
},
|
2013-10-22 14:28:15 +00:00
|
|
|
'Author' => 'Jeff McCutchan',
|
|
|
|
'License' => MSF_LICENSE
|
2013-10-22 14:26:44 +00:00
|
|
|
))
|
2013-09-17 14:47:59 +00:00
|
|
|
|
2013-10-22 14:26:44 +00:00
|
|
|
register_options(
|
|
|
|
[
|
2015-09-04 17:25:51 +00:00
|
|
|
OptString.new('TARGETURI', [ true, 'The path to the Jenkins-CI application', '/jenkins/' ])
|
2017-05-03 20:42:21 +00:00
|
|
|
])
|
2013-10-22 14:26:44 +00:00
|
|
|
end
|
2013-09-17 14:47:59 +00:00
|
|
|
|
2013-10-22 14:26:44 +00:00
|
|
|
def run_host(ip)
|
|
|
|
res = send_request_cgi(
|
|
|
|
{
|
|
|
|
'uri' => target_uri.path,
|
|
|
|
'method' => 'GET',
|
|
|
|
'ctype' => 'text/plain',
|
|
|
|
})
|
2013-10-22 15:02:32 +00:00
|
|
|
|
|
|
|
unless res
|
2016-02-01 22:06:34 +00:00
|
|
|
vprint_error("No response received")
|
2013-10-22 15:02:32 +00:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
unless res.headers.include?('X-Jenkins')
|
2016-02-01 22:06:34 +00:00
|
|
|
vprint_error("responded with #{res.code} but does not seem to be Jenkins")
|
2013-10-22 14:26:44 +00:00
|
|
|
return
|
|
|
|
end
|
2013-10-22 15:02:32 +00:00
|
|
|
|
2013-10-22 14:26:44 +00:00
|
|
|
version = res.headers['X-Jenkins']
|
2017-10-27 16:34:49 +00:00
|
|
|
print_good("#{rhost}:#{rport} Jenkins Version - #{version}")
|
2013-10-22 16:15:16 +00:00
|
|
|
report_service(
|
|
|
|
:host => rhost,
|
|
|
|
:port => rport,
|
|
|
|
:name => (ssl ? 'https' : 'http'),
|
|
|
|
:proto => 'tcp'
|
|
|
|
)
|
|
|
|
|
|
|
|
report_web_site(
|
|
|
|
:host => rhost,
|
|
|
|
:port => rport,
|
|
|
|
:ssl => ssl,
|
|
|
|
:info => "Jenkins Version - #{version}"
|
|
|
|
)
|
2013-10-22 15:02:32 +00:00
|
|
|
|
2013-10-22 14:26:44 +00:00
|
|
|
# script - exploit module for this
|
|
|
|
# view/All/newJob - can be exploited manually
|
|
|
|
# asynchPeople - Jenkins users
|
|
|
|
# systemInfo - system information
|
2013-10-22 15:02:32 +00:00
|
|
|
apps = [
|
|
|
|
'script',
|
|
|
|
'view/All/newJob',
|
|
|
|
'asynchPeople/',
|
|
|
|
'systemInfo'
|
2013-10-22 14:26:44 +00:00
|
|
|
]
|
|
|
|
apps.each do |app|
|
|
|
|
check_app(app)
|
|
|
|
end
|
|
|
|
end
|
2013-09-17 14:47:59 +00:00
|
|
|
|
2013-10-22 14:26:44 +00:00
|
|
|
def check_app(app)
|
|
|
|
uri_path = normalize_uri(target_uri.path, app)
|
|
|
|
res = send_request_cgi({
|
|
|
|
'uri' => uri_path,
|
|
|
|
'method' => 'GET',
|
|
|
|
'ctype' => 'text/plain',
|
|
|
|
})
|
2013-10-22 15:02:32 +00:00
|
|
|
unless res
|
2016-02-01 22:06:34 +00:00
|
|
|
vprint_error("Timeout")
|
2013-10-22 14:26:44 +00:00
|
|
|
return
|
|
|
|
end
|
2013-10-22 15:02:32 +00:00
|
|
|
|
2013-10-22 14:26:44 +00:00
|
|
|
case res.code
|
|
|
|
when 200
|
2016-06-01 02:47:00 +00:00
|
|
|
print_good("#{full_uri} - #{uri_path} does not require authentication (200)")
|
2013-10-22 16:15:16 +00:00
|
|
|
report_note({
|
|
|
|
:type => "jenkins_path",
|
|
|
|
:host => rhost,
|
|
|
|
:port => rport,
|
|
|
|
:proto => 'tcp',
|
2016-06-01 02:47:00 +00:00
|
|
|
:data => "#{full_uri} - #{uri_path} does not require authentication (200)",
|
2013-10-22 16:15:16 +00:00
|
|
|
:update => :unique_data
|
|
|
|
})
|
2013-10-22 14:26:44 +00:00
|
|
|
case app
|
|
|
|
when "systemInfo"
|
|
|
|
parse_system_info(res.body)
|
|
|
|
when "script"
|
|
|
|
report_vuln(
|
|
|
|
:host => rhost,
|
|
|
|
:port => rport,
|
|
|
|
:proto => 'tcp',
|
|
|
|
:sname => (ssl ? 'https' : 'http'),
|
2013-10-22 15:02:32 +00:00
|
|
|
:name => "Jenkins Script-Console Java Execution",
|
2013-10-22 14:26:44 +00:00
|
|
|
:info => "Module #{self.fullname} confirmed access to the Jenkins Script Console with no authentication"
|
|
|
|
)
|
|
|
|
end
|
|
|
|
when 403
|
2016-02-01 22:06:34 +00:00
|
|
|
print_status("#{uri_path} restricted (403)")
|
2013-10-22 14:26:44 +00:00
|
|
|
when 401
|
2016-02-01 22:06:34 +00:00
|
|
|
print_status("#{uri_path} requires authentication (401): #{res.headers['WWW-Authenticate']}")
|
2013-10-22 14:26:44 +00:00
|
|
|
when 404
|
2016-02-01 22:06:34 +00:00
|
|
|
print_status("#{uri_path} not found (404)")
|
2013-10-22 14:26:44 +00:00
|
|
|
when 301
|
2016-02-01 22:06:34 +00:00
|
|
|
print_status("#{uri_path} is redirected (#{res.code}) to #{res.headers['Location']} (not following)")
|
2013-10-22 14:26:44 +00:00
|
|
|
when 302
|
2016-02-01 22:06:34 +00:00
|
|
|
print_status("#{uri_path} is redirected (#{res.code}) to #{res.headers['Location']} (not following)")
|
2013-10-22 14:26:44 +00:00
|
|
|
else
|
2016-02-01 22:06:34 +00:00
|
|
|
print_status("#{uri_path} Don't know how to handle response code #{res.code}")
|
2013-10-22 14:26:44 +00:00
|
|
|
end
|
|
|
|
end
|
2013-09-17 14:47:59 +00:00
|
|
|
|
2013-10-22 14:26:44 +00:00
|
|
|
def parse_system_info(body)
|
2016-02-01 22:06:34 +00:00
|
|
|
vprint_status("Getting useful information from systemInfo")
|
2013-10-22 14:26:44 +00:00
|
|
|
infos = {
|
2013-10-22 15:29:30 +00:00
|
|
|
"os.name" => nil,
|
|
|
|
"os.version" => nil,
|
|
|
|
"sun.os.patch.level" => nil,
|
|
|
|
"os.arch" => nil,
|
|
|
|
"user.name" => nil,
|
|
|
|
"USERDOMAIN" => nil,
|
|
|
|
"user.home" => nil,
|
|
|
|
"user.language" => nil,
|
|
|
|
"user.country" => nil,
|
|
|
|
"user.timezone" => nil,
|
|
|
|
"COMPUTERNAME" => nil,
|
|
|
|
"SystemDrive" => nil,
|
|
|
|
"TEMP" => nil,
|
|
|
|
"TMP" => nil,
|
|
|
|
"SHELL" => nil
|
2013-10-22 14:26:44 +00:00
|
|
|
}
|
2013-10-22 15:02:32 +00:00
|
|
|
|
|
|
|
# remove unclosed tags for REXML
|
2013-10-22 15:29:30 +00:00
|
|
|
body.gsub!('<wbr>', '')
|
|
|
|
body.gsub!('<br>', '')
|
2013-10-22 15:02:32 +00:00
|
|
|
doc = REXML::Document.new(body)
|
|
|
|
tds = doc.get_elements("//td")
|
2013-10-22 15:29:30 +00:00
|
|
|
tds.each_index do |idx|
|
|
|
|
td = tds[idx].get_text.to_s.strip
|
|
|
|
infos[td] = tds[idx+1].get_text.to_s.strip if infos.has_key?(td)
|
2013-10-22 15:02:32 +00:00
|
|
|
end
|
2013-10-21 18:18:12 +00:00
|
|
|
|
2014-04-02 14:49:39 +00:00
|
|
|
fprint = {}
|
|
|
|
jinfo = {}
|
|
|
|
|
2013-10-22 14:26:44 +00:00
|
|
|
# print out the goodies
|
|
|
|
infos.each do |k, v|
|
|
|
|
next if v.nil?
|
2014-04-02 14:49:39 +00:00
|
|
|
v = v.strip
|
|
|
|
next if v.length == 0
|
|
|
|
|
|
|
|
jinfo[k.gsub(/\s+/, '_')] = v
|
|
|
|
|
2013-10-22 14:26:44 +00:00
|
|
|
case k
|
|
|
|
when "os.name"
|
2013-10-22 16:15:16 +00:00
|
|
|
vprint_line(" OS: #{v}")
|
2014-04-02 14:49:39 +00:00
|
|
|
fprint['os.product'] = v
|
2013-10-22 14:26:44 +00:00
|
|
|
when "os.version"
|
2013-10-22 16:15:16 +00:00
|
|
|
vprint_line(" OS Version: #{v}")
|
2014-04-02 14:49:39 +00:00
|
|
|
fprint['os.version'] = v
|
2013-10-22 14:26:44 +00:00
|
|
|
when "sun.os.patch.level"
|
2013-10-22 16:15:16 +00:00
|
|
|
vprint_line(" Patch Level: #{v}")
|
2013-10-22 14:26:44 +00:00
|
|
|
when "os.arch"
|
2013-10-22 16:15:16 +00:00
|
|
|
vprint_line(" Arch: #{v}")
|
2014-04-02 14:49:39 +00:00
|
|
|
fprint['os.arch'] = v
|
2013-10-22 14:26:44 +00:00
|
|
|
when "user.name"
|
2013-10-22 16:15:16 +00:00
|
|
|
vprint_line(" User: #{v}")
|
2013-10-22 14:26:44 +00:00
|
|
|
when "USERDOMAIN"
|
2013-10-22 16:15:16 +00:00
|
|
|
vprint_line(" Domain: #{v}")
|
2014-04-02 14:49:39 +00:00
|
|
|
fprint['host.domain'] = v
|
2013-10-22 14:26:44 +00:00
|
|
|
when "COMPUTERNAME"
|
2013-10-22 16:15:16 +00:00
|
|
|
vprint_line(" Computer Name: #{v}")
|
2014-04-02 14:49:39 +00:00
|
|
|
fprint['host.name'] = v
|
2013-10-22 14:26:44 +00:00
|
|
|
when "SystemDrive"
|
|
|
|
vprint_line(" System Drive: #{v}")
|
|
|
|
when "SHELL"
|
2013-10-22 16:15:16 +00:00
|
|
|
vprint_line(" Shell: #{v}")
|
2013-10-22 14:26:44 +00:00
|
|
|
when "TEMP"
|
2013-10-22 16:15:16 +00:00
|
|
|
vprint_line(" Temp Directory: #{v}")
|
2013-10-22 14:26:44 +00:00
|
|
|
when "TMP"
|
2013-10-22 16:15:16 +00:00
|
|
|
vprint_line(" Temp Directory: #{v}")
|
2013-10-22 14:26:44 +00:00
|
|
|
when "user.home"
|
|
|
|
vprint_line(" Home Directory: #{v}")
|
|
|
|
when "user.language"
|
|
|
|
vprint_line(" Language: #{v}")
|
2014-04-02 14:49:39 +00:00
|
|
|
fprint['os.language'] = v
|
2013-10-22 14:26:44 +00:00
|
|
|
when "user.country"
|
|
|
|
vprint_line(" Country: #{v}")
|
|
|
|
when "user.timezone"
|
|
|
|
vprint_line(" Timezone: #{v}")
|
|
|
|
end
|
|
|
|
end
|
2014-04-02 14:49:39 +00:00
|
|
|
|
|
|
|
# Report a fingerprint.match for OS fingerprinting support, tied to this service
|
|
|
|
report_note(:host => rhost, :port => rport, :proto => 'tcp', :ntype => 'fingerprint.match', :data => fprint)
|
2014-12-12 12:16:21 +00:00
|
|
|
|
2014-04-02 14:49:39 +00:00
|
|
|
# Report a jenkins information note for future analysis, tied to this service
|
|
|
|
report_note(:host => rhost, :port => rport, :proto => 'tcp', :ntype => 'jenkins.info', :data => jinfo)
|
|
|
|
|
2015-07-11 18:40:21 +00:00
|
|
|
vprint_line
|
2013-10-22 14:26:44 +00:00
|
|
|
end
|
2013-09-17 14:47:59 +00:00
|
|
|
end
|