· security  · 5 min read

Hunt SharePoint Exploits (Using Live Response)

A guide on critical SharePoint vulnerability CVE-2025-53770 and how to hunt for it using Advanced Hunting and Live Response to identify exploitation attempts via IIS logs.

Overview

Microsoft has disclosed a new critical RCE vulnerability in SharePoint Server 2016 and 2019 named CVE-2025-53770. The vulnerability chains two previously disclosed issues (CVE-2025-49706 and CVE-2025-49704) to first gain unauthenticated access and then upload a malicious program that extracts the server’s ASP.NET machine keys.

The attacker begins by sending a request to the server’s WebPart editor (ToolPane.aspx), spoofing the request headers to appear as if they were referred from SignOut.aspx. SharePoint therefore accepts the request as legitimate and issues an authenticated session. The attacker can then upload malicious .aspx files. We commonly see spinstall0.aspx, which is a web shell that extracts the ASP.NET keys. These keys can then be used to sign and execute further malicious commands.

Why Additional Hunting Is Necessary

Even when the server is fully patched and AMSI is enabled in full mode, per Microsoft’s guidance, exploitation attempts still hit the server. AMSI blocks them at runtime, but each attempt generates high-severity alerts, creating a lot of noise.

You cannot tell at first glance if an alert was the result of a blocked or successful attempt. By identifying the most frequent attacker IPs, you can block them at the firewall level, significantly reducing these noisy attempts and with that alert fatigue.

The Script and How to Use It

Find your SharePoint server(s) in the Microsoft 365 Defender portal and start a Live Response session. Upload the following script to the library and run it using run filename.ps1. Of course you can also execute it directly on the server if you have access.

# Finds all source IPs of IIS logs which match criteria of exploitation attempts of CVE-2025-53770

Import-Module WebAdministration

$daysBack = 14
$cutoff = (Get-Date).AddDays(-$daysBack)
$ipList = @{}
$logEntriesPerSite = @{}

# Ensure output directory exists
$outputDir = "C:\Temp"
if (-not (Test-Path $outputDir)) {
    New-Item -ItemType Directory -Path $outputDir | Out-Null
}

# Create log file
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$logFile = Join-Path $outputDir "IIS_SuspiciousIPs_$timestamp.log"

# Get all sites and their logging info
$sites = Get-ItemProperty "IIS:\Sites\*" | Where-Object {
    $_.logFile.logFormat -eq "W3C" -and $_.logFile.logExtFileFlags -match "ClientIP"
}

foreach ($site in $sites) {
    $siteName = $site.name
    $logDirRaw = $site.logFile.directory
    $logDir = $logDirRaw -replace '%SystemDrive%', $env:SystemDrive

    if (-not (Test-Path $logDir)) {
        Add-Content -Path $logFile -Value "[$siteName] Log path does not exist: $logDir"
        continue
    }

    $logSubDir = Join-Path $logDir "W3SVC$($site.id)"
    if (-not (Test-Path $logSubDir)) {
        Add-Content -Path $logFile -Value "[$siteName] Subfolder not found: $logSubDir"
        continue
    }

    $logFiles = Get-ChildItem -Path $logSubDir -Filter *.log | Where-Object { $_.LastWriteTime -ge $cutoff }
    $siteEntries = @()

    foreach ($file in $logFiles) {
        $lines = Get-Content $file.FullName | Where-Object { $_ -match "layouts" -and (($_ -match "spinstall0.aspx") -or ($_ -match "ToolPane.aspx" -and $_ -match "SignOut.aspx")) }

        foreach ($line in $lines) {
            $columns = $line -split '\s+'
            if ($columns.Length -ge 9) {
                $ip = $columns[8]
                if ($ip -match '^\d{1,3}(\.\d{1,3}){3}$') {
                    if (-not $ipList.ContainsKey($ip)) {
                        $ipList[$ip] = @()
                    }
                    $ipList[$ip] += $siteName

                    $siteEntries += $line
                }
            }
        }
    }

    if ($siteEntries.Count -gt 0) {
        $logEntriesPerSite[$siteName] = $siteEntries
    }
}

# Write summary to file
Add-Content -Path $logFile -Value "===== Distinct Suspicious Source IPs Detected ====="
foreach ($ip in $ipList.Keys | Sort-Object) {
    $associatedSites = ($ipList[$ip] | Sort-Object -Unique) -join ", "
    Add-Content -Path $logFile -Value "$ip (seen on: $associatedSites)"
}

