Cybersecurity / Incident anatomy / Research retrospective

SharePoint ToolShell: One Request, Two Bugs, and the Patch Bypasses

What began as a Pwn2Own exploit chain became a global SharePoint incident. The interesting part isn't only the RCE. It's how authentication assumptions, deserialization, incomplete fixes and stolen machine keys combined into a much longer security story.

CybersecuritySharePointToolShellRCEPatch bypassIncident response

1. The chain: authentication first, execution second

In May 2025, researchers from Viettel Cyber Security demonstrated a chained, unauthenticated remote-code-execution exploit against on-premises SharePoint at Pwn2Own Berlin. Its impact came from composing two weaknesses: a spoofing/authentication-control weakness and a code-execution path involving unsafe processing of SharePoint Web Part data. Each bug had a different role; together they became ToolShell. [1]

CVERoleWhy it mattered
CVE-2025-49706Spoofing / access-control weaknessHelped an unauthenticated request reach a sensitive SharePoint workflow.
CVE-2025-49704Remote code executionEnabled code execution through unsafe processing of attacker-controlled data.
CVE-2025-53771Later spoofing / path-handling variantRepresents a way around earlier security assumptions.
CVE-2025-53770Later RCE variant, CVSS 9.8Critical follow-on flaw used in the revised exploitation story.

Much of the public discussion focuses on ToolPane.aspx and SharePoint's handling of Web Parts. The vulnerability wasn't simply “a strange URL.” It arose from the interaction between request classification, authorization, and processing of untrusted serialized .NET state. [2] [3]

One-request RCE does not mean one-bug RCE. The request could cross an authorization boundary and reach an execution-capable component in a single transaction; the underlying exploit chain still involved multiple security failures.

2. Why the initial patches were not the end

Microsoft released fixes for the original pair in the July 2025 security cycle. Researchers subsequently identified bypass variants, which were assigned CVE-2025-53770 and CVE-2025-53771. At a high level, the new paths exposed inconsistent request-path canonicalization and authorization checks as a problem: a path that appears equivalent to a human may pass through different routing or security logic inside the application. [2] [4]

The important engineering lesson is not to blacklist one specially constructed path and assume the system is safe. Access-control decisions must operate on a consistently normalized representation of the request, and the deserialization sink must remain protected independently of routing checks.

Attackers were also interested in SharePoint's ASP.NET machine keys. In public incidents, web shells such as spinstall0.aspx were used to retrieve cryptographic key material. That threatens more than the original exploit window: stolen keys can undermine ViewState validation even after a vulnerability is patched. Later research described variants capable of exposing sensitive details directly from memory, without relying on a dropped web shell as the only evidence. [3] [5]

3. The incident timeline

May 2025: Viettel researchers demonstrate the original chained exploit at Pwn2Own Berlin. [1]

July 8–9, 2025: The original issues are addressed in Microsoft's July security release cycle. [2]

July 14–19: Public reproduction and active-exploitation investigations raise the urgency. Eye Security later clarified that its earliest observed waves involved the original CVE pair, not necessarily the subsequently assigned bypass pair. This distinction matters when describing what was actually zero-day versus already patched. [3]

July 19–21: Microsoft publishes additional guidance and emergency fixes addressing the follow-on variants. [2]

Late July: Threat intelligence documents key-theft techniques, web shells, and post-exploitation activity; defenders are urged to assess compromise and rotate machine keys, not merely install updates. [4] [5]

4. Validation notes: what the HTTP responses actually showed

I reviewed several authorized SharePoint testing runs with variations in request routing and header handling. The notes described 404 Not Found, 401 Unauthorized, connection resets associated with security controls, and a later 200 OK response containing server-environment information. The SOC reportedly confirmed Trend Micro alerts for some blocked attempts.

