Understanding Privileges and Account Rights

[Previous] [Next]

A privilege is a right assigned to a trustee that has system-wide ramifications. (In contrast, an access right assigned to a trustee defines access allowed to a specific object in the system.) Examples of privileges include the rights to log on locally, back up files and directories, and shut down the system.

Another way of looking at a privilege is as a way to secure a system function. For example, certain system functions, such as LogonUser and InitiateSystemShutdown, require that the calling software have the proper privileges or else the function will fail.

Privileges have two textual representations you should be aware of. These representations are the friendly name that shows up in the user interface for Windows and the programmatic name used by your software. Each of the programmatic names is defined in a header file in the SDK.

Table 9-10 lists all the privileges available in Windows 2000 at the time of this book's printing. A partial list is given in the Platform SDK documentation.

Table 9-10. Privileges and account rights

Programmatic Name Display Name Header File
Replace a process level token WinNT.h
Generate security audits WinNT.h
Back up files and directories WinNT.h
Bypass traverse checking WinNT.h
Create a pagefile WinNT.h
Create permanent shared objects WinNT.h
Create a token object WinNT.h
Debug programs WinNT.h
Enable computer and user accounts to be trusted for delegation WinNT.h
Increase scheduling priority WinNT.h
Increase quotas WinNT.h
Load and unload device drivers WinNT.h
Lock pages in memory WinNT.h
Add workstations to domain WinNT.h
Profile single process WinNT.h
Force shutdown from a remote system WinNT.h
Restore files and directories WinNT.h
Manage auditing and security log WinNT.h
Shut down the system WinNT.h
Synchronize directory service data WinNT.h
Modify firmware environment values WinNT.h
Profile system performance WinNT.h
Change the system time WinNT.h
Take ownership of files or other objects WinNT.h
Act as part of the operating system WinNT.h
Remove computer from docking station WinNT.h
Receive unsolicited device input WinNT.h
Log on as a batch job NTSecAPI.h
Deny logon as a batch job NTSecAPI.h
Deny logon locally NTSecAPI.h
Deny access to this computer from the network NTSecAPI.h
Deny logon as a service NTSecAPI.h
Log on locally NTSecAPI.h
Access this computer from the network NTSecAPI.h
Log on as a service NTSecAPI.h

The system actually distinguishes between the privilege and its close cousin, the account right, but they are assigned and revoked identically. In Table 9-10, the privileges are defined in WinNT.h and the account rights are defined in NTSecAPI.h.

Now that you have some familiarity with what privileges are and how the system represents them, let's dive into assigning and revoking privileges on a system.

The LSA Functions

To assign privileges programmatically, you need to become familiar with a set of functions known as the LSA functions. LSA stands for Local Security Authority, which handles user logon and authentication on the local machine. The LSA functions can be used to do a number of tasks, of which we will only be concerned with privilege assignment in this chapter.The first step in using the LSA functions is to retrieve a handle to a policy object. A policy object represents the system on which you will be performing account management functions. To obtain a handle to a policy object, you must call LsaOpenPolicy.

 NTSTATUS LsaOpenPolicy(    PLSA_UNICODE_STRING    plsastrSystemName,    PLSA_OBJECT_ATTRIBUTES pObjectAttributes,    ACCESS_MASK            DesiredAccess,    PLSA_HANDLE            pPolicyHandle); 

This function might look a bit strange at first, because the data types aren't commonly used in other Win32 functions. So let's examine them one by one.

The plsastrSystemName parameter is a pointer to an LSA_UNICODE_STRING structure, which is defined as follows:

 typedef struct _LSA_UNICODE_STRING {    USHORT Length;    USHORT MaximumLength;    PWSTR  Buffer; } LSA_UNICODE_STRING 

Like the Net functions, the LSA functions deal only with strings in Unicode format. However, unlike the Net functions, the LSA functions require that all strings be managed in terms of the LSA_UNICODE_STRING structure, which is simply a string length, a buffer length, and a pointer to a buffer.

It is important to remember that the Length (indicating the length of the string) and MaximumLength (indicating the length of the buffer) members are stored in terms of bytes, not characters.

The string pointed to by the Buffer member of the LSA_UNICODE_STRING structure is not explicitly defined to be zero-terminated. Although you can choose to end a string with a zero, you must be sure not to include the null termination in your calculation of the length of the string. Also keep in mind that when the system returns an LSA_UNICODE_STRING structure to your software, it cannot count on zero termination of the string pointed to by the Buffer member.

