Few would challenge the assertion that Windows Management Instrumentation (WMI) has significantly enhanced manageability of the Windows OS desktop. Derived from the Distributed Management Task Force (DMTF) and Web Based Enterprise Management (WBEM) initiatives intended to standardize management of the desktop, WMI allows for standard methods of access to Windows OS components such as registry settings, OS information, PC hardware data, event logs, etc. WMI leverages the Common Information Model (CIM), a general schema describing computer resources and components. The idea of DMTF/WBEM was to standardize access to data and methods across OS platforms previously available only to those who understood the intricacies of the native API set. If you (or a group you are a member of) are not a trustee of the local or domain Administrators group, the default WMI security settings for Windows 2000/XP/2003/Vista (Beta 2) do not allow the ability to remotely connect to the WMI service. This could be problematic since you cannot read what you cannot access. Because organizations may prefer to delegate tasks such as reading remote WMI counters to selected employees without elevating them to Administrator status, the default WMI security may have to be modified.
Using the code
WMI security is implemented by granting trustees permissions to WMI namespaces. A WMI namespace is a collection of logically grouped data and methods describing computer components or services. You can view WMI namespace security on the Windows platform by opening the WMI Control in the Computer Management console, and then expand "Services and Applications." Click "WMI Control" to highlight, then right click and choose "Properties."
You should now see all available items for WMI management. Notice you can back up/restore the WMi repository here (Backup/Restore tab), set the default namespace for scripting (under the Advanced tab), or even set the logging level for errors in this applet.
Go to the Security tab and then expand the Root node of the namespace tree. If you click on a namespace, and then select Security, you should be able to observe the security applied to that particular namespace.
If your machine has retained default security, you should notice that the Administrators group in Windows XP and the Domain Administrators group on a Windows 2003 domain controller are the only two groups with the Remote Enable permissions.
Technically, WMI is a DCOM application, thus any change to DCOM security (via dcomconfig.exe) may also affect WMI. However, it is much safer to implement any necessary changes through the WMI applet shown above, because changes to DCOM security can potentially affect all DCOM applications, something you probably don’t want to do.
Because the group I work for require personnel who are not members of the Domain Administrators group to read CIMv2 data from remote servers, and since I didn’t particularly relish spending valuable time executing terminal service to implement the necessary security modifications, I created the utility described herein. Essentially, I required a tool to read WMI namespace security (possibly to database?) and implement any necessary security changes. Additionally, I wanted a command line utility to script the necessary changes or monitor future WMI namespace settings.
Below are possible security settings for WMI namespaces in Windows 2000/XP/ 2003/Vista (Beta 2).
Execute Methods: Execute Read/Write/Delete objects from CIM
Partial Write : Write to static WMI objects
Provide Write : Write to dynamic WMI objects
Enable Account : Read properties of WMI objects
Remote Enable : Access WMI from remote systems
Read Security : Read security settings
Edit Security : Change security settings
Since these security settings are hosted by the same security model as other objects in a Microsoft OS, you should know something about how access control in the Windows NT/2000/XP/2003 platforms work. Objects in Windows 32-bit platforms are secured by a security descriptor (SD) which contains, among other things, two types of Access Control Lists (ACLs): a Discretionary Access Control List (DACL) and a System Access Control List (SACL). For this utility, we’re only concerned with the DACL, which specifies what a trustee can or cannot do to the object. The DACL is itself comprised of trustee Security IDs (SIDs) and their requisite Access Control Entries (ACEs). Trustees are identified in the DACL by their SID which is mapped to a trustee by the OS upon account creation. When you log into a Windows OS, the OS grants you a token with you personal SID and any SIDs of groups you are a member of. These SIDs are checked by the OS when you try to access an object secured by the OS, and matched against the SIDs in the DACL. You will get the culmination of access permissions granted to the SIDs in your token unless an explicit access denied is encountered.
Now that you have a concept of WMI namespace security, let’s delve into the code to modify it. I wrote the utility for the 1.1 version of the .NET framework, using the C# language. For simplicity, I created two classes,
ViewNameSpaceSecurity which operate as their name implies, inheriting some common connection methods and data from class
NameSpaceSecurity. The utility makes use of Interop services for high-level unmanaged APIs, WMI security methods, and ACE strings, which I’ll explain later. To aid in the ACE string creation, I employed the
AceStringManager class, which derives from
AceString, the actual workhorse.
The main program (WmiSecurity.cs) determines whether the user wants to view existing WMI security or modify existing WMI security by the value of the unsigned integer,
uiCommandLine. Let’s assume the user wants to modify existing security (
CL_MODIFYRECURSE), so a computer name, root namespace, trustee with desired access rights, and recursion switch have been supplied by the caller. Take a look at the method
Help() in WmiSecurity.cs module to determine the exact syntax required. WmiSecurity uses the
CreateAccessMask() method of the
WmiSecurity class shown below to convert the access request from the command line into WMI specific access rights.
public static uint CreateAccessMask(string sAccess)
else if (sAccess.ToUpper()=="REMOTEACCESS")
else if (sAccess.ToUpper()=="PROVIDERWRITE")
else if (sAccess.ToUpper()=="PARTIALWRITE")
else if (sAccess.ToUpper()=="FULLWRITE")
else if (sAccess.ToUpper()=="FULL")
Be aware that the security settings available were consolidated considerably to match common security choices deemed appropriate. For instance, looking at the
CreateAccessMask() method of
WmiSecurity reveals the assignment of
WBEM_ENABLE access to “Read” and additional rights to each consecutive access level, ending with “Full.” Be familiar with what each access level provides and alter this to suit your particular situation.
The main program (WmiSecurity.cs) calls the
ChangeNameSpaceSecurity(), constructor, passing in the namespace to modify and whether the resulting action will recurse through child namespaces as well. The next line sets the Ace flags, which determines whether an access allowed or denied Ace is created, and sends in the unsigned integer representation from
CreateAccessMask(). The main program must now determine whether a backslash is contained in the
sTrustee string passed in by the caller which will determine if the trustee is in domain\trustee format or is in a well known SID format represented by a 2 letter constant.
The 2 letter constants are defined in Sddl.h as well know SIDs, i.e. Domain Users, Domain Guests, Domain Admins, etc. Look at the Platform SDK for detailed information, but suffice to say that ‘DA’ represents domain admins, ‘DG’ represents domain guests, and so on. The important thing to understand is that the OS will take care of converting these constants into useable SIDs for us. We are not so lucky for users and groups the OS has not predefined - for these we must convert the name to a SID.
getSidStringFrom Name() method will do this for us, but we must use some unmanaged APIs to get there. Looking at this method, you can see it is relatively straightforward, employing
ConvertSidToStringSid() unmanaged API’s through Interop services.
public string getSidStringFromName(string sAccount)
StringBuilder sbDomain; Int32 iDomainSize; Int32 iSidSize; IntPtr pAccountSid = IntPtr.Zero; IntPtr pSid = IntPtr.Zero; int iError; SID_NAME_USE snu;
string sSid = "";
sbDomain = new StringBuilder();
LookupAccountName(null, sAccount, pAccountSid,
ref iSidSize, sbDomain, ref iDomainSize, out snu);
if(!LookupAccountName(null, sAccount, pAccountSid,
ref iSidSize, sbDomain, ref iDomainSize, out snu))
throw new Exception("LookupAccountName error: " +
if(!ConvertSidToStringSid(pAccountSid, ref sSid))
throw new Exception("ConvertSidToStringSid error: " +
Figure 1: getSidStringFromName
LookupAccountName in the same manner I would if this were written in the venerable C/C++ language. Like many API’s where the required buffer is unknown, the strategy is to pass in a null pointer (
pAccountSid), telling the API our buffer size (
iSidSize) is zero. The call will fail of course, but it will return (via
iSidSize) the size of the buffer needed for a successful call. We use this to allocate a pointer to a buffer (
pAccountSid), call the API again, and we’re set. If anything goes wrong, we throw an exception with the error code and bug out. Returned to the main program should be a textual SID representation of the trustee in question.
ChangeNameSpaceSecurity uses the
Modify() method to implement security modifications.
ChangeNamespaceSecurity first ensures that the object has been properly initialized, i.e. a namespace has been chosen, the
bAccessAllowed sentinel has been set, the actual access control flags have been set (i.e. Read or Write), and whether or not security modifications on the namespace will be applied to all objects under the namespace recursively. Assuming these tasks have been done, we attempt to connect to the remote computer’s WMI service. If all is well then we should see the “Connected to \\xyz” on the command line, and if not, we throw an exception.
Before anything else is done,
Modify() attempts to create an Ace string object for the pending WMI method calls. Ace strings are a subset of Security Descriptor Definition Language (SDDL), which is itself a string representation of a security descriptor. SDDL and the corresponding high level unmanaged API’s make working with access security in Microsoft systems considerably easier than it used to be. Having worked previously with the low level access control API’s, I can tell you this format saves a lot of wrangling that was synonymous with access control code. The Ace string component of SDDL is structured as shown below:
Notice that each component is separated by a semi-colon and each component must be in order. With this in mind, the AceStringManager object’s constructor is called as shown below:
AceStringManager asm = new AceStringManager(sTrusteeSid,
which constructs the new object with the trustee SID (more on this), ace type, and whether or not the modifications will be recursive to other objects in the namespace.
AceStringManager inherits from the
AceString class, which actually does the grunt work of constructing a workable Ace string. When the
AceStringManager’s only method,
AceStringManager in turn calls
To clarify, let’s assume you want to allow the HelpDesk group the remote connect and read ability on your network servers.
CreateAccessMask() would convert the
REMOTEACCESS request from the command line into the access mask
ReturnSddlString() method would consecutively call
WBEM_ENABLE and then
WBEM_REMOTE_ACCESS. This in turn adds the “WP” and “CC” to the
AceString, which ultimately comprises part of the Ace string created. To ensure that each right added to the Ace string is unique,
AceString searches for a previous instance via the
IndexOf () method of the
AceStringManager, we now call
CreateFinalAceString(), passing in the SID string of a trustee we obtained earlier or the well known SID string constant. We first start by indicating whether the ACE is access allowed or denied by adding an ‘A’ or a ‘D’ to the
StringBuilder object followed by a semicolon. Depending on whether or not we want to apply the modification recursively, we next either append ‘CI’ (container-inherit) or nothing, followed again by a semicolon. We next append
m_sbRights string (created previously by
m_SbAceString, followed again by a semicolon. Since this utility will not define an
object_guid or an
inherit_object_guid, we leave these empty, as shown by appending 2 semicolons to
CreateFinalAceString() appends the SID string to
m_sbAceString. This Ace string is now passed back through
AceStringManager to the original caller,
ChangeNameSpaceSecurity. If you want to learn more about Ace strings or SDDL syntax, you can do a search on “Ace Strings” or “SDDL” in the platform SDK.
Now having obtained the necessary Ace string, we make use of
ManagementPath class, whose job it is to build a path to the intended WMI object, in this case our selected namespace. If all is OK, we now call on
System.Management.ManagementClass, which takes our
m_ms, (defined at connection), the
m_mp, and finally any connection options and allows us to invoke methods on the management object (the namespace) we desire. If you wonder where WMI methods or data can be viewed, you should investigate the WMI Object Browser, part of the WMI Tools available from Microsoft’s web site. A detailed explanation could be the subject for another article, but suffice to say that some investigation on your part is definitely worthwhile. This utility reveals all namespaces available, as well as data and methods available for each object in the namespace.
If you look up
ManagementBaseObject in the Framework 1.1 SDK you’ll see something similar to this:
public ManagementBaseObject InvokeMethod, string methodName,
ManagementBaseObject inParameters, InvokeMethodOptions options);
And since we’re looking to get the existing security on the namespace, we use “
GetSD” as the method, we have no
inParameters, and no options either. Since each method call is unique, it’s a good idea to see what success is defined as – in this case 0.
Now to view the resulting data, we have to understand a little more about Security Descriptors (SDs). Security Descriptors are defined as an opaque structure of pointers to DACLs and SACLs, Primary Owner SIDs, etc. Opaque means you should never attempt to directly modify the contents of a SD directly – instead you should defer to existing APIs specifically created for this purpose. I alluded to the fact that we’ll be using the high level access control APIs, and here’s where the payoff is. We can take the output of the WMI method
GetSD, namely a pointer to the SD, and use this as input to the
ConvertSecurityDescriptorToStringSecurityDescriptor() unmanaged API. Of course, if something unexpected happens during the WMI
GetSd() method, we throw an exception and bug out.
You’ll notice the use of Platform Invocation (PInvoke) for calling unmanaged API’s in this utility. For the 1.1 framework, there is no way around it for some of the security API’s, although that may change in the future. A comprehensive look at InteropServices would fill a book, so I’ll just touch on a few things. Notice the error code after the call to
ConvertSecuritydDescriptorToStringSecurityDescriptor() call, namely that which obtains the error via
Marshall is a framework class that, according to the documentation:
“Provides a collection of methods for allocating unmanaged memory, copying unmanaged memory blocks, and converting managed to unmanaged types, as well as other miscellaneous methods used when interacting with unmanaged code.”
GetLastWin32Error() call grabs the Win32 return from the unmanaged API call executed. You also need this class to free memory allocated from unmanaged API calls. To do this, use the
FreeHGlobal() method, as this will free the memory pointing to the security descriptor structure we asked for. So having obtained the unmanaged string representing a SD, we call
Auto() from the
Marshall class, which allocates a managed string and copies characters from the unmanaged string to it. After this, we simply display the resulting string representing the old namespace security to the screen. Now remember the Ace string created to modify the target computer’s WMI namespace? It is still in string form, so it needs to be converted into a structure format the system can understand. That’s exactly what the next line:
stringSecurityDescriptor, 1, out pSystemSD, out sdSize)
does. The variable
pSystemSD is essentially a pointer to the variable that receives the converted security descriptor. The next line two lines:
byte securityDescriptor = new byte[sdSize];
Marshal.Copy(pSystemSD,securityDescriptor, 0, sdSize);
creates a byte array with size defined by
sdSize, and then copies the contents of the unmanaged memory pointer (
pSystemSD, our SD “blob”) to a managed 8-bit unsigned integer array, in this case
securityDescriptor. Note the 0 is the stating index of the copy, and we copy exactly the size of
sdSize. Note the next line:
ManagementBaseObject inParams = systemSecurity.GetMethodParameters("SetSD");
Which returns a
ManagementBaseObject representing the input parameters for our final call,
SetSD(). The next few lines accomplish this:
inParams["SD"] = securityDescriptor;
outParams = systemSecurity.InvokeMethod("SetSD", inParams, null);
Essentially, we pass in a SD formatted the way the system can use, and invoke the
SetSD() method call. If the return value isn’t what we like, we’ll throw an exception alerting the caller to this. In the finally section, we should free unmanaged memory we have allocated, namely
pStringSD. To view existing security on WMI Namespaces, the main program (WmiSecurity.cs) needs only the computer to run on, the namespace to target, and whether we want to recursively look at subsequent namespace objects. Because the value of
uiCommandLine would be
CL_VIEWRECURSE, the main program will use the
ViewNameSpaceSecurity class versus the
ChangeNameSpaceSecurity class used earlier.
ViewNameSpaceSecurity uses the
ViewSecurity() method, which in turn calls the
ViewSecurityDescriptor() method to obtain and display the textual SID. I won’t bother to explain this method as it is conceptually very similar to
Modify() method. The only change here is that
ViewSecurity() calls the
EnumNameSpaces() method to recurse through subsequent namespace objects when this option is indicated.
So at this point, some examples should suffice to illustrate the use of the utility. Suppose you wanted to add the domain HelpDesk group to the CIMV2 and subsequent namespaces on the S223001 computer. Further, suppose this group should have the ability to remotely connect and read WMI objects. To do this:
WmiSecurity /C=S223001 /A /N=Root\CimV2 /M=MyDomain\HelpDesk:REMOTEACCESS /R
Of course, the user executing the utility requires the Edit Security right on the target namespace or the ubiquitous “Access Denied” error will appear. Likewise, to view the existing security on a WMI namespace:
WmiSecurity /C=S223001 /N=Root\CimV2 /R
Which will present the security on the Root\Cimv2 namespace and all child namespaces. Where /C = Computer (without UNC specification) to view. Where /N = Namespace to target Where /R (optional)= view all subsequent WMI namespaces as well as present one
One final thought: please be aware that any modification to WMI namespace security could potentially weaken system security. Thus, extensive testing should preclude any WMI namespace security modification, particularly in a real world environment. Properly planned, WMI security modifications will enhance employee productivity without introducing possible security breaches.
Points of Interest
This is my first foray into the managed world code with respect to systems programming. In short, there is a fair amount of research to be done if the appropriate method is not available within the .NET framework to accomplish what you want. On the other hand, the actual coding and testing of the project is much easier, and with built in exception handling, dependable. WMI is a part of the Windows platform I would recommend all systems engineers and programmers look into.
This project was developed about 11 months ago, and I have since moved onto other things. However, I plan to recode this in C++ so that WMI namespace security fits into my larger overall object security code I have written.