Read the evidence narrowly. A reset can indicate interception rather than patch status. A 401 or 404 is not proof the entire vulnerability chain is impossible. Conversely, a 200 carrying environment-like data is an investigation lead, not enough by itself to assert widespread compromise. The original notes reach different conclusions across test rounds, so the public account does not invent a single definitive result.

These publication copies permanently cover the Host header, raw payload, internal host/IP details, and sensitive response contents. They are illustrative test evidence, not publicly reusable exploitation instructions.

Anonymized SharePoint test request with connection reset
Figure 1. Connection reset during an initial request variation. Target host and payload are redacted.
Second anonymized SharePoint connection reset response
Figure 2. A second attempt showing a similar reset outcome.
Third anonymized request reset screenshot
Figure 3. A separate request variation with blocked or interrupted connectivity.
Anonymized response with protected server environment details
Figure 4. Response data existed in this capture. All server and local-environment details are removed.
HTTP 200 response with sensitive response body fully removed
Figure 5. HTTP 200 with details redacted. This deserves triage alongside IIS, EDR and SharePoint logs.

5. What the supporting scripts did

The accompanying research material included four test artifacts. Two are serialized SharePoint request bodies and two are Python runners used to submit requests, follow up with a check, and collect response artifacts. The runners support an HTTP proxy, per-host status logging and spreadsheet export. The original files are not distributed on this public site because they contain executable exploit content and could expose target-specific artifacts.

payload.txt

Initial serialized request body used in early testing.

payload4.txt

Later request-body variant studied during bypass validation.

exploit-0-day-initial.txt

Python test runner: target inventory, request/response collection and results file.

exploit-0-day4.txt

Updated Python runner for the later test variant and evidence collection.

Public-safe outline: inventory → authorized validation → collect status and security telemetry → correlate evidence → confirm patch level → rotate keys where required. Request bodies and host lists remain private.

Publication boundary: The original four artifacts were reviewed for this section. The excerpts below document their structure and research purpose. Live serialized exploit bytes, the executable delivery path, target lists, and retrieval details are withheld to avoid publishing a reusable pre-auth RCE chain or exposing tested systems.

The two generations of the experiment

The first pair (payload.txt and exploit-0-day-initial.txt) forms the initial request-body and runner combination. The second pair (payload4.txt and exploit-0-day4.txt) changes the request-body construction and request-routing variant. This is not evidence that the second generation universally bypassed all defenses: each response must be interpreted alongside firewall/WAF decisions, IIS logs, SharePoint logs and endpoint telemetry.

ArtifactActual purpose in the uploaded fileSafe-to-publish details
payload.txtFirst serialized, URL-encoded Web Part request bodyForm-encoded control properties and a compressed serialized object; raw encoded bytes withheld
payload4.txtLater request-body variant used in comparative validationAlternative wrapper and embedded object content; embedded data withheld
exploit-0-day-initial.txtPython runner reading inventory and body, posting a request, capturing responsesInput handling, request metadata, error handling and Excel output workflow
exploit-0-day4.txtPython runner using an updated routing/header variantSame reporting workflow, updated request variant and inventory file

Sanitized payload layout — not executable

The encoded source bodies contain SharePoint-specific control fields that carry serialized .NET component state. The recognizable structural layer can be discussed without distributing its embedded object graph.

payload.txt / payload4.txt — field-level layout only
Content-Type: application/x-www-form-urlencoded
Host: [REDACTED]
Referer: [ROUTING VARIANT WITHHELD]

MSOTlPn_Uri = [CONTROL-TEMPLATE VALUE REDACTED]
MSOTlPn_DWP = [URL-ENCODED WEB-PART MARKUP REDACTED]
               [SERIALIZED / COMPRESSED OBJECT REMOVED]

# Different request wrappers were compared.
# No payload bytes, exploit gadget chain or target information are published.

Sanitized Python runner — representative structure