Dealing with the LSA_UNICODE_STRING type can be a little awkward because it is length-delimited rather than zero-terminated. Because of this, I think that the LSA_UNICODE_STRING virtually begs to be wrapped in a simple C++ class. I've made a class called CLSAStr, which does this, and use it liberally in the TrusteeMan sample application described later in this chapter.

To call LsaOpenPolicy, initialize an instance of the LSA_UNICODE_STRING structure and point it to a Unicode string containing the name of the system for which you wish to manipulate privileges. Passing NULL as the plsastrSystemName parameter indicates the local machine.

The pObjectAttributes parameter points to an instance of the LSA_OBJECT_ATTRIBUTES structure, which is unused at this time and should have each of its members initialized to 0.

The DesiredAccess parameter is declared as type ACCESS_MASK, which maps to a 32-bit unsigned integer. If you will be viewing privilege information on your system, combine POLICY_VIEW_LOCAL_INFORMATION and POLICY_LOOKUP_NAMES as in the example that follows this discussion. If you will be setting privilege information, you should also combine the POLICY_CREATE_ACCOUNT access flag.

The final parameter of LsaOpenPolicy requires that you pass the address of a variable of type LSA_HANDLE. Upon success, the system will place the handle to the open LSA policy object in this variable. Once you have finished with the policy object, you should pass this handle to the LsaClose function:

 NTSTATUS LsaClose(LSA_HANDLE hObjectHandle); 

All LSA functions return a value of type NTSTATUS, and you should pass this value to LsaNtStatusToWinError:

 ULONG LsaNtStatusToWinError(NTSTATUS Status); 

The LsaNtStatusToWinError function converts the NT status code to a familiar Win32 error code just like those returned from GetLastError. After conversion, a successful call to LsaOpenPolicy returns a value of ERROR_SUCCESS.

The following code shows the proper way to open an LSA policy object for a system on the network whose name is "JasonsComputer":

 LSA_OBJECT_ATTRIBUTES   lsaOA = { 0 }; LSA_UNICODE_STRING      lsastrComputer = { 0 }; LSA_HANDLE              hPolicy = NULL; // Computer Name WCHAR* pstrName = L"JasonsComputer"; // Set the size of the useless LSA_OBJECT_ATTRIBUTES structure lsaOA.Length = sizeof(lsaOA); // Fill in the members of the LSA_UNICODE_STRING structure lsastrComputer.Length = (USHORT) (lstrlen(pstrName) * sizeof(WCHAR)); lsastrComputer.MaximumLength = lsastrComputer.Length + sizeof(WCHAR); lsastrComputer.Buffer = pstrName; // Retrieve the policy handle NTSTATUS ntStatus = LsaOpenPolicy(&lsastrComputer, &lsaOA,    POLICY_VIEW_LOCAL_INFORMATION | POLICY_LOOKUP_NAMES |    POLICY_CREATE_ACCOUNT, &hPolicy); ULONG lErr = LsaNtStatusToWinError(ntStatus); if (lErr != ERROR_SUCCESS){    // Handle error } 

Now that you know how to obtain an open handle to an LSA policy object, you are ready to begin managing the privileges on the system.

Enumerating Privileges

There are two ways to enumerate the privileges on a system: either obtain a list of the privileges held by a specific trustee, or request a list of trustees that hold a specific privilege.

Privileges Held by a Specific Trustee

Let's start by discussing how to obtain a list of privileges held by a specific trustee of the system. To enumerate the privileges, use the LsaEnumerateAccountRights function:

 NTSTATUS LsaEnumerateAccountRights(    LSA_HANDLE           hPolicy,     PSID                 psidTrustee,     PLSA_UNICODE_STRING* pplsastrUserRights,      PULONG               plCountOfRights); 

You must pass an open policy object created with POLICY_LOOKUP_NAMES access as the hPolicy parameter of LSAEnumerateAccountRights. The psidTrustee parameter is a pointer to the SID for the trustee for which you want to enumerate privileges. You can use LookupAccountName (discussed earlier in the section "Understanding SIDs") to obtain a SID from a trustee's account name. This trustee can be a user account, group account, or computer account.

You should pass the address of a variable of type LSA_UNICODE_STRING as the pplsastrUserRights parameter of LsaEnumerateAccountRights. The system generates an array of LSA_UNICODE_STRING structures, allocates a buffer to hold the array, and then places a pointer to the buffer in the variable pointed to by the pplsastrUserRights parameter. The system then returns the number of privileges in the array in the ULONG variable pointed to by plCountOfRights.

Because the system has allocated a buffer on your behalf, you must free the buffer when you are finished with it. Passing a pointer to the buffer to the LsaFreeMemory function does this:

 NTSTATUS LsaFreeMemory(PVOID pvBuffer); 

