Condividi tramite


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

Comments

  • Anonymous
    November 07, 2013
    The comment has been removed

  • Anonymous
    November 08, 2013
    What OS and version of PowerShell are you using?  Did you cut and paste the script directly into ISE?

  • Anonymous
    February 04, 2014
    I'm having the same excact error as Stefan. Windows Server 2008 R2 Copy/pasted the script directly to notepad and then tried running it with powershell.

  • Anonymous
    February 06, 2014
    I'll install W2k8R2 and will try this out.  There may have been changes in PowerShell that enable it to work on newer versions that I tested against.

  • Anonymous
    February 10, 2014
    Ok, I found a W2k8R2 VHD and I copied and pasted the text from above to 'test.ps1' and ran it in PowerShell and it worked for me.  $PSVersionTable.PSVersion tells me I'm running PowerShell 2.0.  What version of PowerShell are you running?

  • Anonymous
    April 09, 2014
    PS v4 has some issues with this script. Add-Member needs to be modified to something like Add-Member -NotePropertyName "Name" -NotePropertyValue "$($ace.Trustee.Domain)$($ace.Trustee.Name)" Then  $WBEM_RIGHT_SUBSCRIBE $WBEM_RIGHT_PUBLISH need to be dropped.

  • Anonymous
    April 09, 2014
    $WBEM_RIGHT_SUBSCRIBE $WBEM_RIGHT_PUBLISH have nothing to do with PS version. They need to be modified based on OS version.

  • Anonymous
    September 10, 2014
    hi am getting this error, am missing something? Invoke-WmiMethod : Invalid namespace At line:32 char:31

  •     $output = Invoke-WmiMethod <<<<  @invokeparams @credparams    + CategoryInfo          : InvalidOperation: (:) [Invoke-WmiMethod], ManagementException    + FullyQualifiedErrorId : InvokeWMIManagementException,Microsoft.PowerShell.Commands.InvokeWmiMethod
  • Anonymous
    September 11, 2014
    Which namespace did you provide?

  • Anonymous
    January 12, 2015
    The comment has been removed

  • Anonymous
    January 12, 2015
    @Jon, sounds like you have general WMI remoting issues unrelated to this specific script?  Did you try the troubleshooting steps already?  msdn.microsoft.com/.../aa394603(v=vs.85).aspx Steve [MSFT]

  • Anonymous
    February 03, 2015
    I think you've missed something when updating this post. In the first block you add 10 values to the $WBEM_RIGHTS_FLAGS, but only define 8 actual flags. This will produce an error due to the 2 missing variable definitions. In the later block you define 8 values in $WBEM_RIGHTS_FLAGS and 8 flags. This block will work.

  • Anonymous
    March 29, 2015
    @Steve  Works fine on Windows 2008 and 2012 R2. NOT working on Windows 2008 R2. GetSecurityDescriptor method reports error 0x8004101d: Unexpected error namespace "rootcimv2"

  • Anonymous
    March 30, 2015
    Since there seems to be continued interest in this, let me see about revisiting the code and updating it.  No ETA at this time however.