Both uploaded scripts are Python HTTP clients, rather than bespoke implants. They read a private scope inventory and a payload file, submit a test transaction through a configured HTTP proxy, record HTTP status codes, save full raw response artifacts, and write a summary workbook. The example below represents their control and evidence-handling logic; it deliberately replaces the exploit request with a non-executable placeholder.

exploit-0-day-initial.txt / exploit-0-day4.txt — redacted workflow
import csv
from pathlib import Path

AUTHORIZED_SCOPE = "[PRIVATE INVENTORY]"
REQUEST_BODY = "[WITHHELD EXPLOIT DATA]"
EVIDENCE_DIR = Path("responses")
REPORT_FILE = "[PRIVATE RESULTS WORKBOOK]"

# 1. Read the authorized target list and preserve scope.
# 2. Select one of two historical request variants.
# 3. Submit the request through the testing proxy.
#    [DELIVERY CODE AND TARGET PATH OMITTED]
# 4. Record the observed status or connection exception.
# 5. Store raw responses securely for triage.
# 6. Correlate with proxy/WAF, IIS and EDR telemetry.
# 7. Export a per-host matrix for manual adjudication.

# This is a non-executable outline, not a runnable exploit.

Why four files were useful

Separating runner logic from request bodies made a focused comparison possible: change the request variant while keeping evidence capture broadly consistent. A difference between 401, 404, connection reset and 200 is a test observation, not an automatic pass/fail verdict. A reset can be caused by a reverse proxy. A 200 can come from an application error page or a genuinely reached code path. Corroboration matters more than the status code.

6. Original field notes: hypothesis, tests, evidence

The screenshots below are archival editorial notes and a public article draft, not screenshots of an authenticated target and not proof of successful code execution. They document the hypotheses considered: chained weakness, incomplete patch handling, machine-key risk, and why revisiting assumptions after a patch matters. They also show the four research artifacts being tracked separately.

Anonymized archived field note describing the SharePoint ToolShell exploit chain and patch-bypass investigation
Research note: the proposed chain, patch-bypass hypothesis and threat-intelligence chronology. The image contains commentary, not an active HTTP exchange.
Cropped screenshot of the blog's research artifact inventory on mobile
Publication draft showing how the original two request bodies and two scripts were categorized. Phone chrome and the browser URL bar have been excluded.

From those notes, the research approach can be summarized as:

Historical exposure lead→Primary-source hypothesis→Authorized scope→Controlled variation→Multi-layer evidence→Defensive decision

Generation and testing logic: the original Pwn2Own research and subsequent reporting suggested which security boundary needed investigation. One variant served as a baseline; the second exercised a different request-processing interpretation. The Python harness kept the inventory, status collection and response archiving repeatable. The HTTP response alone was never the complete security verdict; comparing it with blocking telemetry and server-side observations was the point.

What this does not establish: the screenshots do not document a specific LeakIX snapshot identifier, retrieval timestamp, affected hostnames, or a controlled A/B test of any named XDR product. This article therefore does not fabricate those identifiers or attribute an unverified protection failure to a particular vendor.

7. The source trail: how the public research became two test variants

This section reconstructs the research path from my archived notes, the two Python runners, and the related public posts. It matters because the first and second scripts did not come from an invented exploit technique: they grew out of a chain of reproducible public observations, an evolving proof of concept, and a subsequent finding that changed the expected evidence. The public Gist I referenced belongs to gboddin (sometimes misremembered as “Gibbon”); the patch-analysis posts are by Soroush Dalili (@irsdl). The X links below are preserved exactly as recorded in the original notes.

Two different clocks: dates in the chronology below are source publication/incident dates, not necessarily the precise date or minute I ran a script. The original notes describe rounds of testing, including “yesterday night” and “today morning,” but contain no verifiable absolute execution timestamps. My testing followed the first public payload and then the July 23 research update; I cannot legitimately assign invented times to individual scans or LeakIX historical queries.

