Skip to content

Instantly share code, notes, and snippets.

@LuemmelSec
Last active January 9, 2026 15:34
Show Gist options
  • Select an option

  • Save LuemmelSec/208b8ba52b645ec189031d2b5200f76e to your computer and use it in GitHub Desktop.

Select an option

Save LuemmelSec/208b8ba52b645ec189031d2b5200f76e to your computer and use it in GitHub Desktop.
Script to get SharePoint Version and release date remotely
# Script to check SharePoint version numbers directly with Microsoft
# Usage:
# iex(new-object net.webclient).downloadstring("https://raw.githubusercontent.com/LuemmelSec/Pentest-Tools-Collection/refs/heads/main/tools/Azure/Get-SPVersionInfo.ps1")
# Get-SPVersionInfo -ServerUrl "https://my-sharepointserver"
# Get-SPVersionInfo -InputFile .\targets.txt -SkipCertificateCheck -SkipHttpErrorCheck
function Get-SPVersionInfo {
param (
[string]$ServerUrl,
[string]$InputFile,
[switch]$SkipCertificateCheck,
[switch]$SkipHttpErrorCheck
)
if (-not $ServerUrl -and -not $InputFile -and -not $SkipCertificateCheck -and -not $SkipHttpErrorCheck) {
Write-Host "Usage: Get-SPVersionInfo -ServerUrl <URL> | -InputFile <Path> [-SkipCertificateCheck] [-SkipHttpErrorCheck]" -ForegroundColor DarkYellow
return
}
function Normalize-Url {
param ([string]$url)
if ($url -notmatch '^https?://') {
return "https://$url"
}
return $url
}
if ($InputFile) {
if (-not (Test-Path $InputFile)) {
Write-Host "Input file '$InputFile' not found." -ForegroundColor Red
return
}
$targets = Get-Content $InputFile | Where-Object { $_ -and $_.Trim() -ne "" }
foreach ($target in $targets) {
$normalizedTarget = Normalize-Url $target
Write-Host "`n======================"
Write-Host "Checking $normalizedTarget"
Write-Host "======================"
Get-SPVersionInfo -ServerUrl $normalizedTarget -SkipCertificateCheck:$SkipCertificateCheck -SkipHttpErrorCheck:$SkipHttpErrorCheck
}
return
}
if (-not $ServerUrl) {
Write-Host "No server URL specified." -ForegroundColor Red
return
}
$ServerUrl = Normalize-Url $ServerUrl
try {
$uri = [Uri]$ServerUrl
if (-not $uri.Scheme.StartsWith("http")) {
throw "Invalid URL scheme."
}
} catch {
Write-Host "Invalid ServerUrl format: $ServerUrl" -ForegroundColor Red
return
}
$msUpdatePage = "https://learn.microsoft.com/en-us/officeupdates/sharepoint-updates"
$serviceCnfUrl = "$ServerUrl/_vti_pvt/service.cnf"
$startPageUrl = "$ServerUrl/_layouts/15/start.aspx"
$headerVersion = $null
$serviceCnfVersion = $null
$headerVersionTrimmed = $null
$serviceCnfVersionTrimmed = $null
$foundMatchHeader = $false
$foundMatchServiceCnf = $false
$headerFullMatch = $false
$serviceCnfFullMatch = $false
$siteClientTagMatched = $false
function Get-FirstCellValue {
param ($cellHtml)
$first = ($cellHtml -split '(?i)<br\s*/?>')[0]
return ($first -replace '<.*?>', '').Trim()
}
function Get-FirstKbNumber {
param ($cellHtml)
$m = [regex]::Match($cellHtml, 'KB\s*(\d+)', 'IgnoreCase')
if ($m.Success) { return $m.Groups[1].Value }
else { return "N/A" }
}
function GetProductNameFromVersion($versionString) {
# Accepts 3- or 4-segment versions
if ($versionString -match "^16\.0\.(\d{4,5})(?:\.\d+)?$") {
$majorMinor = ($versionString -split '\.')[2]
if ([int]$majorMinor -lt 10000) { return "2016" }
elseif ([int]$majorMinor -ge 10000 -and [int]$majorMinor -lt 18000) { return "2019" }
else { return "Subscription" }
}
elseif ($versionString -match "^15\.0\.(\d+)(?:\.\d+)?$") {
return "2013"
}
elseif ($versionString -match "^14\.0\.(\d+)(?:\.\d+)?$") {
return "2010"
}
elseif ($versionString -match "^12\.0\.(\d+)(?:\.\d+)?$") {
return "2007"
}
else { return $null }
}
function Write-VulnerabilityWarning {
param (
[string]$product,
[version]$currentVersion,
[version]$patchedVersion,
[bool]$fullMatchVersion
)
$severity = $fullMatchVersion ? "CONFIRMED VULNERABLE" : "potentially VULNERABLE"
$color = $fullMatchVersion ? 'DarkRed' : 'Red'
Write-Host "`nWARNING: SharePoint $product is $severity to CVE-2025-53770!" -ForegroundColor $color
Write-Host "Detected version: $currentVersion, Required minimum version: $patchedVersion" -ForegroundColor $color
}
function CheckVulnerability {
param ([string]$versionString, [bool]$fullMatchVersion)
$product = GetProductNameFromVersion $versionString
$secureVersions = @{
"2016" = [version]"16.0.5513.1001"
"2019" = [version]"16.0.10417.20037"
"Subscription" = [version]"16.0.18526.20508"
}
if ($product -eq "2013" -or $product -eq "2010" -or $product -eq "2007") {
Write-Host "`nWARNING: SharePoint $product detected (version: $versionString). You really got balls still exposing this to the internet!" -ForegroundColor DarkRed
return
}
if ($product -and $secureVersions.ContainsKey($product)) {
$currentVersion = try { [version]$versionString } catch { $null }
$patchedVersion = $secureVersions[$product]
if ($currentVersion -and $currentVersion -ge $patchedVersion) {
Write-Host "`nSharePoint $product appears to be patched against CVE-2025-53770." -ForegroundColor Green
}
elseif ($versionString -match "\.0\.\d{4}$") {
Write-Host "`nSharePoint $product has hotfix applied ($versionString), treated as patched." -ForegroundColor Green
}
else {
Write-VulnerabilityWarning -product $product -currentVersion $currentVersion -patchedVersion $patchedVersion -fullMatchVersion:$fullMatchVersion
}
} else {
Write-Host "`nUnable to determine product or patch baseline for version: $versionString" -ForegroundColor Yellow
}
}
function CheckForMatch {
param (
[string]$versionToCheck,
$blocks,
[ref]$foundMatch,
[ref]$fullMatch,
[ref]$vulnChecked = $null
)
$matchedBlocks = @()
foreach ($block in $blocks) {
$blockContent = $block.Groups[1].Value
if ($blockContent -match [regex]::Escape($versionToCheck)) {
$matchedBlocks += $blockContent
}
}
if ($matchedBlocks.Count -eq 1) {
$blockContent = $matchedBlocks[0]
$cells = [regex]::Matches($blockContent, "<td.*?>(.*?)</td>")
if ($cells -and $cells.Count -ge 4) {
$product = Get-FirstCellValue $cells[0].Groups[1].Value
$kb = Get-FirstKbNumber $cells[1].Groups[1].Value
$fullVersion = Get-FirstCellValue $cells[2].Groups[1].Value
$releaseDate = Get-FirstCellValue $cells[3].Groups[1].Value
Write-Host "`nMatch Found:"
Write-Host "Product: $product"
Write-Host "KB Number: $kb"
Write-Host "Full Version: $fullVersion"
Write-Host "Release Date: $releaseDate"
$foundMatch.Value = $true
$fullMatch.Value = $true
$script:latestDetectedFullVersion = $fullVersion
} else {
Write-Host "Warning: Could not extract expected update information from table row for version $versionToCheck." -ForegroundColor Yellow
}
} elseif ($matchedBlocks.Count -gt 1) {
$releaseDates = @()
foreach ($blockContent in $matchedBlocks) {
$cells = [regex]::Matches($blockContent, "<td.*?>(.*?)</td>")
if ($cells.Count -gt 3) {
$releaseDate = Get-FirstCellValue $cells[3].Groups[1].Value
$releaseDates += $releaseDate
}
}
$parsedDates = $releaseDates | Where-Object { $_ -match "\w+ \d{4}" }
$parsedDates = $parsedDates | Sort-Object { try { [datetime]::ParseExact($_, 'MMMM yyyy', $null) } catch { $_ } }
if ($parsedDates.Count -gt 1) {
$start = $parsedDates[0]
$end = $parsedDates[-1]
$productInUse = GetProductNameFromVersion $versionToCheck
if ($productInUse) {
Write-Host "`nProduct detected: SharePoint $productInUse (version: $versionToCheck)"
} else {
Write-Host "`nProduct detected: Unknown SharePoint variant (version: $versionToCheck)"
}
Write-Host "`nNote: Version $versionToCheck appears in multiple updates from $start to $end." -ForegroundColor Yellow
Write-Host "Exact patch level cannot be determined. Please verify manually:"
Write-Host "https://learn.microsoft.com/en-us/officeupdates/sharepoint-updates"
$foundMatch.Value = $true
if ($vulnChecked -ne $null) { $vulnChecked.Value = $true }
CheckVulnerability -versionString $versionToCheck -fullMatchVersion:$false
return
} else {
Write-Host "Multiple table rows found for version $versionToCheck, but could not parse release dates." -ForegroundColor Yellow
}
} else {
Write-Host "No 1:1 match against Microsoft data for version $versionToCheck." -ForegroundColor Yellow
}
}
try {
$webContent = Invoke-WebRequest -Uri $msUpdatePage -UseBasicParsing
$contentText = $webContent.Content
$trBlocks = [regex]::Matches($contentText, "<tr>(.*?)</tr>", [System.Text.RegularExpressions.RegexOptions]::Singleline)
try {
$startPageParams = @{
Uri = $startPageUrl
UseBasicParsing = $true
Headers = @{ "User-Agent" = "Mozilla/5.0" }
ErrorAction = 'Stop'
}
if ($SkipCertificateCheck) { $startPageParams.SkipCertificateCheck = $true }
if ($SkipHttpErrorCheck) { $startPageParams.SkipHttpErrorCheck = $true }
$startPageResponse = Invoke-WebRequest @startPageParams
if ($startPageResponse.Content -match 'siteClientTag\s*:\s*"[^$]*\$\$(\d+\.\d+\.\d+\.\d+)"') {
$siteClientTagVersion = $matches[1]
Write-Host "siteClientTag version: $siteClientTagVersion"
$foundMatchSiteClientTag = $false
$fullMatchSiteClientTag = $false
$script:latestDetectedFullVersion = $null
$vulnChecked = $false
CheckForMatch -versionToCheck $siteClientTagVersion -blocks $trBlocks -foundMatch ([ref]$foundMatchSiteClientTag) -fullMatch ([ref]$fullMatchSiteClientTag) -vulnChecked ([ref]$vulnChecked)
if (-not $vulnChecked) {
if ($script:latestDetectedFullVersion) {
CheckVulnerability -versionString $script:latestDetectedFullVersion -fullMatchVersion:$fullMatchSiteClientTag
} else {
CheckVulnerability -versionString $siteClientTagVersion -fullMatchVersion:$fullMatchSiteClientTag
}
}
return
} else {
Write-Host "Could not extract siteClientTag version from start.aspx." -ForegroundColor Yellow
}
} catch {
Write-Host "Error querying siteClientTag from start.aspx: $_" -ForegroundColor Yellow
}
$requestParams = @{
Uri = $ServerUrl
Method = 'Get'
Headers = @{ "User-Agent" = "Mozilla/5.0" }
UseBasicParsing = $true
}
if ($SkipCertificateCheck) { $requestParams.SkipCertificateCheck = $true }
if ($SkipHttpErrorCheck) { $requestParams.SkipHttpErrorCheck = $true }
$response = Invoke-WebRequest @requestParams
$headerVersion = $response.Headers["MicrosoftSharePointTeamServices"]
if ($headerVersion) {
$headerVersionTrimmed = $headerVersion -replace "^(\d+\.\d+)\.0\.(\d+)$", '$1.$2'
Write-Host "Header version: $headerVersion"
}
$serviceCnfParams = @{
Uri = $serviceCnfUrl
UseBasicParsing = $true
ErrorAction = 'Stop'
}
if ($SkipCertificateCheck) { $serviceCnfParams.SkipCertificateCheck = $true }
if ($SkipHttpErrorCheck) { $serviceCnfParams.SkipHttpErrorCheck = $true }
try {
$cnfResponse = Invoke-WebRequest @serviceCnfParams
if ($cnfResponse -match "vti_extenderversion:SR\|(\d+\.\d+\.\d+\.\d+)") {
$serviceCnfVersion = $matches[1]
$serviceCnfVersionTrimmed = $serviceCnfVersion -replace "^(\d+\.\d+)\.0\.(\d+)$", '$1.$2'
Write-Host "Service.cnf version: $serviceCnfVersion"
}
} catch {
Write-Host "Could not retrieve or parse service.cnf: $_" -ForegroundColor Yellow
}
if ($headerVersionTrimmed) {
Write-Host "`nChecking header version against Microsoft sources..."
$script:latestDetectedFullVersion = $null
$vulnChecked = $false
CheckForMatch -versionToCheck $headerVersionTrimmed -blocks $trBlocks -foundMatch ([ref]$foundMatchHeader) -fullMatch ([ref]$headerFullMatch) -vulnChecked ([ref]$vulnChecked)
if (-not $vulnChecked) {
if ($script:latestDetectedFullVersion) {
CheckVulnerability -versionString $script:latestDetectedFullVersion -fullMatchVersion:$headerFullMatch
} else {
CheckVulnerability -versionString $headerVersionTrimmed -fullMatchVersion:$headerFullMatch
}
}
}
if ($serviceCnfVersionTrimmed -and $serviceCnfVersionTrimmed -ne $headerVersionTrimmed) {
Write-Host "`nChecking service.cnf version against Microsoft sources..."
$script:latestDetectedFullVersion = $null
$vulnChecked = $false
CheckForMatch -versionToCheck $serviceCnfVersionTrimmed -blocks $trBlocks -foundMatch ([ref]$foundMatchServiceCnf) -fullMatch ([ref]$serviceCnfFullMatch) -vulnChecked ([ref]$vulnChecked)
if (-not $vulnChecked) {
if ($script:latestDetectedFullVersion) {
CheckVulnerability -versionString $script:latestDetectedFullVersion -fullMatchVersion:$serviceCnfFullMatch
} else {
CheckVulnerability -versionString $serviceCnfVersionTrimmed -fullMatchVersion:$serviceCnfFullMatch
}
}
}
if (-not $foundMatchHeader -and -not $foundMatchServiceCnf) {
Write-Host "`nNo matching update information found for either version." -ForegroundColor Yellow
}
} catch {
Write-Host "An error occurred: $_" -ForegroundColor Red
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment