Module: Msf::Exploit::Remote::HTTP::Exchange

Includes:
Msf::Exploit::Remote::HttpClient
Defined in:
lib/msf/core/exploit/remote/http/exchange.rb

Overview

This module provides a way of interacting with Exchange installations

Instance Attribute Summary

Attributes included from Msf::Exploit::Remote::HttpClient

#client, #cookie_jar

Instance Method Summary collapse

Methods included from Msf::Exploit::Remote::HttpClient

#basic_auth, #cleanup, #configure_http_login_scanner, #connect, #connect_ws, #deregister_http_client_options, #disconnect, #download, #full_uri, #handler, #http_fingerprint, #initialize, #lookup_http_fingerprints, #normalize_uri, #path_from_uri, #peer, #proxies, #reconfig_redirect_opts!, #request_opts_from_url, #request_url, #rhost, #rport, #send_request_cgi, #send_request_cgi!, #send_request_raw, #service_details, #setup, #ssl, #ssl_version, #strip_tags, #target_uri, #validate_fingerprint, #vhost

Methods included from Auxiliary::Report

#active_db?, #create_cracked_credential, #create_credential, #create_credential_and_login, #create_credential_login, #db, #db_warning_given?, #get_client, #get_host, #inside_workspace_boundary?, #invalidate_login, #mytask, #myworkspace, #myworkspace_id, #report_auth_info, #report_client, #report_exploit, #report_host, #report_loot, #report_note, #report_service, #report_vuln, #report_web_form, #report_web_page, #report_web_site, #report_web_vuln, #store_cred, #store_local, #store_loot

Methods included from Metasploit::Framework::Require

optionally, optionally_active_record_railtie, optionally_include_metasploit_credential_creation, #optionally_include_metasploit_credential_creation, optionally_require_metasploit_db_gem_engines

Instance Method Details

#exchange_get_version(exchange_builds: nil) ⇒ Rex::Version?

Get the Exchange version number.

Parameters:

  • exchange_builds (Array) (defaults to: nil)

    Array containing vulnerable build numbers to check for. Used to narrow scope of brute force approach used if all other enumeration approaches fail.

Returns:

  • (Rex::Version, nil)

    The Exchange version if it was able to be recovered. Nil otherwise

See Also:


57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/msf/core/exploit/remote/http/exchange.rb', line 57

def exchange_get_version(exchange_builds: nil)
  # First check target is actually Exchange
  return nil unless target_running_exchange?

  # If no exchange_builds parameter, call get_exchange_builds to build the Exchange version array.
  # Otherwise use supplied exchange_builds parameter after first checking to make sure its a non-empty Array.
  exchange_builds = get_exchange_builds if !exchange_builds.is_a?(Array) || exchange_builds.nil?

  # Unless lets try a cheap way of doing this via a leak of the X-OWA-Version header.
  # If we get this we know the version number for sure and we can skip a lot of leg work.
  res = send_request_cgi(
    'method' => 'GET',
    'uri' => normalize_uri(target_uri.path, '/owa/service')
  )

  unless res
    print_error('Target did not respond!')
    return nil
  end

  if res.headers['X-OWA-Version']
    build = res.headers['X-OWA-Version']
    return Rex::Version.new(build)
  end

  # Next, determine if we are up against an older version of Exchange Server where
  # the /owa/auth/logon.aspx page gives the full version. Recent versions of Exchange
  # give only a partial version without the build number.
  res = send_request_cgi(
    'method' => 'GET',
    'uri' => normalize_uri(target_uri.path, '/owa/auth/logon.aspx')
  )

  unless res
    print_error('Target did not respond!')
    return nil
  end

  if res.code == 200 && res.body =~ %r{/owa/(?>auth/)?(?<build>\d+(?>\.\d+){3})}
    return Rex::Version.new(Regexp.last_match('build'))
  end

  # Next try @tseller's way and try /ecp/Current/exporttool/microsoft.exchange.ediscovery.exporttool.application
  # URL which if successful should provide some XML with entries like the following:
  #
  # <assemblyIdentity name="microsoft.exchange.ediscovery.exporttool.application"
  # version="15.2.986.5" publicKeyToken="b1d1a6c45aa418ce" language="neutral"
  # processorArchitecture="msil" xmlns="urn:schemas-microsoft-com:asm.v1" />
  #
  # This only works on Exchange Server 2013 and later and may not always work, but if it
  # does work it provides the full version number so its a nice strategy.
  res = send_request_cgi(
    'method' => 'GET',
    'uri' => normalize_uri(target_uri.path, '/ecp/current/exporttool/microsoft.exchange.ediscovery.exporttool.application')
  )

  unless res
    print_error('Target did not respond!')
    return nil
  end

  if res.code == 200 && res.body =~ /name="microsoft.exchange.ediscovery.exporttool" version="(?<build>\d+(?>\.\d+){3})"/
    return Rex::Version.new(Regexp.last_match('build'))
  end

  # Finally, try a variation on the above and use a well known trick of grabbing /owa/auth/logon.aspx
  # to get a partial version number, then use the URL at /ecp/<version here>/exporttool/. If we get a 200
  # OK response, we found the target version number, otherwise we didn't find it.
  #
  # Props go to @jmartin-r7 for improving my original code for this and suggestion the use of
  # canonical_segments to make this close to the Rex::Version code format. Also for noticing that
  # version_range is a Rex::Version object already and cleaning up some of my original code to simplify
  # things on this premise.

  exchange_builds.each do |version|
    res = send_request_cgi(
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, "/ecp/#{version}/exporttool/")
    )

    unless res
      print_error('Target did not respond!')
      return nil
    end

    if res && res.code == 200
      return Rex::Version.new(version)
    end
  end

  # If we reach here we couldn't find the Exchange Server version, so just return nil to indicate this.
  nil
end

#get_exchange_buildsArray

Build the array of Exchange Servers using the JSON file at data/exchange_versions.json. If the array has already been built then return it.

Returns:

  • (Array)

    Array of Exchange Server versions retrieved from the data/exchange_versions.json JSON file.


14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/msf/core/exploit/remote/http/exchange.rb', line 14

def get_exchange_builds
  # If we already built the exchange builds array, then just return it.
  return @exchange_builds if @exchange_builds

  @exchange_builds = []
  raw_exchange_build_json = JSON.parse(File.read(::File.join(Msf::Config.data_directory, 'exchange_versions.json'), mode: 'rb'))

  for server_version in raw_exchange_build_json['exchange_builds']
    for build in server_version['builds']
      @exchange_builds << build
    end
  end
end

#target_running_exchange?Boolean

Determine if the target is running Exchange Server or not

Returns:

  • (Boolean)

    True if the target is running Exchange Server, false if not.


31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/msf/core/exploit/remote/http/exchange.rb', line 31

def target_running_exchange?
  res = send_request_cgi(
    'method' => 'GET',
    'uri' => normalize_uri(target_uri.path, '/owa/auth/logon.aspx')
  )

  unless res
    print_error('Target did not respond!')
    return false
  end

  if res && res.code == 200 && (res.body =~ /function IsOwaPremiumBrowser/ || res.body =~ /To use Outlook, browser settings must allow scripts to run./)
    print_status('Target is an Exchange Server!')
    true
  else
    print_status('Target is NOT an Exchange Server!')
    false
  end
end