July 14, 2025 — the reproducibility signal. CODE WHITE's public reproduction highlighted that ToolShell was no longer only a Pwn2Own-stage claim. It prompted careful examination of where SharePoint treated a request as authorized and where untrusted Web Part state would be processed. [16]

July 18–19 — active exploitation and response clues. Eye Security documented real attacks and the request/response artifacts defenders could look for. Importantly, Eye later clarified that its July 17–19 waves concerned the original 49704/49706 chain, not necessarily the newly numbered bypass CVEs. That correction is part of the source history, not a footnote to discard. [3]

July 20 — Soroush Dalili's authentication research. My notes cite @irsdl's post while discussing the role of request-header handling in the access-control weakness. This informed the first controlled test matrix: maintain one evidence workflow while varying how the request was classified, and observe authorization responses and defensive interception. [11]

July 21 — patch-bypass interpretation. The follow-up @irsdl analysis, together with Wiz's write-up, directed attention toward inconsistent route normalization and authorization decisions. It supplied a hypothesis for changing the request representation rather than assuming a first test failure meant the underlying problem was closed. The mechanics are discussed here, without publishing the bypass recipe. [12]

July 21 — the public proof-of-concept baseline. My source notes explicitly attribute the first payload trial to gboddin's SharePoint ToolShell Gist and also link @leak_ix's contemporaneous post. I used the public request-body concept to create payload.txt and the first batch-testing harness, exploit-0-day-initial.txt. [10] [13]

First testing round — establish observable behavior. The Python harness read a scoped host inventory and request-body file, sent tests through a local inspection proxy, recorded HTTP outcomes, saved responses, and exported a workbook. The archived results note 401, 404, and connection resets; the SOC reportedly correlated some resets with Trend Micro alerts. This is evidence that those attempts did not establish exploitation, not proof that every system was fully remediated or that every security product stopped all variants.

July 23 — a change in what the attacker needed to leave behind. @leak_ix's second post and the dated article “Using their own weapons for defense — A SharePoint story” described research into in-memory extraction of SharePoint secrets, without depending on a persistent web-shell file. This invalidated the idea that the absence of one named ASPX artifact alone could rule out the technique. [14] [15]

Second testing round — change the hypothesis, retain the evidence trail. After that research, I studied payload4.txt and revised the Python runner as exploit-0-day4.txt. Its role was to compare another request-processing variant and the expected response-carried evidence against the earlier file-artifact-oriented test, while keeping per-host response archiving and spreadsheet reporting. Some later captures show 200 OK with server-environment output; that observation warrants investigation, but a status code alone does not prove the complete exploit chain succeeded.

What changed in the logic — and why

Research inputHypothesis testedScript / artifact relationshipWhat required independent confirmation
Eye Security + @irsdl + gboddin GistDoes the first public request family reach sensitive SharePoint processing, and what blocks it?payload.txt + exploit-0-day-initial.txt; batch inventory, proxy observation, status and response loggingWhether a reset came from Trend Micro, another control, or network conditions; whether the host was patched; whether any dangerous code path was reached
@irsdl patch-analysis threadCan differing route representations produce different authorization or routing outcomes?Request-variant investigation guided by public patch-analysis research; the harness maintained comparable evidence fieldsWhich layer normalized the request, which access-control decision applied, and whether a difference signified real bypass
LeakIX, July 23 in-memory research and honeypot telemetryIs the absence of a dropped web shell a sufficient negative test when sensitive information could be returned directly?payload4.txt + exploit-0-day4.txt; later request-body/routing variant with preserved response recordingWhether sensitive key material was actually exposed, whether artifacts were created, and whether endpoint, IIS or application telemetry corroborated the response
Original research & Eye telemetry→irsdl bypass analysis→gboddin public baseline→First Python runner→LeakIX July 23 findings→Second runner / response validation

