Skip to content

Commit

Permalink
Refactor service control manager DACL check
Browse files Browse the repository at this point in the history
  • Loading branch information
itm4n committed Jan 9, 2025
1 parent a2a6d7b commit ef1c82f
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 145 deletions.
2 changes: 2 additions & 0 deletions info/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
### Changed

- Rename helper function 'Get-ModificationRight' to 'Get-ObjectAccessRight', and make it even more generic so that it can be used to check any access right, not just modification rights (which is still the default).
- The helper function 'Get-ObjectAccessRight' now handles access right checks for the Service Control Manager.

### Removed

- The cmdlet 'Get-ModifiableService' was replaced by the more generic helper function 'Get-ModificationRight'.
- The helper function 'Convert-NameToSid' embedded within 'Get-ModificationRight' was removed as it is no longer needed.
- The helper function 'Get-ModifiableRegistryPath' was replaced by 'Get-ObjectAccessRight'.
- The helper function 'Test-ServiceDiscretionaryAccessControlList' was replaced by 'Get-ObjectAccessRight'.
- The helper function 'Get-ServiceDiscretionaryAccessControlList' was replaced by 'Get-ObjectAccessRight'.

## 2025-01-08

Expand Down
60 changes: 13 additions & 47 deletions src/check/Services.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -315,56 +315,22 @@ function Invoke-ServiceControlManagerPermissionCheck {
[UInt32] $BaseSeverity
)