If LsaEnumerateAccountRights succeeds, the translated status code will be ERROR_SUCCESS. If the account has no privileges assigned to it, the translated error code will be ERROR_FILE_NOT_FOUND. The elements of the array returned by LsaEnumerateAccountRights are of type LSA_UNICODE_STRING (defined earlier in the section "The LSA Functions"). Each element will point to a buffer containing a Unicode string representation of an account right, including values such as SeDebugPrivilege and SeEnableDelegationPrivilege. You can refer back to Table 9-10 for a list of all possible returned privileges.

Although it is not directly related to enumerating privileges, the LookupPrivilegeDisplayName function should be mentioned. This function translates a programmatic privilege name, such as SeTcbPrivilege, into its friendly display name, which in this case would be "Act as part of the operating system".

 BOOL LookupPrivilegeDisplayName(    PCTSTR pszSystemName,    PCTSTR pszName,    PTSTR  pszDisplayName,    PDWORD cbDisplayName,     PDWORD pLanguageId); 

This function takes the system name and the programmatic name for the privilege, and then returns the friendly name in a buffer that you supply.

LookupPrivilegeDisplayName will not return the display name for an account right. It works only for privileges. Although our discussion here does not distinguish between these two types of account right, this topic is discussed more fully in Chapter 11.

You can determine whether an account right is a privilege by referring to Table 9-10. If the right in question is defined in the WinNT.h header file, it is a privilege. If it is defined in the NTSecAPI.h header file, it is only an account right, not an actual privilege.