The engineering idea: treat public threat intelligence as a way to refine the question being tested, not as a library of signature strings. The first question was whether a known request family crossed the security boundary. The second was whether changes in route processing and memory-resident behavior changed the observable outcome. The supporting scripts were written to make those observations repeatable; their names mark two research iterations, not proof of exploitation.

Why point-in-time LeakIX data mattered: the dated July 21 and July 23 publications let me anchor the public attacker-method timeline rather than retrospectively projecting today's understanding into an earlier test. LeakIX's honeypot captures and historical exposure research are complementary, but different, evidence sources. My archive does not preserve the exact moment I queried LeakIX's exposed-service index, any snapshot ID, or the individual host records; consequently none are implied here.

Source integrity: Direct X pages do not always expose tweet text to unauthenticated crawlers. These precise X permalinks come from the original exported research notes and are presented as attributed research references. Publicly dated investigations by Eye Security and LeakIX independently corroborate the broader sequence. The original test payload bytes, target lists, machine keys and exploit-delivery instructions remain omitted from this public retrospective.

8. Point-in-time intelligence, decoys, and the defender's time advantage

I used LeakIX as a point-in-time exposure intelligence input: historical observations of internet-facing technologies helped prioritize where to look and which period to cross-reference against the July 2025 ToolShell timeline. The crucial distinction is that an exposure snapshot establishes only observed service exposure at a recorded point. It does not prove that a system was vulnerable, attacked, or compromised. The working notes shared for this publication do not include the exact LeakIX query, exported event ID or timestamp, so those specifics are not asserted here.

That temporal lens matters. The landscape on July 7 was not the same as the landscape after July 8 Patch Tuesday or after the emergency updates later in July. Current inventories often erase the history defenders need most. A useful correlation asks what a service looked like then, which security release was current then, and what real attack telemetry had surfaced then. Next comes authorization, a controlled test and independent evidence—not automatic exploitation of internet search results.

Why independent decoys do important work

watchTowr's Attacker Eye model combines realistic decoys and live exploit telemetry with exposure validation. Defused Cyber documented a particularly relevant SharePoint study: 20 SharePoint-like honeypots were deployed after the July 20 advisory, and 91 ToolShell attempts were recorded in the first 48 hours. The team reported that more than half of the submitted samples were marked clean in VirusTotal at observation time, and one attack arrived roughly 30 minutes before a public proof of concept. [6] [7]

That is why decoy operators, exposure-intelligence researchers and incident responders do vital, often thankless work. They can surface a real request shape, suspicious timing, a fresh indicator, or a bypass hypothesis before a static signature or a polished vendor advisory catches up. Their observations help defenders reproduce in a safe lab, build detections, prioritize patches and ask sharper questions of security products. They are not infallible, but they move the evidence clock forward.

The best threat intelligence does not just name a CVE. It reduces the time between an attacker trying something and a defender being able to measure and stop it.

None of this requires treating exposed servers identified by LeakIX as test targets without permission. Historical exposure leads, honeypot traffic, proof-of-concept research, and organization-owned validation are four different kinds of evidence.

9. “Protected on day one” — marketing claim or tested outcome?

ToolShell is a good case study in why I distrust blanket product statements that customers were “protected on day one.” Such statements can be meaningful when scoped to a particular detection, platform configuration, product version, and tested attack behavior. They are not a mathematical guarantee against every equivalent request path, deserialization variant, bypass, or subsequent key theft.

For balance, several vendors published substantial real-world coverage: Microsoft documented Defender XDR detections across the chain, while CrowdStrike reported blocking hundreds of exploitation attempts. These are not inherently false claims; they are evidence of coverage in observed circumstances, not proof of universal prevention. [4] [8]

