mirror of
https://github.com/itm4n/PrivescCheck
synced 2026-06-08 14:54:28 +00:00
Add a check for enumerating whitelisted MSI files with interesting custom actions (issue #76)
This commit is contained in:
@@ -66,6 +66,7 @@
|
||||
"CONFIG_SMB", "Invoke-SmbConfigurationCheck", "TA0008 - Lateral Movement", "Configuration - SMB", "Low", "List", "Audit", "True", "False", "Check whether SMBv1 is enabled (server), and whether signing is required (server and client)."
|
||||
"CONFIG_PRINTNIGHTMARE", "Invoke-PointAndPrintConfigurationCheck", "TA0004 - Privilege Escalation", "Configuration - Point and Print", "High", "List", "Base", "True", "False", "Check whether the Print Spooler service is enabled and if the Point and Print configuration allows non-administrator users to install printer drivers."
|
||||
"CONFIG_MSI_REPAIR_UAC_PROMPT", "Invoke-MsiAutomaticRepairUacPromptCheck", "TA0004 - Privilege Escalation", "Configuration - Application Repair UAC Prompt", "Medium", "List", "Extended", "True", "False", "Check whether the UAC prompt displayed when attempting an application repair through the Windows Installer was disabled."
|
||||
"CONFIG_MSI_REPAIR_WHITELIST", "Invoke-MsiAutomaticRepairWhitelistCheck", "TA0004 - Privilege Escalation", "Configuration - Application Repair Whitelist", "Low", "List", "Base", "True", "False", "Check whether a whitelist of MSI packages is set in the registry to disable UAC prompts, and whether they have custom actions that may be leveraged for local privilege escalation."
|
||||
"CONFIG_COINSTALLERS", "Invoke-DriverCoInstallerCheck", "TA0004 - Privilege Escalation", "Configuration - Driver Co-Installers", "Low", "List", "Audit", "True", "False", "Check whether Driver Co-installers are disabled. A local user might be able to gain SYSTEM privileges by plugging in a device such as a mouse or keyboard with a vulnerable Driver Co-installer."
|
||||
"CONFIG_SCCM_CACHE_FOLDERS", "Invoke-SccmCacheFolderCheck", "TA0043 - Reconnaissance", "Configuration - SCCM Cache Folders", "None", "List", "Extended", "True", "False", "Get information about SCCM cache folders (incl. number and list of binary, script, and text files)."
|
||||
"CONFIG_COM_REGISTRY_PERMISSIONS", "Invoke-ComServerRegistryPermissionCheck", "TA0004 - Privilege Escalation", "Configuration - COM registry permissions", "Medium", "List", "Extended", "False", "False", "Check whether the current user has any modification rights on a COM server in the registry. This may not necessarily result in a privilege escalation. Further analysis is required."
|
||||
|
||||
|
@@ -5,6 +5,7 @@
|
||||
### Added
|
||||
|
||||
- Attempt to determine private key file path when enumerating machine and user personal certificates (issue #77).
|
||||
- Check for whitelisted MSI packages with potentially interesting custom actions (issue #76).
|
||||
|
||||
## 2025-12-24
|
||||
|
||||
|
||||
+127
-5
@@ -1194,7 +1194,8 @@ function Invoke-MsiAutomaticRepairUacPromptCheck {
|
||||
|
||||
begin {
|
||||
$RegKey = "HKLM\SOFTWARE\Policies\Microsoft\Windows\Installer"
|
||||
$RegValue = "DisableLUAInRepair"
|
||||
$RegValueDisableLUAInRepair = "DisableLUAInRepair"
|
||||
$RegValueSecureRepairPolicy = "SecureRepairPolicy"
|
||||
|
||||
$DisableLUAInRepairDescriptions = @(
|
||||
"The User Account Control (UAC) prompts for credentials before initiating an application repair.",
|
||||
@@ -1203,13 +1204,30 @@ function Invoke-MsiAutomaticRepairUacPromptCheck {
|
||||
}
|
||||
|
||||
process {
|
||||
$RegData = (Get-ItemProperty -Path "Registry::$($RegKey)" -Name $RegValue -ErrorAction SilentlyContinue).$RegValue
|
||||
$Vulnerable = $RegData -ge 1
|
||||
$Vulnerable = $False
|
||||
$DisableLUAInRepair = (Get-ItemProperty -Path "registry::$($RegKey)" -Name $RegValueDisableLUAInRepair -ErrorAction SilentlyContinue).$RegValueDisableLUAInRepair
|
||||
$SecureRepairPolicy = (Get-ItemProperty -Path "registry::$($RegKey)" -Name $RegValueSecureRepairPolicy -ErrorAction SilentlyContinue).$RegValueSecureRepairPolicy
|
||||
$SecureRepairWhitelist = Get-Item -Path "registry::$($RegKey)\SecureRepairWhitelist" -ErrorAction SilentlyContinue
|
||||
$SecureRepairWhitelistCount = $(if ($SecureRepairWhitelist) { $SecureRepairWhitelist.ValueCount } else { 0 })
|
||||
|
||||
if ($DisableLUAInRepair -ge 1) {
|
||||
$Vulnerable = $True
|
||||
$Description = $DisableLUAInRepairDescriptions[1]
|
||||
}
|
||||
else {
|
||||
$Description = $DisableLUAInRepairDescriptions[0]
|
||||
if (($SecureRepairWhitelistCount -gt 0) -and ($SecureRepairPolicy -eq 2)) {
|
||||
$Vulnerable = $True
|
||||
$BaseSeverity = $script:SeverityLevel::Low
|
||||
$Description = "$($Description) A whitelist of MSI packages is set."
|
||||
}
|
||||
}
|
||||
|
||||
$Result = New-Object -TypeName PSObject
|
||||
$Result | Add-Member -MemberType "NoteProperty" -Name "Key" -Value $RegKey
|
||||
$Result | Add-Member -MemberType "NoteProperty" -Name "Value" -Value $RegValue
|
||||
$Result | Add-Member -MemberType "NoteProperty" -Name "Data" -Value $(if ($null -eq $RegData) { "(null)" } else { $RegData })
|
||||
$Result | Add-Member -MemberType "NoteProperty" -Name "DisableLUAInRepair" -Value $(if ($null -eq $DisableLUAInRepair) { "(null)" } else { $DisableLUAInRepair })
|
||||
$Result | Add-Member -MemberType "NoteProperty" -Name "SecureRepairPolicy" -Value $(if ($null -eq $SecureRepairPolicy) { "(null)" } else { $SecureRepairPolicy })
|
||||
$Result | Add-Member -MemberType "NoteProperty" -Name "SecureRepairWhitelistCount" -Value $SecureRepairWhitelistCount
|
||||
$Result | Add-Member -MemberType "NoteProperty" -Name "Description" -Value $DisableLUAInRepairDescriptions[[UInt32]$Vulnerable]
|
||||
|
||||
$CheckResult = New-Object -TypeName PSObject
|
||||
@@ -1219,6 +1237,110 @@ function Invoke-MsiAutomaticRepairUacPromptCheck {
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-MsiAutomaticRepairWhitelistCheck {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Enumerate whitelisted MSI packages with custom actions that may be leveraged for local privilege escalation.
|
||||
|
||||
.DESCRIPTION
|
||||
This cmdlet enumerates MSI packages in the 'SecureRepairWhitelist' (Windows Installer registry key) and attempts to determine if they have custom actions set that may be exploited for elevating privileges locally.
|
||||
|
||||
.EXAMPLE
|
||||
PS C:\> Invoke-MsiAutomaticRepairWhitelistCheck
|
||||
|
||||
...
|
||||
|
||||
Path : C:\WINDOWS\Installer\1b4a8b.msi
|
||||
ProductCode : C53ECC06-72B1-FEBF-D241-038A4BC83E57
|
||||
Name : Application Verifier x64 External Package (OnecoreUAP)
|
||||
Vendor : Microsoft
|
||||
Version : 10.1.22621.5040
|
||||
AllUsers : 1
|
||||
CAName : AVRFInstall_UnInstallwow64External
|
||||
CASource : WixCA
|
||||
CATarget : CAQuietExec
|
||||
CATargetExpanded : CAQuietExec
|
||||
CAExeType : Dll
|
||||
CASecurityContextFlags : NoImpersonate
|
||||
CARunAsSystem : True
|
||||
CABinaryExtractCommand : Invoke-MsiExtractBinaryData -Path "C:\WINDOWS\Installer\1b4a8b.msi" -Name "WixCA" -OutputPath "WixCA.dll"
|
||||
|
||||
...
|
||||
|
||||
.NOTES
|
||||
This check is inspired by previous work from @garatc (https://github.com/garatc), see MSIAudit (https://github.com/garatc/MSIAudit).
|
||||
|
||||
.LINK
|
||||
https://support.microsoft.com/en-us/topic/unexpected-uac-prompts-when-running-msi-repair-operations-after-installing-the-august-2025-windows-security-update-5806f583-e073-4675-9464-fe01974df273
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[UInt32] $BaseSeverity
|
||||
)
|
||||
|
||||
begin {
|
||||
# https://support.microsoft.com/en-us/topic/unexpected-uac-prompts-when-running-msi-repair-operations-after-installing-the-august-2025-windows-security-update-5806f583-e073-4675-9464-fe01974df273
|
||||
$InstallerKeyPath = "HKLM\SOFTWARE\Policies\Microsoft\Windows\Installer"
|
||||
}
|
||||
|
||||
process {
|
||||
$Results = @()
|
||||
$SecureRepairPolicyValue = Get-ItemProperty -Path "registry::$($InstallerKeyPath)" -Name "SecureRepairPolicy" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty SecureRepairPolicy
|
||||
|
||||
if ($SecureRepairPolicyValue -eq 2) {
|
||||
|
||||
$Exceptions = [String[]] (Get-Item -Path "registry::$($InstallerKeyPath)\SecureRepairWhitelist" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Property)
|
||||
$WhitelistedMsiFiles = Find-MsiFile -ProductCode $Exceptions | Where-Object { -not [String]::IsNullOrEmpty($_) }
|
||||
|
||||
$ProgressCount = 0
|
||||
Write-Progress -Activity "Checking whitelisted MSI files (0/$($WhitelistedMsiFiles.Count))..." -Status "0% Complete:" -PercentComplete 0
|
||||
foreach ($WhitelistedMsiFile in $WhitelistedMsiFiles) {
|
||||
$ProgressPercent = [UInt32] ($ProgressCount * 100 / $WhitelistedMsiFiles.Count)
|
||||
Write-Progress -Activity "Checking whitelisted MSI files ($($ProgressCount)/$($WhitelistedMsiFiles.Count)): $($WhitelistedMsiFile)" -Status "$($ProgressPercent)% Complete:" -PercentComplete $ProgressPercent
|
||||
|
||||
$Candidates = Get-MsiFileItem -FilePath $WhitelistedMsiFile
|
||||
foreach ($Candidate in $Candidates) {
|
||||
|
||||
$CandidateCustomActions = [Object[]] ($Candidate.CustomActions | Where-Object { $_.Candidate -eq $true })
|
||||
if ($Candidate.AllUsers -ne 1) { continue }
|
||||
if ($null -eq $Candidate.CustomActions) { continue }
|
||||
if ($CandidateCustomActions.Count -eq 0) { continue }
|
||||
|
||||
$MsiObject = $Candidate | Select-Object -Property Path,ProductCode,Name,Vendor,Version,AllUsers
|
||||
|
||||
$AnalyzeCommand = "Get-MsiFileItem -FilePath `"$($MsiObject.Path)`" | Select-Object -ExpandProperty CustomActions | Where-Object { `$_.Candidate }"
|
||||
$RepairCommand = "Start-Process -FilePath `"msiexec.exe`" -ArgumentList `"/fa $($MsiObject.Path)`""
|
||||
|
||||
$MsiObject | Add-Member -MemberType "NoteProperty" -Name "AnalyzeCommand" -Value $AnalyzeCommand
|
||||
$MsiObject | Add-Member -MemberType "NoteProperty" -Name "RepairCommand" -Value $RepairCommand
|
||||
|
||||
foreach ($CandidateCustomAction in $CandidateCustomActions) {
|
||||
|
||||
$MsiObjectCopy = $MsiObject.PSObject.Copy()
|
||||
$MsiObjectCopy | Add-Member -MemberType "NoteProperty" -Name "CAName" -Value $CandidateCustomAction.Action
|
||||
$MsiObjectCopy | Add-Member -MemberType "NoteProperty" -Name "CASource" -Value $CandidateCustomAction.Source
|
||||
$MsiObjectCopy | Add-Member -MemberType "NoteProperty" -Name "CATarget" -Value $CandidateCustomAction.Target
|
||||
$MsiObjectCopy | Add-Member -MemberType "NoteProperty" -Name "CATargetExpanded" -Value $CandidateCustomAction.TargetExpanded
|
||||
$MsiObjectCopy | Add-Member -MemberType "NoteProperty" -Name "CAExeType" -Value $CandidateCustomAction.ExeType
|
||||
$MsiObjectCopy | Add-Member -MemberType "NoteProperty" -Name "CASecurityContextFlags" -Value $CandidateCustomAction.SecurityContextFlags
|
||||
$MsiObjectCopy | Add-Member -MemberType "NoteProperty" -Name "CARunAsSystem" -Value $CandidateCustomAction.RunAsSystem
|
||||
$MsiObjectCopy | Add-Member -MemberType "NoteProperty" -Name "CABinaryExtractCommand" -Value $CandidateCustomAction.BinaryExtractCommand
|
||||
$Results += $MsiObjectCopy
|
||||
}
|
||||
}
|
||||
|
||||
$ProgressCount += 1
|
||||
}
|
||||
}
|
||||
|
||||
$CheckResult = New-Object -TypeName PSObject
|
||||
$CheckResult | Add-Member -MemberType "NoteProperty" -Name "Result" -Value $Results
|
||||
$CheckResult | Add-Member -MemberType "NoteProperty" -Name "Severity" -Value $(if ($Results) { $BaseSeverity } else { $script:SeverityLevel::None })
|
||||
$CheckResult
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-CredentialDelegationCheck {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
|
||||
+58
-16
@@ -152,7 +152,7 @@ function Get-CustomAction {
|
||||
|
||||
begin {
|
||||
$SystemFolders = Get-MsiSystemFolderProperty -Arch $Arch -AllUsers $AllUsers
|
||||
$QuietExecFunctions = @("CAQuietExec", "CAQuietExec64", "WixQuietExec", "WixQuietExec64")
|
||||
# $QuietExecFunctions = @("CAQuietExec", "CAQuietExec64", "WixQuietExec", "WixQuietExec64")
|
||||
}
|
||||
|
||||
process {
|
||||
@@ -179,7 +179,6 @@ function Get-CustomAction {
|
||||
$SecurityContextFlags = ([string[]] (Get-CustomActionSecurityContextFlag -Type $Type)) -join ","
|
||||
|
||||
$TargetExpanded = Get-MsiExpandedString -String $Target -Database $Database -SystemFolders $SystemFolders
|
||||
if ($TargetExpanded -eq $Target) { $TargetExpanded = $null }
|
||||
|
||||
# 0x0800 -> no impersonation, run in system context
|
||||
$RunAsSystem = $([bool] ($Type -band 0x0800))
|
||||
@@ -198,7 +197,7 @@ function Get-CustomAction {
|
||||
$BinaryExtractCommand = "Invoke-MsiExtractBinaryData -Path `"$($FilePath)`" -Name `"$($Source)`" -OutputPath `"$($OutputFilename)`""
|
||||
}
|
||||
else {
|
||||
$BinaryExtractCommand = "(null)"
|
||||
$BinaryExtractCommand = "N/A (non-binary source)"
|
||||
}
|
||||
|
||||
$Candidate = $false
|
||||
@@ -206,15 +205,16 @@ function Get-CustomAction {
|
||||
# CA must not be configured to run only on patch uninstall
|
||||
(-not $RunOnPatchUninstallOnly) -and
|
||||
# CA must run as SYSTEM
|
||||
($RunAsSystem) -and
|
||||
($RunAsSystem)
|
||||
# ($RunAsSystem) -and
|
||||
# If CA is a DLL, it must not be a "quiet exec" function
|
||||
(
|
||||
($ExeType -ne "Dll") -or
|
||||
(
|
||||
($ExeType -eq "Dll") -and
|
||||
(-not ($QuietExecFunctions -contains $Target))
|
||||
)
|
||||
)
|
||||
# (
|
||||
# ($ExeType -ne "Dll") -or
|
||||
# (
|
||||
# ($ExeType -eq "Dll") -and
|
||||
# (-not ($QuietExecFunctions -contains $Target))
|
||||
# )
|
||||
# )
|
||||
) {
|
||||
$Candidate = $true
|
||||
}
|
||||
@@ -224,7 +224,7 @@ function Get-CustomAction {
|
||||
$CustomAction | Add-Member -MemberType "NoteProperty" -Name "Type" -Value $Type
|
||||
$CustomAction | Add-Member -MemberType "NoteProperty" -Name "Source" -Value $Source
|
||||
$CustomAction | Add-Member -MemberType "NoteProperty" -Name "Target" -Value $Target
|
||||
if ($TargetExpanded) { $CustomAction | Add-Member -MemberType "NoteProperty" -Name "TargetExpanded" -Value $TargetExpanded }
|
||||
$CustomAction | Add-Member -MemberType "NoteProperty" -Name "TargetExpanded" -Value $TargetExpanded
|
||||
$CustomAction | Add-Member -MemberType "NoteProperty" -Name "ExeType" -Value $ExeType
|
||||
$CustomAction | Add-Member -MemberType "NoteProperty" -Name "SourceType" -Value $SourceType
|
||||
$CustomAction | Add-Member -MemberType "NoteProperty" -Name "ReturnProcessing" -Value $ReturnProcessing
|
||||
@@ -715,15 +715,15 @@ function Get-MsiItem {
|
||||
$Installer = New-Object -ComObject WindowsInstaller.Installer
|
||||
$Database = Invoke-MsiOpenDatabase -Installer $Installer -Path $Path -Mode 0
|
||||
|
||||
$IdentifyingNumber = [string] (Get-MsiProperty -Database $Database -Property "ProductCode")
|
||||
$ProductCode = [string] (Get-MsiProperty -Database $Database -Property "ProductCode")
|
||||
$Name = [string] (Get-MsiProperty -Database $Database -Property "ProductName")
|
||||
$Vendor = [string] (Get-MsiProperty -Database $Database -Property "Manufacturer")
|
||||
$Version = [string] (Get-MsiProperty -Database $Database -Property "ProductVersion")
|
||||
$AllUsers = Get-MsiProperty -Database $Database -Property "ALLUSERS"
|
||||
|
||||
# Extract the GUID value, without the curly braces.
|
||||
if ($IdentifyingNumber -match "(\d|[A-F]){8}-((\d|[A-F]){4}-){3}((\d|[A-F]){12})") {
|
||||
$IdentifyingNumber = $Matches[0]
|
||||
if ($ProductCode -match "(\d|[A-F]){8}-((\d|[A-F]){4}-){3}((\d|[A-F]){12})") {
|
||||
$ProductCode = $Matches[0]
|
||||
}
|
||||
|
||||
# If ALLUSERS is not defined, the default is "per-user", which corresponds to a value of 0.
|
||||
@@ -732,7 +732,7 @@ function Get-MsiItem {
|
||||
|
||||
$MsiItem = New-Object -TypeName PSObject
|
||||
$MsiItem | Add-Member -MemberType "NoteProperty" -Name "Path" -Value $Path
|
||||
$MsiItem | Add-Member -MemberType "NoteProperty" -Name "IdentifyingNumber" -Value $(if ($IdentifyingNumber) { $IdentifyingNumber.Trim() })
|
||||
$MsiItem | Add-Member -MemberType "NoteProperty" -Name "ProductCode" -Value $(if ($ProductCode) { $ProductCode.Trim() })
|
||||
$MsiItem | Add-Member -MemberType "NoteProperty" -Name "Name" -Value $(if ($Name) { $Name.Trim() })
|
||||
$MsiItem | Add-Member -MemberType "NoteProperty" -Name "Vendor" -Value $(if ($Vendor) { $Vendor.Trim() })
|
||||
$MsiItem | Add-Member -MemberType "NoteProperty" -Name "Version" -Value $(if ($Version) { $Version.Trim() })
|
||||
@@ -776,4 +776,46 @@ function Get-MsiFileItem {
|
||||
Get-MsiItem -Path $FilePath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Find-MsiFile {
|
||||
|
||||
[OutputType([String[]])]
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[String[]] $ProductCode
|
||||
)
|
||||
|
||||
begin {
|
||||
$InstallerPath = Join-Path -Path $env:windir -ChildPath "Installer"
|
||||
$MsiFilePaths = [String[]] (Get-ChildItem -Path "$($InstallerPath)\*.msi" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName)
|
||||
}
|
||||
|
||||
process {
|
||||
for ($i = 0; $i -lt $ProductCode.Count; $i++) {
|
||||
$ProductCode[$i] = ($ProductCode[$i] -replace '{','') -replace '}',''
|
||||
}
|
||||
|
||||
$ProgressCount = 0
|
||||
Write-Progress -Activity "Enumerating MSI files (0/$($MsiFilePaths.Count))..." -Status "0% Complete:" -PercentComplete 0
|
||||
foreach ($MsiFilePath in $MsiFilePaths) {
|
||||
$ProgressPercent = [UInt32] ($ProgressCount * 100 / $MsiFilePaths.Count)
|
||||
Write-Progress -Activity "Enumerating MSI files ($($ProgressCount)/$($MsiFilePaths.Count)): $($MsiFilePath)" -Status "$($ProgressPercent)% Complete:" -PercentComplete $ProgressPercent
|
||||
|
||||
$Installer = New-Object -ComObject WindowsInstaller.Installer
|
||||
$Database = Invoke-MsiOpenDatabase -Installer $Installer -Path $MsiFilePath -Mode 0
|
||||
$MsiProductCode = ([String] (Get-MsiProperty -Database $Database -Property "ProductCode")).Trim()
|
||||
$null = [System.Runtime.InteropServices.Marshal]::ReleaseComObject($Installer)
|
||||
|
||||
if ($MsiProductCode -match "(\d|[A-F]){8}-((\d|[A-F]){4}-){3}((\d|[A-F]){12})") {
|
||||
$MsiProductCode = $Matches[0]
|
||||
}
|
||||
|
||||
if ($ProductCode -contains $MsiProductCode) {
|
||||
$MsiFilePath
|
||||
}
|
||||
|
||||
$ProgressCount += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user