The following sample function shows how to list all the privileges held by a trustee. It takes an LSA policy handle and a PSID as its parameters.

 BOOL PrintTrusteePrivs(LSA_HANDLE hPolicy, PSID psid) {    BOOL fSuccess = FALSE;    WCHAR szTempPrivBuf[256];    WCHAR szPrivDispBuf[1024];    PLSA_UNICODE_STRING plsastrPrivs = NULL;    __try {       // Retrieve the array of privileges for the given SID       ULONG lCount = 0;       NTSTATUS ntStatus = LsaEnumerateAccountRights(hPolicy, psid,          &plsastrPrivs, &lCount);       ULONG lErr = LsaNtStatusToWinError(ntStatus);       if (lErr != ERROR_SUCCESS) {          plsastrPrivs = NULL;          __leave;       }       ULONG lDispLen = 0;       ULONG lDispLang = 0;       for (ULONG lIndex = 0; lIndex < lCount; lIndex++) {          // Assure zero termination          lstrcpyn(szTempPrivBuf,             plsastrPrivs[lIndex].Buffer, plsastrPrivs[lIndex].Length);          szTempPrivBuf[plsastrPrivs[lIndex].Length] = 0;          wprintf(L"Programmatic Name: %s\n", szTempPrivBuf);          // Translate to Display Name          lDispLen = 1024; // Size of static Display buffer          if (LookupPrivilegeDisplayName(NULL, szTempPrivBuf,              szPrivDispBuf, &lDispLen, &lDispLang))             wprintf(L"Display Name: %s\n\n", szPrivDispBuf);       }       fSuccess = TRUE;    }    __finally {       if (plsastrPrivs) LsaFreeMemory(plsastrPrivs);    }    return(fSuccess); } 

Trustees That Hold a Specific Privilege

Now for the second way to retrieve privilege information for a Windows 2000 system: request a list of trustees that hold a specific privilege by calling LsaEnumerateAccountsWithUserRight.

 NTSTATUS LsaEnumerateAccountsWithUserRight(    LSA_HANDLE          hPolicy,     PLSA_UNICODE_STRING plsastrUserRight,     PVOID*              ppvEnumerationBuffer,     PULONG              CountReturned); 

This function requires a handle to an LSA policy object much like LsaEnumerateAccountRights does. However, instead of filling an LSA_UNICODE_STRING structure with the name of a trustee, you should pass a pointer to an LSA_UNICODE_STRING structure complete with the programmatic name of a privilege or an account right.

The system returns trustee information to your software by allocating a buffer and returning its pointer via the ppvEnumerationBuffer parameter. Although this parameter is defined as a pointer to a PVOID, you should pass the address of a pointer to a variable of type PLSA_ENUMERATION_INFORMATION because the system will return the information as an array of LSA_ENUMERATION_INFORMATION structures. It would have been nice if the developers of Windows had defined LsaEnumerateAccountsWithUserRight to take a pointer to the correct type, but they chose instead to require a pointer to a PVOID, so you will have to cast this parameter.

The LSA_ENUMERATION_INFORMATION structure is actually very simple and contains only a single member—a pointer to a SID:


The number of elements in the array is returned in the variable pointed to by CountReturned, which is the final parameter of LsaEnumerateAccountsWithUserRight. The SIDs in the returned array can be used to generate a trustee name by passing the SID to LookupAccountSid, discussed earlier in this chapter. When you are finished with the buffer returned by LsaEnumerateAccountsWithUserRight, you should pass its pointer to LsaFreeMemory.

Assigning and Removing Privileges

It is uncommon to have to create software solely to enumerate the privileges given to a trustee account. However, just about any software that creates trustee accounts will need to assign (and perhaps remove) privileges from an account. Fortunately, the LSA functions provide two simple functions to perform these tasks. The first, LsaAddAccountRights, is used to grant privileges to a trustee:

 NTSTATUS LsaAddAccountRights(    LSA_HANDLE          hPolicy,    PSID                psidTrustee,    PLSA_UNICODE_STRING plsastrUserRights,    ULONG               lCountOfRights); 

This function is easy to use. You must pass the handle to an open LSA policy object, as well as a pointer to the SID for the trustee whose privileges you wish to modify. (You can obtain a SID for a user or group account by calling LookupAccountName, as discussed earlier in this chapter.)

Before calling LsaAddAccountRights, however, you should build an array of LSA_UNICODE_STRING structures, each with the name of a privilege you want the trustee to hold. Pass a pointer to this array as the plsastrUserRights parameter of LsaAddAccountRights. Finally, pass the number of elements in the array as the final parameter, lCountOfRights.

The LsaAddAccountRights function ignores any account rights or privileges already held by the trustee. It fails, however, if any of the rights in the array are not valid names for the system.

The following code provides an example of how to assign the SeInteractiveLogonRight account right and the SeTcbPrivilege privilege to a trustee account whose SID is pointed to by the variable psid.

 LSA_UNICODE_STRING lsastrPrivs[2] = { 0 }; lsastrPrivs[0].Buffer = SE_INTERACTIVE_LOGON_NAME; lsastrPrivs[0].Length =     lstrlen(lsastrPrivs[0].Buffer) * sizeof(WCHAR); lsastrPrivs[0].MaximumLength = lsastrPrivs[0].Length + sizeof(WCHAR); lsastrPrivs[1].Buffer = SE_TCB_NAME; lsastrPrivs[1].Length =    lstrlen(lsastrPrivs[1].Buffer) * sizeof(WCHAR); lsastrPrivs[1].MaximumLength = lsastrPrivs[1].Length + sizeof(WCHAR); NTSTATUS ntStatus = LsaAddAccountRights(hPolicy, psid,    lsastrPrivs, 2); ULONG lErr = LsaNtStatusToWinError(ntStatus); 

The function used to remove privileges from a trustee account is similar to LsaAddAccountRights. It is called LsaRemoveAccountRights and is prototyped as follows:

 NTSTATUS LsaRemoveAccountRights(    LSA_HANDLE          hPolicy,    PSID                psidTrustee,    BOOLEAN             fAllRights,    PLSA_UNICODE_STRING plsastrUserRights,    ULONG               lCountOfRights); 

This function is the same as LsaAddAccountRights except that it removes the list of rights from the trustee indicated by psidTrustee and has one extra parameter, the Boolean fAllRights.

The fAllRights parameter allows you to remove all privileges from a trustee without building a list of the privileges held by the trustee. If you pass TRUE for this parameter, the plsastrUserRights and lCountOfRights parameters should be NULL and 0, respectively. If you pass FALSE for the fAllRights parameter, LsRemoveAccountRights is used the same way as LsaAddAccountRights.

Before wrapping up our discussion of LsaRemoveAccountRights, I should point out one detail about removing all rights from an account. The Platform SDK documentation states that if you pass TRUE for the fAllRights parameter, the system will remove all privileges from the account and then delete the account from the system. However, this is true only from a certain perspective. Each system running Windows 2000 must maintain a local database of account privilege entries (technically implemented internally as a local account) even if the trustee account lives on another machine such as a domain controller. The LsaRemoveAccountRights function will not delete a trustee account (even if the account is local) from the system that hosts the account; it deletes only the account privilege entries from its local database.

Programming Server-Side Applications for Microsoft Windows 2000
Programming Server-Side Applications for Microsoft Windows 2000 (Microsoft Programming)
ISBN: 0735607532
EAN: 2147483647
Year: 2000
Pages: 126

Similar book on Amazon

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net