Add in Exchange Version mixin and module example
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
class Exploit
|
||||
class Remote
|
||||
module HTTP
|
||||
# This module provides a way of interacting with Exchange installations
|
||||
module Exchange
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
|
||||
|
||||
# Taken from https://docs.microsoft.com/en-us/exchange/new-features/build-numbers-and-release-dates
|
||||
@@exchange_server_4_0_builds = ["4.0.996", "4.0.995", "4.0.994", "4.0.993", "4.0.838", "4.0.837"]
|
||||
@@exchange_server_5_0_builds = ["5.0.1460", "5.0.1458", "5.0.1457"]
|
||||
@@exchange_server_5_5_builds = ["5.5.2653", "5.5.2650", "5.5.2448", "5.5.2232", "5.5.1960"]
|
||||
@@exchange_server_2000_builds = ["6.0.6620.7", "6.0.6620.5", "6.0.6603", "6.0.6556", "6.0.6487", "6.0.6249", "6.0.5762", "6.0.4712", "6.0.4417"]
|
||||
@@exchange_server_2003_builds = ["6.5.7654.4", "6.5.7653.33", "6.5.7683", "6.5.7226", "6.5.6944"]
|
||||
@@exchange_server_2007_builds = ["8.3.517.0", "8.3.502.0", "8.3.485.1", "8.3.468.0", "8.3.459.0", "8.3.445.0", "8.3.417.1", "8.3.406.0", "8.3.389.2", "8.3.379.2", "8.3.348.2", "8.3.342.4", "8.3.327.1", "8.3.298.3", "8.3.297.2", "8.3.279.6", "8.3.279.5", "8.3.279.3", "8.3.264.0", "8.3.245.2", "8.3.213.1", "8.3.192.1", "8.3.159.2", "8.3.137.3", "8.3.106.2", "8.3.83.6", "8.2.305.3", "8.2.254.0", "8.2.247.2", "8.2.234.1", "8.2.217.3", "8.2.176.2", "8.1.436.0", "8.1.393.1", "8.1.375.2", "8.1.359.2", "8.1.340.1", "8.1.336.1", "8.1.311.3", "8.1.291.2", "8.1.278.2", "8.1.263.1", "8.1.240.6", "8.0.813.0", "8.0.783.2", "8.0.754.0", "8.0.744.0", "8.0.730.1", "8.0.711.2", "8.0.708.3", "8.0.685.25"]
|
||||
@@exchange_server_2010_builds = ["14.3.513.0", "14.3.509.0", "14.3.496.0", "14.3.468.0", "14.3.461.1", "14.3.452.0", "14.3.442.0", "14.3.435.0", "14.3.419.0", "14.3.417.1", "14.3.411.0", "14.3.399.2", "14.3.389.1", "14.3.382.0", "14.3.361.1", "14.3.352.0", "14.3.336.0", "14.3.319.2", "14.3.301.0", "14.3.294.0", "14.3.279.2", "14.3.266.2", "14.3.248.2", "14.3.235.1", "14.3.224.2", "14.3.224.1", "14.3.210.2", "14.3.195.1", "14.3.181.6", "14.3.174.1", "14.3.169.1", "14.3.158.1", "14.3.146.0", "14.3.123.4", "14.2.390.3", "14.2.375.0", "14.2.342.3", "14.2.328.10", "14.3.328.5", "14.2.318.4", "14.2.318.2", "14.2.309.2", "14.2.298.4", "14.2.283.3", "14.2.247.5", "14.1.438.0", "14.1.421.3", "14.1.421.2", "14.1.421.0", "14.1.355.2", "14.1.339.1", "14.1.323.6", "14.1.289.7", "14.1.270.1", "14.1.255.2", "14.1.218.15", "14.0.726.0", "14.0.702.1", "14.0.694.0", "14.0.689.0", "14.0.682.1", "14.0.639.21"]
|
||||
@@exchange_server_2013_builds = ["15.0.1497.28", "15.0.1497.26", "15.0.1497.24", "15.0.1497.23", "15.0.1497.18", "15.0.1497.15", "15.0.1497.12", "15.0.1497.2", "15.0.1473.6", "15.0.1473.3", "15.0.1395.12", "15.0.1395.4", "15.0.1367.3", "15.0.1365.1", "15.0.1347.2", "15.0.1320.4", "15.0.1293.2", "15.0.1263.5", "15.0.1236.3", "15.0.1210.3", "15.0.1178.4", "15.0.1156.6", "15.0.1130.7", "15.0.1104.5", "15.0.1076.9", "15.0.1044.25", "15.0.995.29", "15.0.913.22", "15.0.847.64", "15.0.847.32", "15.0.775.38", "15.0.712.24", "15.0.620.29", "15.0.516.32"]
|
||||
@@exchange_server_2016_builds = ["15.1.2375.18", "15.1.2375.17", "15.1.2375.12", "15.1.2375.7", "15.1.2308.21", "15.1.2308.20", "15.1.2308.15", "15.1.2308.14", "15.1.2308.8", "15.1.2242.12", "15.1.2242.10", "15.1.2242.8", "15.1.2242.4", "15.1.2176.14", "15.1.2176.12", "15.1.2176.9", "15.1.2176.2", "15.1.2106.13", "15.1.2106.2", "15.1.2044.13", "15.1.2044.4", "15.1.1979.8", "15.1.1979.3", "15.1.1913.12", "15.1.1913.5", "15.1.1847.12", "15.1.1847.3", "15.1.1779.8", "15.1.1779.2", "15.1.1713.10", "15.1.1713.5", "15.1.1591.18", "15.1.1591.10", "15.1.1531.12", "15.1.1531.3", "15.1.1466.16", "15.1.1466.3", "15.1.1415.10", "15.1.1415.2", "15.1.1261.35", "15.1.1034.26", "15.1.845.34", "15.1.669.32", "15.1.544.27", "15.1.466.34", "15.1.396.30", "15.1.225.42", "15.1.225.16"]
|
||||
@@exchange_server_2019_builds = ["15.2.986.15", "15.2.986.14", "15.2.986.9", "15.2.986.5", "15.2.922.20", "15.2.922.19", "15.2.922.14", "15.2.922.13", "15.2.922.7", "15.2.858.15", "15.2.858.12", "15.2.858.10", "15.2.858.5", "15.2.792.15", "15.2.792.13", "15.2.792.10", "15.2.792.3", "15.2.721.13", "15.2.721.2", "15.2.659.12", "15.2.659.4", "15.2.595.8", "15.2.595.3", "15.2.529.13", "15.2.529.5", "15.2.464.15", "15.2.464.5", "15.2.397.11", "15.2.397.3", "15.2.330.11", "15.2.330.5", "15.2.221.18", "15.2.221.12", "15.2.196.0"]
|
||||
|
||||
# Full list of all known Exchange Server builds.
|
||||
@@exchange_builds = [*@@exchange_server_4_0_builds, *@@exchange_server_5_0_builds, *@@exchange_server_5_5_builds, *@@exchange_server_2000_builds, *@@exchange_server_2003_builds, *@@exchange_server_2007_builds, *@@exchange_server_2010_builds, *@@exchange_server_2013_builds, *@@exchange_server_2016_builds, *@@exchange_server_2019_builds]
|
||||
|
||||
# Get the Exchange version number.
|
||||
#
|
||||
# @see https://docs.microsoft.com/en-us/exchange/new-features/build-numbers-and-release-dates Exchange Version Numbers
|
||||
#
|
||||
# @return [Rex::Version, nil] The Exchange version if it was able to be recovered. Nil otherwise
|
||||
def exchange_get_version()
|
||||
=begin
|
||||
# First 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 && ((%r{/owa/(?<build>\d+\.\d+\.\d+\.\d+)} =~ res.body) || (%r{/owa/auth/(?<build>\d+\.\d+\.\d+\.\d+)} =~ res.body))
|
||||
return Rex::Version.new(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="\d+\.\d+\.\d+\.\d+"/
|
||||
build = res.body.match(/name="microsoft.exchange.ediscovery.exporttool" version="(\d+\.\d+\.\d+\.\d+)"/)[1]
|
||||
return Rex::Version.new(build)
|
||||
end
|
||||
=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
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+13
-95
@@ -13,6 +13,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Exploit::CmdStager
|
||||
include Msf::Exploit::Powershell
|
||||
include Msf::Exploit::Remote::HTTP::Exchange
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
@@ -134,104 +135,21 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
]
|
||||
end
|
||||
|
||||
# Credits to Alan David Foster for the assistance on this :)
|
||||
def vulnerable_build?(current_build_rex)
|
||||
vuln_builds.any? { |start_version, end_version| (current_build_rex >= start_version) && (current_build_rex < end_version) }
|
||||
end
|
||||
|
||||
def check
|
||||
# First 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
|
||||
return CheckCode::Unknown('Target did not respond to check.')
|
||||
current_build_rex = exchange_get_version()
|
||||
if current_build_rex.nil?
|
||||
return CheckCode::Unknown("Couldn't retrieve the target Exchange Server version!")
|
||||
end
|
||||
|
||||
if res.headers['X-OWA-Version']
|
||||
build = res.headers['X-OWA-Version']
|
||||
if vuln_builds.any? { |build_range| Rex::Version.new(build).between?(*build_range) }
|
||||
return CheckCode::Appears("Exchange Server #{build} is a vulnerable build.")
|
||||
else
|
||||
return CheckCode::Safe("Exchange Server #{build} is not a vulnerable build.")
|
||||
end
|
||||
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
|
||||
return CheckCode::Unknown('Target did not respond to check.')
|
||||
end
|
||||
|
||||
if res.code == 200 && ((%r{/owa/(?<build>\d+\.\d+\.\d+\.\d+)} =~ res.body) || (%r{/owa/auth/(?<build>\d+\.\d+\.\d+\.\d+)} =~ res.body))
|
||||
if vuln_builds.any? { |build_range| Rex::Version.new(build).between?(*build_range) }
|
||||
return CheckCode::Appears("Exchange Server #{build} is a vulnerable build.")
|
||||
else
|
||||
return CheckCode::Safe("Exchange Server #{build} is not a vulnerable build.")
|
||||
end
|
||||
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
|
||||
return CheckCode::Unknown('Target did not respond to check.')
|
||||
end
|
||||
|
||||
if res.code == 200 && res.body =~ /name="microsoft.exchange.ediscovery.exporttool" version="\d+\.\d+\.\d+\.\d+"/
|
||||
build = res.body.match(/name="microsoft.exchange.ediscovery.exporttool" version="(\d+\.\d+\.\d+\.\d+)"/)[1]
|
||||
if vuln_builds.any? { |build_range| Rex::Version.new(build).between?(*build_range) }
|
||||
return CheckCode::Appears("Exchange Server #{build} is a vulnerable build.")
|
||||
else
|
||||
return CheckCode::Safe("Exchange Server #{build} is not a vulnerable build.")
|
||||
end
|
||||
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.
|
||||
|
||||
vuln_builds.each do |version_range|
|
||||
return CheckCode::Unknown('Range provided is not iterable') unless version_range[0].canonical_segments[0..-2] == version_range[1].canonical_segments[0..-2]
|
||||
|
||||
prepend_range = version_range[0].canonical_segments[0..-2]
|
||||
lowest_patch = version_range[0].canonical_segments.last
|
||||
while Rex::Version.new((prepend_range.dup << lowest_patch).join('.')) <= version_range[1]
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, "/ecp/#{build}/exporttool/")
|
||||
)
|
||||
unless res
|
||||
return CheckCode::Unknown('Target did not respond to check.')
|
||||
end
|
||||
if res && res.code == 200
|
||||
return CheckCode::Appears("Exchange Server #{build} is a vulnerable build.")
|
||||
end
|
||||
|
||||
lowest_patch += 1
|
||||
end
|
||||
|
||||
CheckCode::Unknown('Could not determine the build number of the target Exchange Server.')
|
||||
if vulnerable_build?(current_build_rex)
|
||||
CheckCode::Appears("Exchange Server #{current_build_rex.to_s} is a vulnerable version!")
|
||||
else
|
||||
CheckCode::Safe("Exchange Server #{current_build_rex.to_s} does not appear to be a vulnerable version!")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user