Add-Content -Path $logFile -Value "`n===== Detailed Log Matches per Site ====="
foreach ($siteName in $logEntriesPerSite.Keys) {
    Add-Content -Path $logFile -Value "`n--- [$siteName] ---"
    foreach ($entry in $logEntriesPerSite[$siteName]) {
        Add-Content -Path $logFile -Value $entry
    }
}

# Output to console
if ($ipList.Count -eq 0) {
    Write-Output "No suspicious entries found across any site."
} else {
    Write-Output "Suspicious IPs detected across sites:"
    ($ipList.Keys | Sort-Object) | ForEach-Object { Write-Output $_ }
    Write-Output "`nFor detailed log lines and site mapping, please download the full log file from:"
    Write-Output $logFile
}

The script generates a summary of suspicious IPs and all log entries containing a potential exploit attempt, grouped by site. Use the path shown at the end to retrieve the full log with the getfile command.

Example log output:

===== Distinct Suspicious Source IPs Detected =====
10.1.1.100 (seen on: SharePoint site)

===== Detailed Log Matches per Site =====

--- [SharePoint site] ---
2025-07-18 17:35:26 10.1.1.100 GET /_layouts/15/spinstall0.aspx - 80 - 10.5.2.136 Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64;+rv:120.0)+Gecko/20100101+Firefox/120.0 /_layouts/SignOut.aspx 301 0 0 6
2025-07-20 14:59:11 10.1.1.100 GET /_layouts/15/spinstall0.aspx - 80 - 10.5.2.136 Hello+from+Palo+Alto+Networks,+find+out+more+about+our+scans+in+https://docs-cortex.paloaltonetworks.com/r/1/Cortex-Xpanse/Scanning-activity - 301 0 0 6

In this example, the IP is private due to NAT. Use the timestamp of the suspicious log entries to trace the real source IP on your firewall or load balancer.

The second log’s User-Agent reveals Palo Alto Cortex Xpanse scanning. I’ve seen a significant amount on all SharePoint servers I’ve scanned so far using the script. To reduce alert noise, consider blocking their IP ranges:

35.203.210.0/23
147.185.132.0/23
162.216.149.0/24
162.216.150.0/24
198.235.24.0/24
205.210.31.0/24
216.25.88.0/21

Microsoft’s KQL Queries Are Incomplete

Microsoft’s official hunting queries only look in the layouts folder, but most real-world attacks target the _layouts directory. Therefore, we need to extend their queries as follows:

DeviceFileEvents
| where FolderPath has_any (
  @'microsoft shared\Web Server Extensions\16\TEMPLATE\LAYOUTS',
  @'microsoft shared\Web Server Extensions\15\TEMPLATE\LAYOUTS',
  @'microsoft shared\Web Server Extensions\16\TEMPLATE\_LAYOUTS',
  @'microsoft shared\Web Server Extensions\15\TEMPLATE\_LAYOUTS')
| where FileName has "spinstall0"
| project Timestamp, DeviceName, InitiatingProcessFileName, InitiatingProcessCommandLine, FileName, FolderPath, ReportId, ActionType, SHA256
| order by Timestamp desc
DeviceProcessEvents
| where InitiatingProcessFileName has "w3wp.exe"
 and InitiatingProcessCommandLine !has "DefaultAppPool"
 and FileName =~ "cmd.exe"
 and ProcessCommandLine has_all ("cmd.exe", "powershell")
 and ProcessCommandLine has_any ("EncodedCommand", "-ec")
| extend CommandArguments = split(ProcessCommandLine, " ")
| mv-expand CommandArguments to typeof(string)
| where CommandArguments matches regex "^[A-Za-z0-9+/=]{15,}$"
| extend B64Decode = replace("\\x00", "", base64_decodestring(tostring(CommandArguments)))  
| where B64Decode has_any (
  "spinstall0",
  @'C:\PROGRA~1\COMMON~1\MICROS~1\WEBSER~1\15\TEMPLATE\LAYOUTS',
  @'C:\PROGRA~1\COMMON~1\MICROS~1\WEBSER~1\16\TEMPLATE\LAYOUTS',
  @'C:\PROGRA~1\COMMON~1\MICROS~1\WEBSER~1\15\TEMPLATE\_LAYOUTS',
  @'C:\PROGRA~1\COMMON~1\MICROS~1\WEBSER~1\16\TEMPLATE\_LAYOUTS')

Set these up as custom detections, possibly even with an automatic isolation.
If you see hits:

  • Immediately isolate the machine.
  • Rotate the SharePoint ASP.NET machine keys.
  • Apply the latest updates.
  • Ensure AMSI is enabled in full mode.

Stay vigilant. Patch quickly. And always check your logs.

Back to Blog

Related Posts

View All Posts »