Scripting WMI Namespace Security (part 2 of 3)

In the first part of this series, we discussed what WMI
namespace security was and why it is something you would want to change.   For
this blog post, I’ll show a Powershell script for retrieving the current
security descriptor of a WMI namespace. 
Note that everything I’m doing in the Powershell script can be done in
vbscript, but I’ll leave that as an exercise to the reader.  Here’s the entire script.  I’ll discuss the various sections to explain
why I did something.  Note that this is
not intended to be a discussion about Powershell, so I’m focusing on the WMI
specific parts.

# Copyright (c) Microsoft Corporation. All rights reserved.

# For personal use only. Provided AS IS and WITH ALL FAULTS.

# Get-WmiNamespaceSecurity.ps1

# Example: Get-WmiNamespaceSecurity root/cimv2

Param ( [parameter(Mandatory=$true,Position=0)][string] $namespace,

    [string] $computer = ".",

    [System.Management.Automation.PSCredential] $credential = $null)

Process {

    $ErrorActionPreference = "Stop"

    Function Get-PermissionFromAccessMask($accessMask) {

        $WBEM_ENABLE = 1

  $WBEM_METHOD_EXECUTE = 2

    $WBEM_FULL_WRITE_REP = 4

    $WBEM_PARTIAL_WRITE_REP = 8

    $WBEM_WRITE_PROVIDER = 0x10

    $WBEM_REMOTE_ACCESS = 0x20

        $READ_CONTROL = 0x20000

        $WRITE_DAC = 0x40000

       

        $WBEM_RIGHTS_FLAGS = $WBEM_ENABLE,$WBEM_METHOD_EXECUTE,$WBEM_FULL_WRITE_REP,`

            $WBEM_PARTIAL_WRITE_REP,$WBEM_WRITE_PROVIDER,$WBEM_REMOTE_ACCESS,`

            $WBEM_RIGHT_SUBSCRIBE,$WBEM_RIGHT_PUBLISH,$READ_CONTROL,$WRITE_DAC

        $WBEM_RIGHTS_STRINGS = "Enable","MethodExecute","FullWrite","PartialWrite",`

            "ProviderWrite","RemoteAccess","Subscribe","Publish","ReadSecurity","WriteSecurity"

        $permission = @()

        for ($i = 0; $i -lt $WBEM_RIGHTS_FLAGS.Length; $i++) {

            if (($accessMask -band $WBEM_RIGHTS_FLAGS[$i]) -gt 0) {

  $permission += $WBEM_RIGHTS_STRINGS[$i]

            }

        }

       

        $permission

    }

    $INHERITED_ACE_FLAG = 0x10

    $invokeparams = @{Namespace=$namespace;Path="__systemsecurity=@";Name="GetSecurityDescriptor";ComputerName=$computer}

    if ($credential -eq $null) {

        $credparams = @{}

    } else {

        $credparams = @{Credential=$credential}

    }

    $output = Invoke-WmiMethod @invokeparams @credparams

    if ($output.ReturnValue -ne 0) {

        throw "GetSecurityDescriptor failed: $($output.ReturnValue)"

    }

   

    $acl = $output.Descriptor

    foreach ($ace in $acl.DACL) {

        $user = New-Object System.Management.Automation.PSObject

        $user | Add-Member -MemberType NoteProperty -Name "Name" `

            -Value "$($ace.Trustee.Domain)\$($ace.Trustee.Name)"

        $user | Add-Member -MemberType NoteProperty -Name "Permission" `

            -Value (Get-PermissionFromAccessMask($ace.AccessMask))

        $user | Add-Member -MemberType NoteProperty -Name "Inherited" `

            -Value (($ace.AceFlags -band $INHERITED_ACE_FLAG) -gt 0)

        $user

    }

}

 