Vendor / control claimWhat a defender should actually verify
“We have a detection for ToolShell.”Which variant, time window, telemetry source, parser, rule version, and required features?
“Blocked initial exploitation.”Did an upstream WAF reset the connection, or did the endpoint agent interrupt malicious behavior? Was malicious code ever reached?
“Customers are protected.”Do alerts cover altered routing and encoded request representations, or only known IOC strings and one request form?
“Patched and safe.”Was the emergency patch installed, compromise hunted and machine-key material rotated if appropriate?
“No alerts.”Was the sensor deployed, healthy, configured to prevent, and actually receiving the needed event stream?

My threat-modeling stance is simple: test the security invariant, not merely the signature. Could an unauthorized request be misclassified as authorized? Could a representation change reach the same dangerous sink? Could previously stolen machine keys remain usable after patching? An XDR may stop a post-exploitation behavior while missing the triggering HTTP request; a WAF may block a known request while endpoint telemetry remains blind. Both results should be reported honestly.

Conclusion: product detections are valuable, but their scope must be falsifiable. Red-team claims should survive blue-team observation, and blue-team claims should survive new variants. Vendor brochures are not compensating controls.

10. The defense lesson: patch, investigate, rotate

Microsoft says this incident affected supported on-premises SharePoint versions; SharePoint Online in Microsoft 365 was not affected. The remediation approach for an exposed or potentially compromised server should distinguish prevention from incident response. [2]

ControlWhy it matters
Apply supported security updatesInstall the appropriate cumulative security updates and verify product/build and language-pack requirements.
Review exposure and incident telemetryCorrelate WAF/proxy events with IIS, ULS, EDR, AMSI and suspicious child-process activity.
Hunt for artifacts and unusual behaviorInvestigate unexpected ASPX files, requests to sensitive SharePoint components, suspicious IIS worker activity and machine-key access.
Rotate ASP.NET machine keys and restart IISKeys may remain useful to attackers after a patch; follow Microsoft's coordinated farm rotation procedure.
Contain confirmed compromiseIsolate affected systems, preserve evidence and examine persistence and lateral movement before returning them to service.

The real failure mode was an assumption cascade: routing was treated as authorization, validation was trusted too far downstream, and a patch appeared to close an entry path while leaving exploitable equivalences. A successful response code is not the same thing as a verified exploit — and a blocked response is not the same thing as a clean server.

References and further reading

  1. Viettel Cyber Security — SharePoint ToolShell research
  2. Microsoft Security Response Center — customer guidance, updates and machine-key rotation
  3. Eye Security — SharePoint Under Siege (including later chronology correction)
  4. Microsoft Threat Intelligence — observed exploitation and detection
  5. Cyber Security Agency of Singapore — compromised SharePoint remediation guide
  6. Defused Cyber — N-Day SharePoint Exploit Intelligence with Honeypots (July 27, 2025)
  7. watchTowr — Attacker Eye honeypots, exposure and exploit validation overview
  8. CrowdStrike — claimed detection and blocking of ToolShell exploitation attempts
  9. LeakIX — public exposed-service and internet observation intelligence
  10. gboddin — public ToolShell proof-of-concept Gist, cited in the July 21 research notes
  11. Soroush Dalili (@irsdl) — July 20, 2025 authentication / request-header research post
  12. Soroush Dalili (@irsdl) — July 21, 2025 patch-bypass analysis post
  13. LeakIX (@leak_ix) — July 21, 2025 post referenced in the original test notes
  14. LeakIX (@leak_ix) — July 23, 2025 follow-up research post
  15. LeakIX — Using their own weapons for defense: A SharePoint story (July 23, 2025)
  16. CODE WHITE — July 14, 2025 public reproduction

Editorial note: This is a retrospective combining public research with anonymized test notes and non-executable publication excerpts. The dated LeakIX and X publications are linked above; the separate LeakIX exposure-index queries and exact private scan timestamps do not have independently verifiable snapshot IDs or timestamps in the archived notes. Internal system identifiers, raw attack payloads, cryptographic material and detailed responses have been deliberately withheld. The reported outcomes should not be taken as a certification of any organization's current patch or compromise status.