Skip to content

Commit

Permalink
Add multithreading to application file permission check
Browse files Browse the repository at this point in the history
  • Loading branch information
itm4n committed Dec 28, 2024
1 parent 4e7aaf7 commit ec36e24
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 42 deletions.
6 changes: 6 additions & 0 deletions info/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 2025-12-28

### Changed

- Add multithreading to application file permissions check.

## 2025-12-27

### Added
Expand Down
48 changes: 6 additions & 42 deletions src/check/Applications.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -33,57 +33,21 @@ function Invoke-InstalledApplicationPermissionCheck {
[UInt32] $BaseSeverity
)

begin {
$AllResults = @()
$FsRedirectionValue = Disable-Wow64FileSystemRedirection
}

process {
$InstalledPrograms = Get-InstalledApplication -Filtered

foreach ($InstalledProgram in $InstalledPrograms) {

# Ensure the path is not a known system folder, in which case it does not make
# sense to check it. This also prevents the script from spending a considerable
# amount of time and resources searching those paths recursively.
if (Test-IsSystemFolder -Path $InstalledProgram.FullName) {
Write-Warning "System path detected, ignoring: $($InstalledProgram.FullName)"
continue
}

# Build the search path list. The following trick is used to search recursively
# without using the 'Depth' option, which is only available in PSv5+. This
# allows us to maintain compatibility with PSv2.
$SearchPath = New-Object -TypeName System.Collections.ArrayList
[void] $SearchPath.Add([String] $(Join-Path -Path $InstalledProgram.FullName -ChildPath "\*"))
[void] $SearchPath.Add([String] $(Join-Path -Path $InstalledProgram.FullName -ChildPath "\*\*"))

$CandidateItems = Get-ChildItem -Path $SearchPath -ErrorAction SilentlyContinue
if ($null -eq $CandidateItems) { continue }

foreach ($CandidateItem in $CandidateItems) {

if (($CandidateItem -is [System.IO.FileInfo]) -and (-not (Test-IsCommonApplicationFile -Path $CandidateItem.FullName))) { continue }
if ([String]::IsNullOrEmpty($CandidateItem.FullName)) { continue }
$AllResults = @()

$ModifiablePaths = Get-ModifiablePath -Path $CandidateItem.FullName | Where-Object { $_ -and (-not [String]::IsNullOrEmpty($_.ModifiablePath)) }
if ($null -eq $ModifiablePaths) { continue }
foreach ($ModifiablePath in $ModifiablePaths) {
$ModifiablePath.Permissions = $ModifiablePath.Permissions -join ', '
$AllResults += $ModifiablePath
Get-InstalledApplication -Filtered |
Invoke-CommandMultithread -InitialSessionState $(Get-InitialSessionState) -Command "Get-ModifiableApplicationFile" -InputParameter "FileItem" |
ForEach-Object {
$_.Permissions = $_.Permissions -join ', '
$AllResults += $_
}
}
}

$CheckResult = New-Object -TypeName PSObject
$CheckResult | Add-Member -MemberType "NoteProperty" -Name "Result" -Value $AllResults
$CheckResult | Add-Member -MemberType "NoteProperty" -Name "Severity" -Value $(if ($AllResults) { $BaseSeverity } else { $script:SeverityLevel::None })
$CheckResult
}

end {
Restore-Wow64FileSystemRedirection -OldValue $FsRedirectionValue
}
}

function Invoke-ProgramDataPermissionCheck {
Expand Down
64 changes: 64 additions & 0 deletions src/helper/AccessControl.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,70 @@ function Get-ModifiableComClassEntryImagePath {
}
}

function Get-ModifiableApplicationFile {
<#
.SYNOPSIS
Helper - Test the permissions of application files.
Author: @itm4n
License: BSD 3-Clause
.DESCRIPTION
This cmdlet is used as a helper function by 'Invoke-InstalledApplicationPermissionCheck' to support multithreading. It checks the permissions of a single application folder, which can be a time consuming if it has a lot of sub-folders and files.
.PARAMETER FileItem
A mandatory input file item (file or directory) returned by 'Get-InstalledApplication'.
.EXAMPLE
PS C:\> $Applications = Get-InstalledApplication -Filtered
PS C:\> Get-ModifiableApplicationFile -FileItem $Applications[0]
#>

[CmdletBinding()]
param (
[Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[Object] $FileItem
)

begin {
$FsRedirectionValue = Disable-Wow64FileSystemRedirection
}

process {
# Ensure the path is not a known system folder, in which case it does not make
# sense to check it. This also prevents the script from spending a considerable
# amount of time and resources searching those paths recursively.
if (Test-IsSystemFolder -Path $FileItem.FullName) {
Write-Warning "System path detected, ignoring: $($FileItem.FullName)"
return
}

# Build the search path list. The following trick is used to search recursively
# without using the 'Depth' option, which is only available in PSv5+. This
# allows us to maintain compatibility with PSv2.
$SearchPath = @()
$SearchPath += $(Join-Path -Path $FileItem.FullName -ChildPath "\*")
$SearchPath += $(Join-Path -Path $FileItem.FullName -ChildPath "\*\*")

# Enumerate sub-folders and files, return immediately if nothing is found.
$CandidateItems = Get-ChildItem -Path $SearchPath -ErrorAction SilentlyContinue
if ($null -eq $CandidateItems) { return }

foreach ($CandidateItem in $CandidateItems) {

# Ignore application files that do not have a common extension such as '.exe'
# or '.dll'.
if (($CandidateItem -is [System.IO.FileInfo]) -and (-not (Test-IsCommonApplicationFile -Path $CandidateItem.FullName))) { continue }

Get-ModifiablePath -Path $CandidateItem.FullName | Where-Object { $_ -and (-not [String]::IsNullOrEmpty($_.ModifiablePath)) }
}
}

end {
Restore-Wow64FileSystemRedirection -OldValue $FsRedirectionValue
}
}

function Get-ExploitableUnquotedPath {
<#
.SYNOPSIS
Expand Down

0 comments on commit ec36e24

Please sign in to comment.