Internally, all OS Security Descriptors, ACLs, ACEs, etc…
are just represented by numbers.  I would
not expect that an admin would remember that the Remote Enable permission has
the numerical value of 0x20 (decimal 32). 
So the internal function Get-PermssionFromAccessMask was created to
translate the “magic numbers” into human readable form.

Here I store all the numerical values into descriptive
variables.  If you’re wondering where I
got these values from, they are from wbemcli.h and winnt.h (for the last two
related to permissions for reading and writing to the ACL).

    Function Get-PermissionFromAccessMask($accessMask) {

        $WBEM_ENABLE = 1

  $WBEM_METHOD_EXECUTE = 2

    $WBEM_FULL_WRITE_REP = 4

    $WBEM_PARTIAL_WRITE_REP = 8

    $WBEM_WRITE_PROVIDER = 0x10

    $WBEM_REMOTE_ACCESS = 0x20

        $READ_CONTROL = 0x20000

  $WRITE_DAC = 0x40000

 

Next, I store the values and text representations in
arrays.  Each element maps to the
corresponding index of the other.  I can
then simply see which permissions are included in the access mask and create a
new array that contains the text of the permission.

        $WBEM_RIGHTS_FLAGS = $WBEM_ENABLE,$WBEM_METHOD_EXECUTE,$WBEM_FULL_WRITE_REP,`

            $WBEM_PARTIAL_WRITE_REP,$WBEM_WRITE_PROVIDER,$WBEM_REMOTE_ACCESS,`

            $READ_CONTROL,$WRITE_DAC

        $WBEM_RIGHTS_STRINGS = "Enable","MethodExecute","FullWrite","PartialWrite",`

            "ProviderWrite","RemoteAccess","ReadSecurity","WriteSecurity"

        $permission = @()

        for ($i = 0; $i -lt $WBEM_RIGHTS_FLAGS.Length; $i++) {

            if (($accessMask -band $WBEM_RIGHTS_FLAGS[$i]) -gt 0) {

                $permission += $WBEM_RIGHTS_STRINGS[$i]

            }

        }

 

The next section takes advantage of Powershell 2.0 splatting
so I can store commonly used parameters in a single variable.  In this script, I don’t reuse the parameters,
but I tend to make it a habit.  If
credentials were not specified I don’t want the credential prompt to come up
since this script can be used locally or remotely. 

    $invokeparams = @{Namespace=$namespace;Path="__systemsecurity=@";Name="GetSecurityDescriptor";ComputerName=$computer}

    if ($credential -eq $null) {

        $credparams = @{}

    } else {

        $credparams = @{Credential=$credential}

    }

 

Finally, I want to output the result as an object, not
simply as text so that it can be piped to another cmdlet for further
processing.  So I do the actual call to
__SystemSecurity::GetSecurityDescriptor() and get back a
Win32_SecurityDescriptor (checking the ReturnValue first).  I walk through each ACE (Access Control
Entry) and store the username, permission set, and whether it’s inherited into
my new object, then I send that object to the pipeline.

    $output = Invoke-WmiMethod @invokeparams @credparams

    if ($output.ReturnValue -ne 0) {

        throw "GetSecurityDescriptor failed: $($output.ReturnValue)"

    }

   

    $acl = $output.Descriptor

    foreach ($ace in $acl.DACL) {

        $user = New-Object System.Management.Automation.PSObject

        $user | Add-Member -MemberType NoteProperty -Name "Name" `

            -Value "$($ace.Trustee.Domain)\$($ace.Trustee.Name)"

        $user | Add-Member -MemberType NoteProperty -Name "Permission" `

            -Value (Get-PermissionFromAccessMask($ace.AccessMask))

        $user | Add-Member -MemberType NoteProperty -Name "Inherited" `

            -Value (($ace.AceFlags -band $INHERITED_ACE_FLAG) -gt 0)

        $user

    }

 

Next time, we’ll look at setting namespace security which is
a bit more complicated.

Steve Lee

Senior Test Manager

Microsoft