begin {
$CurrentUserSids = Get-CurrentUserSid
process {
$AllResults = @()
$ServiceControlManagerHandle = [IntPtr]::Zero

$PermissionReference = @(
$script:ServiceControlManagerAccessRight::CreateService,
$script:ServiceControlManagerAccessRight::ModifyBootConfig,
$script:ServiceControlManagerAccessRight::AllAccess,
$script:ServiceControlManagerAccessRight::GenericWrite
)
}

process {
$ServiceControlManagerHandle = Get-ServiceHandle -Name "SCM" -SCM
if ($ServiceControlManagerHandle -eq [IntPtr]::Zero) { return }

Get-ServiceDiscretionaryAccessControlList -Handle $ServiceControlManagerHandle -SCM |
Where-Object { $($_ | Select-Object -ExpandProperty "AceType") -match "AccessAllowed" } |
ForEach-Object {
$CurrentAce = $_

$Permissions = [Enum]::GetValues($script:ServiceControlManagerAccessRight) | Where-Object {
($CurrentAce.AccessMask -band ($script:ServiceControlManagerAccessRight::$_)) -eq ($script:ServiceControlManagerAccessRight::$_)
}

if (Compare-Object -ReferenceObject $Permissions -DifferenceObject $PermissionReference -IncludeEqual -ExcludeDifferent) {

$IdentityReference = $($CurrentAce | Select-Object -ExpandProperty "SecurityIdentifier").ToString()

if ($CurrentUserSids -contains $IdentityReference) {

$Result = New-Object -TypeName PSObject
$Result | Add-Member -MemberType "NoteProperty" -Name "AceType" -Value $($CurrentAce | Select-Object -ExpandProperty "AceType")
$Result | Add-Member -MemberType "NoteProperty" -Name "AccessRights" -Value $($CurrentAce | Select-Object -ExpandProperty "AccessRights")
$Result | Add-Member -MemberType "NoteProperty" -Name "IdentitySid" -Value $IdentityReference
$Result | Add-Member -MemberType "NoteProperty" -Name "IdentityName" -Value $(Convert-SidToName -Sid $IdentityReference)
$AllResults += $Result
}
}
}

$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
}
Get-ObjectAccessRight -Name "SCM" -Type ServiceControlManager | Foreach-Object {

end {
if ($ServiceControlManagerHandle -ne [IntPtr]::Zero) { $null = $script:Advapi32::CloseServiceHandle($ServiceControlManagerHandle) }
$Result = New-Object -TypeName PSObject
$Result | Add-Member -MemberType "NoteProperty" -Name "Name" -Value "ServiceControlManager"
$Result | Add-Member -MemberType "NoteProperty" -Name "IdentityReference" -Value $_.IdentityReference
$Result | Add-Member -MemberType "NoteProperty" -Name "Permissions" -Value $_.Permissions
$AllResults += $Result
}

$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
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/core/WinApi.Enum.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ $script:ServiceControlManagerAccessRight = New-Enum $Module WinApiModule.Service
Lock = 0x00000008
QueryLockStatus = 0x00000010
ModifyBootConfig = 0x00000020
ReadControl = 0x00020000
WriteDac = 0x00040000
WriteOwner = 0x00080000
AllAccess = 0x000f003f
GenericRead = 0x00020014 # STANDARD_RIGHTS_READ | SC_MANAGER_ENUMERATE_SERVICE | SC_MANAGER_QUERY_LOCK_STATUS
GenericWrite = 0x00020022 # STANDARD_RIGHTS_WRITE | SC_MANAGER_CREATE_SERVICE | SC_MANAGER_MODIFY_BOOT_CONFIG
Expand Down
104 changes: 7 additions & 97 deletions src/core/WinApi.Wrappers.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -318,96 +318,6 @@ function Get-ServiceHandle {
}
}

function Get-ServiceDiscretionaryAccessControlList {
<#
.SYNOPSIS
Wrapper - Get the DACL of a service (or the Service Control Manager itself)
Author: @itm4n
License: BSD 3-Clause
.DESCRIPTION
This cmdlet takes a service handle returned by 'Get-ServiceHandle' as an input and returns its DACL. If it can't query a service's DACL, it returns null.
.PARAMETER Handle
A mandatory service handle input returned by 'Get-ServiceHandle'.
.PARAMETER SCM
An optional switch specifying whether the service being queried is the Service Control Manager itself.
.EXAMPLE
PS C:\> $ServiceHandle = Get-ServiceHandle -Name "IKEEXT"
PS C:\> $ServiceDacl = Get-ServiceDiscretionaryAccessControlList -Handle $ServiceHandle
PS C:\> $null = $script:Advapi32::CloseServiceHandle($ServiceHandle)
PS C:\> $ServiceDacl
AccessRights : QueryConfig, QueryStatus, EnumerateDependents, Interrogate, GenericExecute
BinaryLength : 20
AceQualifier : AccessAllowed
IsCallback : False
OpaqueLength : 0
AccessMask : 131581
SecurityIdentifier : S-1-5-18
AceType : AccessAllowed
AceFlags : None
IsInherited : False
InheritanceFlags : None
PropagationFlags : None
AuditFlags : None
...
.EXAMPLE
PS C:\> $ServiceControlManagerHandle = Get-ServiceHandle -Name "SCM"
PS C:\> $ServiceDacl = Get-ServiceDiscretionaryAccessControlList -Handle $ServiceControlManagerHandle -SCM
PS C:\> $null = $script:Advapi32::CloseServiceHandle($ServiceControlManagerHandle)
PS C:\> $ServiceDacl
AccessRights : Connect
BinaryLength : 20
AceQualifier : AccessAllowed
IsCallback : False
OpaqueLength : 0
AccessMask : 1
SecurityIdentifier : S-1-5-11
AceType : AccessAllowed
AceFlags : None
IsInherited : False
InheritanceFlags : None
PropagationFlags : None
AuditFlags : None
...
#>

[CmdletBinding()]
param (
[Parameter(Position=0, Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[IntPtr] $Handle,

[Switch] $SCM = $false
)

begin {
if ($SCM) {
$AccessRightEnum = $script:ServiceControlManagerAccessRight
}
else {
$AccessRightEnum = $script:ServiceAccessRight
}
}

process {
$SecurityInfo = Get-ObjectSecurityInfo -Handle $Handle -Type Service
if ($null -eq $SecurityInfo) { return }

$SecurityInfo.Dacl | ForEach-Object {
Add-Member -InputObject $_ -MemberType "NoteProperty" -Name "AccessRights" -Value ($_.AccessMask -as $AccessRightEnum) -PassThru
}
}
}

function Get-ServiceStatus {
<#
.SYNOPSIS
Expand Down Expand Up @@ -1477,7 +1387,7 @@ function Get-ObjectSecurityInfo {
[IntPtr] $Handle,

[Parameter(Mandatory=$true)]
[ValidateSet("File", "Directory", "RegistryKey", "Service")]
[ValidateSet("File", "Directory", "RegistryKey", "Service", "ServiceControlManager")]
[String] $Type
)

Expand All @@ -1489,12 +1399,12 @@ function Get-ObjectSecurityInfo {
process {

switch ($Type) {
"File" { $ObjectType = $script:SE_OBJECT_TYPE::SE_FILE_OBJECT; $AccessRights = $script:FileAccessRight }
"Directory" { $ObjectType = $script:SE_OBJECT_TYPE::SE_FILE_OBJECT; $AccessRights = $script:DirectoryAccessRight }
"Service" { $ObjectType = $script:SE_OBJECT_TYPE::SE_SERVICE; $AccessRights = $script:ServiceAccessRight }
"SCM" { $ObjectType = $script:SE_OBJECT_TYPE::SE_SERVICE; $AccessRights = $script:ServiceControlManagerAccessRight }
"RegistryKey" { $ObjectType = $script:SE_OBJECT_TYPE::SE_REGISTRY_KEY; $AccessRights = $script:RegistryKeyAccessRight }
default { throw "Unhandled object type: $($Type)" }
"File" { $ObjectType = $script:SE_OBJECT_TYPE::SE_FILE_OBJECT; $AccessRights = $script:FileAccessRight }
"Directory" { $ObjectType = $script:SE_OBJECT_TYPE::SE_FILE_OBJECT; $AccessRights = $script:DirectoryAccessRight }
"RegistryKey" { $ObjectType = $script:SE_OBJECT_TYPE::SE_REGISTRY_KEY; $AccessRights = $script:RegistryKeyAccessRight }
"Service" { $ObjectType = $script:SE_OBJECT_TYPE::SE_SERVICE; $AccessRights = $script:ServiceAccessRight }
"ServiceControlManager" { $ObjectType = $script:SE_OBJECT_TYPE::SE_SERVICE; $AccessRights = $script:ServiceControlManagerAccessRight }
default { throw "Unhandled object type: $($Type)" }
}

$SecurityInfo = 7 # DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION
Expand Down
33 changes: 32 additions & 1 deletion src/helper/AccessControl.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ function Get-ObjectAccessRight {
ModifiablePath : wuauserv
IdentityReference : NT AUTHORITY\Authenticated Users (S-1-5-11)
Permissions : {QueryConfig, QueryStatus, EnumerateDependents, Start...}
.EXAMPLE
PS C:\> Get-ObjectAccessRight -Name SCM -Type ServiceControlManager -AccessRights @($script:ServiceControlManagerAccessRight::Connect)
ModifiablePath : SCM
IdentityReference : NT AUTHORITY\Authenticated Users (S-1-5-11)
Permissions : {Connect}
ModifiablePath : SCM
IdentityReference : NT AUTHORITY\INTERACTIVE (S-1-5-4)
Permissions : {Connect, EnumerateService, QueryLockStatus, GenericRead}
#>

[CmdletBinding()]
Expand All @@ -53,7 +64,7 @@ function Get-ObjectAccessRight {
[String] $Name,

[Parameter(Mandatory=$true)]
[ValidateSet("File", "Directory", "RegistryKey", "Service")]
[ValidateSet("File", "Directory", "RegistryKey", "Service", "ServiceControlManager")]
[String] $Type,

[UInt32[]] $AccessRights = $null
Expand Down Expand Up @@ -99,9 +110,19 @@ function Get-ObjectAccessRight {
$script:ServiceAccessRight::ChangeConfig,
$script:ServiceAccessRight::WriteDac,
$script:ServiceAccessRight::WriteOwner,
$script:ServiceAccessRight::GenericWrite,
$script:ServiceAccessRight::AllAccess
)

$ServiceControlManagerModificationRights = @(
$script:ServiceControlManagerAccessRight::CreateService,
$script:ServiceControlManagerAccessRight::ModifyBootConfig,
$script:ServiceControlManagerAccessRight::WriteDac,
$script:ServiceControlManagerAccessRight::WriteOwner,
$script:ServiceControlManagerAccessRight::GenericWrite,
$script:ServiceControlManagerAccessRight::AllAccess
)

$CurrentUserSids = Get-CurrentUserSid
$CurrentUserDenySids = Get-CurrentUserDenySid
}
Expand All @@ -128,6 +149,11 @@ function Get-ObjectAccessRight {
$TargetAccessRights = $(if ($PSBoundParameters['AccessRights']) { $AccessRights } else { $ServiceModificationRights })
$Handle = Get-ServiceHandle -Name $Name -AccessRights $script:ServiceAccessRight::ReadControl
}
"ServiceControlManager" {
$ObjectAccessRights = $script:ServiceControlManagerAccessRight
$TargetAccessRights = $(if ($PSBoundParameters['AccessRights']) { $AccessRights } else { $ServiceControlManagerModificationRights })
$Handle = Get-ServiceHandle -Name "SCM" -SCM -AccessRights $script:ServiceAccessRight::ReadControl
}
default {
throw "Unhandled object type: $($Type)"
}
Expand Down Expand Up @@ -251,6 +277,11 @@ function Get-ObjectAccessRight {
$null = $script:Advapi32::CloseServiceHandle($Handle)
}
}
"ServiceControlManager" {
if ($Handle -ne [IntPtr]::Zero) {
$null = $script:Advapi32::CloseServiceHandle($Handle)
}
}
default {
# Sanity check. We want to make sure to add a 'CloseHandle' function whenever
# a new object type is added to this helper.
Expand Down

0 comments on commit ef1c82f

Please sign in to comment.