Bits of Windows Authorization
From UIC
Bits Of Windows Authorization
Contents |
| Infos | |
|---|---|
| Author: | AndreaGeddon |
| Email: | andreageddon@gmail.com |
| Website: | w w w |
| Date: | 20/04/2008 (dd/mm/yyyy) |
| Level: |
|
| Language: | English |
| Comments: | Ah the good old ages, when i used to piss off Que in these comments... Don't worry... I'm here heheheh ;p NdQue |
Introduzione
Authorization is the process of controlling the access that subjects have on objects. In few words, authorization deals with controlling the access that users have on objects like files, processes, etc. Authorization, and more in general access control, is a fundamental topic in a system to ensure a proper level of security. We are now going to see how this access control is implemented in Windows and what level of security it does provide.
Essay
First of all we have to make a basic distinction between authentication and authorization: the former is the process of identifying a user by a security mechanism, typically a username / password couple, the latter is the process of granting or denying access to a resource for an authenticated user. E.g: a user can successfully identify and have access to a system, but he could be denied the access to system files in order not to tamper with the operating system integrity.
Overview on authentication
Windows authentication is performed through sessions: the session manager starts a new session for the user to be authenticated. The session manager process (smss.exe) will run Winlogon application (winlogon.exe), whose functionality is to recall the Local Security Authority Subsystem (lsass.exe) to perform the operations of authentication. Lsa is responsible for checking username and password against the security accounts manager (SAM) database (locally, or in pass-through mode, it is handled by a standard windows provided authentication package, the MSV1_0 [1]; remotely, in a distributed environment, it is handled via the Kerberos [2] / NTLM [3] / Digest protocol [4], see later), and determining whether a user is authenticated or not. Eventually Windows (XP and earlier) provides a layer between Winlogon and Lsass, named GINA [5] (Graphical Identification And Authorization), so that third party authentication packages can be developed and integrated in the system (customized interfaces and login procedures for smarcard readers, biometric devices, etc). In Windows Vista, however, this mechanism has been re-designed in what is now called CredSSP [6] (Credential Security Support Provider) to overcome some problems that were encountered in GINA, and to offer a more modular architecture easier to use and more reliable for the system itself.
Windows authentication of course also deals with single sign-on technology: once a user authenticates in a distributed Windows-based system, he must be granted access to various resources on the network (other computers, printers, etc) without having to provide his authentication credentials again to the remote machines. This is achieved through the use of distributed authentication protocols: the main one is Kerberos (Windows 2000 and later), but also NTLan Manager (NTLM) and Digest are supported (Windows NT and later).
All the discussed topics are very wide and complex, and they won't be described in detail in this document, which instead will focus on the process of authorization. See the reference in the bottom of the document for more information about above mentioned topics.
Authorization unveiled
Upon successful authentication a user is associated with an access token, which is a structure holding information about the id of the user (LUID, locally unique identifier) and various information such as groups it belongs to, its privileges and so on. This is part of the Windows implementation which is based on the discretionary access control model [7] (DAC). In this model the creator of the data is in charge to specify who else has the rights to access or modify the data (hence the name “discretionary”). Other models exist, like the mandatory access control [8] (MAC) and the role-based access control [9] (RBAC), they all have advantages and disadvantages and best scenarios of use. Windows only implements DAC natively, while for example MAC and RBAC can be found in other OSes implementation (in particular SELinux [10]). Why is this important? These models specify standards requirements of security that are described in detail in the Trusted Computer System Evaluation Criteria [11] (TCSEC, aka Orange Book), which is a document developed by the American department of defense in order to give a formal categorization of the security of the systems being evaluated. With DAC model, Windows meets the C2 level in the TCSEC specification (the D level is minimal security, like Windows 9x/Me, the C level is based on Discretionary protection, the B level is based on Mandatory protection, the A level is the highest and is based on formally verifiable design and implementation). Of course the access control model is not the only component being evaluated in the criteria, more information can be found in the reference at the bottom of this document.
As a parenthesis: MAC protects confidentiality by assigning security levels (aka labels) to data and users (subjects and objects), so tat users in less privileged levels can not access data in upper/classified security levels. Also, users with high security labels are not allowed to write data that could be accessible to users in lower levels. These enforcement rules are known as the Simple security property and the Star-property (aka no read up / no write down), and they are described in the formal Bell-Lapadula model [12] (again, developed by US Dod for the formal description of multi-level security). RBAC instead works by defining “roles” with associated access privileges: every user in the system is assigned one or more roles in order to be able to perform his tasks. A partial RBAC model is implemented in Windows (the Authorization Manager [13], introduced in Windows Server 2003; it is a quite complex architecture, see references for more information), as in many other systems, and the mandatory model is used (for Windows Vista) in the design of the integrity control (MIC, explained later).
Back to the Windows DAC, we said a user has an associated token specifying his privileges towards objects. What kind of securable objects are there?
- Files (on the filesystem) and Filemappings
- Processes and Threads
- Registry Keys
- Network shared objects (folders/files and printers)
- Pipes
- Events
- Mutexes
- Semaphores
- Waitable timers
- Jobs
- Serivces
- Access Tokens
- Directory Services (Active Directory)
every access to these objects is validated against the user access token in order to grant or deny its access. Users can query or modify these privileges via the authorization APIs, excepted for the Directory Service Objects, which are handled differently since they are part of the Active Directory, and therefore they require the use of the Active Directory Service Interface [14] (ADSI) to perform the query and modification operations. The basic structure of the architecture is easy:
+--------------+ +---------------------+
| Access Token | | Security Descriptor |
+--------------+ +---------------------+
| SID (user) | --------> | +-----+-----+ |
| SID (groups) | | | ACE | SID | |
| ... | | | ACE | SID | |
+--------------+ | | ... | ... | |
| | ACE | SID | |
| +-----+-----+ |
+---------------------+
a SID is a Security Identifier, which is a structure that uniquely identify a user or a group. An ACE is an Access Control Entry, that is an entry of a list (the ACL, Access Control List) associated to a securable object in order to specify who is granted and who is denied access to the resource. What happens when we are accessing a securable object? Basically, when we open a handle to a securable resrouce, a SD (security descriptor) is created. When we issue read or write operations on the resource, the object manager will check the ACL in the SD to see if there is any ACE that contains the SIDs present in the access token associated to the user that is issuing the request. According to what is found in the ACEs the request will proceed or fail. Every object can have discretionary ACL (DACL) used to control user permissions, or can have a system ACL (SACL) which is used for auditing (an ACE in the SACL will generate audit events that will be logged in the event log). All these components are simple data structures (SD, SID, ACE etc), but they are not supposed to be accessed directly. A program should retrieve or modify the information in the structures by using the appropriate APIs: the structures are opaque, not because of “security through obscurity” mentality, but to prevent the user from using the authorization components in an inappropriate way. The misuse of access tokens, SIDs and security descriptors would break the whole access control model, allowing protection to be bypassed easily. Of course, the structures available to the user are just copies, even if they are manually modified the access control model will not be tampered in any way. All the modifications must happen through the authorization APIs, which will communicate with the security reference monitor in the kernel in order to have the modification accepted or denied. The kernel-mode barrier will prevent any unwanted tampering. Let's see a small example to clarify all this nerdo-babble:
+---------------------+ +-----------------------+
| Bob's | | C:\bob.txt 's |
| Access Token | | Security Descriptor |
+---------------------+ +-----------------------+
| User SID: (Bob) | --------> | +---------+--------+ |
| Group SID: (Admins) | | | R\W | Bob | |
+---------------------+ | | R\W | Admins | |
| | ... | ... | |
| | R | Bruce | |
| +---------+--------+ |
+-----------------------+
when Bob opens the file C:\bob.txt, Windows checks the entries in the ACL in the SD for the file, in order to determine if access must be allowed or denied. Bobs access token contains Bob's SID and its group (administrators) SID. In the ACL we find, in the first entry, that Bob is allowed to read and write the document. Bob's read and write operations will therefore succeed. Similarly, Bruce's write operations won't (ACEs can be either of type “allow” or “deny”, in the example we are assuming all the ACEs to be of type “allow”).
Vista improvements
All the DAC model provides a deep control on every object in the system, if used correctly a system can achieve a very high security level. Still, some legacy problems have been vanifying this security model in Windows: simply, the fact that windows is mostly run by default with administrator privilege, giving full access to the system to every potentially malicious component being run. To overcome this problem without breaking retrocompatibility, in windows Vista a new security layer has been introduced: User Account Control [15] (UAC). How does it work? As just said, it is an additional layer situated on the top of the standard DAC model, named Mandatory Integrity Control model [16] (MIC). It is similar to the mandatory access control model cited some paragraphs above, but where MAC works protecting data confidentiality, MIC protects data integrity. MIC has integrity levels (as opposed to MAC, which has security levels) that specify the privilege of a subject to modify data protected by other levels. This is inspired to the Biba integrity model [17], characterized by the same Simple and Star security properties, but in this model they are reversed (as opposed to the Bell-Lapadula): infact they become the “no read down” and “no write up” rule, meaning that a subject at a low integrity level cannot write data protected by higher security levels; also, a subject at a high level cannot read data from a lower level, since data integrity from lower levels is not trusted, and could cause corruption to upper levels. Windows MIC is a bit different of course, since it has to deal with execution as well. In UAC there are four global integrity levels (low, medium, high, system), every token is enriched with the information about its integrity level (every level is represented by a SID, so the SID of the integrity level will be included in the access token), and whenever a subject accesses an object, UAC will check in the object ACLs to ensure that the integrity level SID in the object is less than or equal to the one stored in the subject's access token. This check happens before the ACL is checked for discretionary permissions, so even a subject running with administrator privilege may not be allowed to modify an object that is protected by a higher integrity level. The mandatory policy enforcement is based on both the user token and the file integrity level, so that if a user with high integrity level executes an untrusted executable, the new process will be started with a low integrity level, thus ensuring it can never corrupt high level integrity system components. This mechanism is very helpful for implementing process isolation and sandboxing: what Vista does, for example, is to assign to Internet Explorer process a low integrity level, so whatever file is downloaded, or saved in any way (via some exploit, badly crafted activex etc), will inherit the low level, limiting a lot the possibility of damage to the system that a malware or any dangerous component may achieve. MIC is only one part of Vista's security enhancements, the whole model has several other components to overcome problems like shattering / squatting [18], and of course the model is not free of problems, see the references for more information.
A closer look
Let's now see some code to practice what we have learned so far.
Who am I?
A code snippet that extracts the process SID from its token, and queries it to know the account name associated to the SID.
#define STRING_SIZE 0x0400
HANDLE TokenHandle;
DWORD DataSize;
VOID *DataBuffer = malloc(BUFFER_SIZE);
SID *Sid;
CHAR String[STRING_SIZE];
DWORD StringSize = STRING_SIZE;
CHAR String2[STRING_SIZE];
DWORD String2Size = STRING_SIZE;
// open a handle to the current process token
OpenProcessToken(
GetCurrentProcess(),
TOKEN_ALL_ACCESS,
&TokenHandle),
// determine data size needed for the token
GetTokenInformation(
TokenHandle,
TokenUser,
NULL,
NULL,
&DataSize);
// get the token data
GetTokenInformation(
TokenHandle,
TokenUser,
DataBuffer,
BUFFER_SIZE,
&DataSize);
// extract the SID from the process token
Sid = (SID*)((TOKEN_USER*)DataBuffer)->User.Sid;
// retrieve the name associated to the SID
LookupAccountSid(
NULL,
Sid,
String,
&StringSize,
String2,
&String2Size,
&NameUse);
First we open a handle to the token of the process. The token is a kernel object, so we get a opaque handle to it. Next, we need to determine the size of the data needed to store the token. Why? The token after all is a fixed size structure. As we can see passing the TokenUser value for TokenInformationClass parameter will result in the TOKEN_USER structure being filled in the buffer pointed by TokenInformation parameter. The structure is totally transparent
SID_AND_ATTRIBUTES User;
} TOKEN_USER,
typedef struct _SID_AND_ATTRIBUTES {
PSID Sid;
DWORD Attributes;
} SID_AND_ATTRIBUTES,
but the function will also return the SID associated to the user, and the Sid is of variable length, and since the GetTokenInformation api needs to fetch the data from the kernel, in order to minimize the number of I/O the kernel will provide all the needed data in just one buffer. A quick disassembly will show that GetTokenInformation is just a wrapper to the syscall NtQueryInformationToken, which in turn, in the kernel will resolve to RtlCopySidAndAttributesArray. We can easily verify after the next call to the GetTokenInformation, the DataBuffer content will be the following:
003251F8 01 05 00 00 00 00 00 05 ........
00325200 15 00 00 00 0B 75 D9 76 .....uÙv
00325208 8D EB EA 5E 43 17 0A 32 .ëê^C..2
00325210 EC 03 00 00
the first doubleword is a pointer to the Sid, which is stored right after the 8-bytes sized token structure. All is left to do is to get this Sid and pass it to the LookupAccountSid api to retrieve the username associated to the Sid. Also, the api will return the domain name where the user account is found. This api will rely on the LSA subsystem, which as we stated earlier is in charge of querying the SAM database for accounts information. This kind of communication must happen in a safe way with the lsass process, and must be able to traverse the network to locate the appropriate computer in the network / domain that contains the credentials for the user accounts; how can this be achieved? The answer is simple: RPC [19]! The LookupAccountSid api will resolve on NdrClientCall2, which is part of the RPC NDR engine (Remote Procedure Call Network Data Representation), the NDR is a marshalling engine for transporting RPC calls over network. Note: according to the msdn documentation, to determine the size of the data to retrieve for the GetTokenInformation api the TokenInformationClass should be set to zero. First, this generates a compiler error, since the NULL value is not specified in the TOKEN_INFORMATION_CLASS enumeration. Second, without passing the appropriate information class that needs to be queried, the system would be unable to determine the size of the buffer for the data to be returned. So, the second param must not be NULL, it must specify the required information class. What needs to be null is the pointer to the buffer for the returned data, and the size of such buffer, specified in the TokenInformationLength parameter.
Which group do I belong to?
We can play again with the token, and see how we can find information about the groups we are part of.
TokenHandle,
TokenGroups,
NULL,
NULL,
&DataSize);
GetTokenInformation(
TokenHandle,
TokenGroups,
DataBuffer,
BUFFER_SIZE,
&DataSize);
for(i = 0; i < ((TOKEN_GROUPS*)DataBuffer)->GroupCount; i++)
{
Sid = (SID*)((TOKEN_GROUPS*)DataBuffer)->Groups[i].Sid;
StringSize = String2Size = STRING_SIZE;
LookupAccountSid(NULL,
Sid,
String,
&StringSize,
String2,
&String2Size,
&NameUse);
}
the code is pretty much the same, except that the TokenGroups information class will cause the api to return an array of SID_AND_ATTRIBUTES structures, since a user can belong to more than one group. Similarly to the previous code snippet, all the Sids are stored right after the array of structures. The returned groups are for exeample Everyone, BUILTIN\Administrators, BUILTIN\Users, NT AUTHORITY\Interactive, and so on. The Sids are uniquely created by the authentication server, but some of them are well known. The binary data pointed by the PSID in the SID_AND_ATTRIBUTES is just a binary representation of these values (well, it's not straight forward, it has its own format, more precisely it follows the Security Descriptor Definition Language [20], or SDDL, see the references for more information). See the reference for a list with all the well known Sids [21].
What can I do?
Enough with the token, we can handle token interrogations, now its time to move to Security Sescriptors and ACLs. As a test, we can create the “C:\data.txt” file, and then deny write access for a user, in order to have both allow and deny ACLs associated to the file. Let's see how to retrieve this information.
#define ACCESS_READ 1
#define ACCESS_WRITE 2
#define ACCESS_EXECUTE 0
HANDLE DataFile, TokenHandle;
ACL *FileDAcl, *FileSAcl;
SECURITY_DESCRIPTOR *FileSd;
ACE_HEADER *Ace;
ACCESS_ALLOWED_ACE *AllowedAce;
ACCESS_DENIED_ACE *AccessDenied;
DWORD AccessToCheck, GrantedAccess;
GENERIC_MAPPING GenericMap;
PRIVILEGE_SET PrivilegeSet;
DWORD PrivilegeSetSize = sizeof(PRIVILEGE_SET);
BOOL AccessStatus;
SID *SidOwner, *SidGroup;
DataFile = CreateFile(
DATAFILE,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
GetSecurityInfo(
DataFile,
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION,
(void**)&SidOwner,
(void**)&SidGroup,
&FileDAcl,
NULL,
(void**)&FileSd);
//
// check if we have write access
//
ImpersonateSelf(
SecurityImpersonation);
OpenThreadToken(
GetCurrentThread(),
TOKEN_ALL_ACCESS,
TRUE,
&TokenHandle);
AccessToCheck = ACCESS_WRITE;
GenericMap.GenericRead = ACCESS_READ;
GenericMap.GenericWrite = ACCESS_WRITE;
GenericMap.GenericExecute = ACCESS_EXECUTE;
GenericMap.GenericAll = ACCESS_READ | ACCESS_WRITE | ACCESS_EXECUTE;
MapGenericMask(
&AccessToCheck,
&GenericMap);
AccessCheck(
FileSd,
TokenHandle,
AccessToCheck,
&GenericMap,
&PrivilegeSet,
&PrivilegeSetSize,
&GrantedAccess,
&AccessStatus);
RevertToSelf();
in this example we only inspect allowed and denied ACLs, and in the denied ACL handler we see how to check if we are granted write access (see later for the rest of the code). Our intent now is to check the thread's token against the file Security Descriptor, and check if we are granted or denied a specific access. First thing to do is to open a handle to the desired file, we can use a call to CreateFile, specifying the parameters we need. Having the file object, we can obtain its security descriptor by using the GetSecurityInfo API. The paramters are pretty straight forward: we pass the handle of the file, the object type, and the kind of information we are looking for. Now here we have to be careful: in our case we are only interested in the DACL information, and the API would work perfectly by using DACL_SECURITY_INFORMATION alone. Anyway, when using other APIs involving the Security Descriptor, in general every information is required to be present in it. In this case, the SD would be valid, but would not contain group and owner information, because we didn't request it, so other APIs may possibly fail when passing them the SD (later we will use AccessCheck, which is one of those picky APIs). So we now specify all parameters in order to get also Owner and Group information. We don't need to retrieve the SACL information, and actually if we pass the SACL_SECURITY_INFORMATION the function will fail, since there is no SACL associated to the object. We also specify parameters receiving pointers to the information we need, but these pointers do not point to external data, they will point inside the SD structure returned in the pointer passed in the last parameter of the function. Why do we need the pointers if we have the function? Because the SECURITY_DESCRIPTOR function is opaque, and the data size of the members inside have a variable size. This is the definition of a SD:
UCHAR Revision;
UCHAR Sbz1;
USHORT Control;
ULONG Owner;
ULONG Group;
ULONG Sacl;
ULONG Dacl;
} SECURITY_DESCRIPTOR,
*PSECURITY_DESCRIPTOR;
those ULONGs are not really doublewords, they are just placeholders for data of unspecified length. To be more precise the Owner and Group member are SIDs, and the Sacl and Dacl members are ACL structures, followed by their own SIDs (remember? we talked about this few paragraphs ago). In this way we have opened the file and we requested its security information, but actually there is a shortcut to avoid having to open the file. We could have used the following code:
GetFileSecurity(
DATAFILE,
DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION,
FileSd,
BUFFER_SIZE,
&DataSize);
The GetFileSecurity API opens the file passed as first parameter, and internally will resolve to a call to NtQuerySecurityObject, which is like GetSecurityInfo, but it only returns the SD, without returning the pointers relative to Owner, Group, Dacl and Sacl. In turn the NtQuerySecurityObject will just complete his job in the kernel through the primitive SeQuerySecurityAccessMask. This shortcut can be used when dealing with file system object, but we cannot use it for process objects for example. And how does GetSecurityInfo work? This API is routed through the NTMARTA interface (NT Multiple Access Routing Authority), which is a library that easily dispatches the requests for permissions checking on the specified objects. Each object has its own specific access rights and requires its own specific handling, so in NTMARTA we see the SE_OBJECT_TYPE type enumeration is used as an index into the array of functions that every action has for a specific object. So for example the “query rights” action is just an array of 12 APIs, so that SE_FILE_OBJECT-th element is the pointer to MartaGetFileRights routine, the SE_SERVICE-th element is the pointer to MartaGetServiceRights routine, and so on. Why then GetFileSecurity is not routed through NTMARTA, but goes straight into the kernel? Because in GetFileSecurity we only specify a filename, and we don't receive a open handle to the object. So the system only needs to perform the query in kernelmode, interfacing with the object manager, with no need to create a usermode-available handle to the object. In the GetSecurityInfo instead we pass a handle to the object, so it means a handle has been created, and the whole SD has been associated with it. In this case the system doesn't need to bother the kernel to query the data, since a shadow copy of it is already available in usermode. Anyway, we now have the open file and its related SD, we have all the ingredients we need, let's check the rights permissions to see if we have write access. First, we need the thread token; one method we can use is the AccessCheck API, which will do what we need. Actually, AccessCheck needs a client identified by an access token. So opening the handle to the thread token is not enough, we need to impersonate it as well; depending on what we need to do, we can perform the impersonation in several ways, in our case the ImpersonateSelf API fits perfectly. This API causes the system to open a handle to the current process token, duplicate it and set it as the impersonation token for the thread. As parameter we need to pass SecurityImpersonation or above, or the access check will fail. What is impersonation? And why we need it? Impersonation is a feature introduced in the early NT engine, its main purpose is to allow a service to impersonate a client, in order to access resources by using the same privilege that the client itself would have, rather than using the service's privilege (which in general is high); this was done both for security reasons and for ease of management. In terms of internal working the ImpersonateSelf simply resolves on ZwSetInformationThread with the ThreadInformationClass parameter set to ThreadImpersonationToken, where the passed token is just a duplicate of the process token. Now, after impersonating the client (remember to revert the impersonation when you are done) we can setup the access right we want to check (in our case ACCESS_WRITE), and we can call the AccessCheck API. We pass the file SD, the thread token that we impersonated, the kind of access we want to check (write), and other parameters, in particular we want to check the GrantedAccess parameter, which is a bool which will tell us if the access we requested is granted or not. In our case, this parameter will be false, since we have setup the file permissions in order to deny the write access for our user. If we use the same code, but we pass ACCESS_READ in the AccessToCheck parameter, the returned GrantedAccess bool will be true. The AccessCheck API is just a wrapper to the kernel primitive, which has a pretty complicated logic to implement the actual access check that is being required. BUT! We still are not happy. We use impersonation and a call to an API to check a simple access right, what else could we do? The authorization architecture is fairly wide and complex, so there are lots of ways to do the same things. In particular, when we get the SD for a file, we already have everything we might want: the pointer to the ACL, its ACEs and related SIDs. What we can do now is to do all the parsing job ourselves! The information about who is granted what access is already stored in the SD, we just need to read it. Let's see how.
for(i = 0; i < FileDAcl->AceCount; i++)
{
switch(Ace->AceType)
{
case ACCESS_ALLOWED_ACE_TYPE:
AllowedAce = (ACCESS_ALLOWED_ACE*)Ace;
Sid = (SID*)&(AllowedAce->SidStart);
StringSize = String2Size = STRING_SIZE;
LookupAccountSid(
NULL,
Sid,
String,
&StringSize,
String2,
&String2Size,
&NameUse);
AccessToCheck = AccessDenied->Mask;
break;
case ACCESS_DENIED_ACE_TYPE:
AccessDenied = (ACCESS_DENIED_ACE*)Ace;
Sid = (SID*)&(AccessDenied->SidStart);
StringSize = String2Size = STRING_SIZE;
LookupAccountSid(
NULL,
Sid,
String,
&StringSize,
String2,
&String2Size,
&NameUse);
AccessToCheck = AccessDenied->Mask;
break;
default:
...
break;
}
Ace = (ACE_HEADER*)(((BYTE*)Ace) + Ace->AceSize);
}
All we need to is to get a pointer to the first ACE, then inspect the whole array of all the ACEs and read them. In the example we only consider denied and allowed aces. Every ACE begins with a ACE_HEADER that specifies what kind of ACE is it, then the rest of the structure is depending on the type of the ACE itself (all the ACE types are listed on the msdn). Once we get our ACE, and we know what kind of ACE it is, the first question we want to know is: who is this ACE referring to? To answer this, we can simply extract the SID pointer from the ACE, and use the LookupAccountSid to know the name of user or group being affected (we have already seen this API when dealing with the tokens). In our case, for example, we inspect the ACEs for the file, the first one we find is a denied ace. The LookupAccountSid tells us that this ACE refers to our user. So we know that this ACE is denying something to our user. What is being denied? Let's just retrieve the Mask parameter from the ACE, and we have all the information we were looking for. This parameter contains an access mask specifying the access rights involved in this ACE. The access mask has a standard format:
31 30 29 28 27 26 25 24 23 ... 16 15 ... 0
+--+--+--+--+-----+--+--+---------+--------+
| | | | | | | | | |
+--+--+--+--+-----+--+--+---+-----+-+------+
| | | | | | | | +- Specific Rights
| | | | | | | +--------- Standard Rights
| | | | | | +--------------- SACL
| | | | | +------------------ Maximum Allowed
| | | | +---------------------- Reserved
| | | +--------------------------- GENERIC_ALL
| | +------------------------------ GENERIC_EXECUTE
| +--------------------------------- GENERIC_WRITE
+------------------------------------ GENERIC_READ
basically, what we have is a set of generic rights (GENERIC_READ, GENERIC_WRITE and GENERIC_EXECUTE), then we have the set of standard rights (DELETE, READ_CONTROL, WRITE_DAC, WRITE_OWNER, SYNCHRONIZE), and finally we have a set of specific access rights. The specific rights are specific to the type of object (file, process, pipe, etc) they refer to. In our example, after inspecting the denied ACE, the Mask parameter will contain the value 0x0116, so it only contains a set of specific rights, no generic or standard rights have been set. We would have expected to find something like GENERIC_WRITE, we will see in a minute why this is not being used. The access mask contains a combination of the following specific file rights:
b000000100 FILE_APPEND_DATA
b000010000 FILE_WRITE_EA
b100000000 FILE_WRITE_ATTRIBUTES
so, we easily realize this is a denial of write permission. Why don't use GENERIC_WRITE then? Let's see how the generic rights are defined:
FILE_READ_ATTRIBUTES
STANDARD_RIGHTS_EXECUTE
SYNCHRONIZE
GENERIC_READ:
FILE_READ_ATTRIBUTES
FILE_READ_DATA
FILE_READ_EA
STANDARD_RIGHTS_READ
SYNCHRONIZE
GENERIC_WRITE:
FILE_APPEND_DATA
FILE_WRITE_ATTRIBUTES
FILE_WRITE_DATA
FILE_WRITE_EA
STANDARD_RIGHTS_WRITE
SYNCHRONIZE
denying the GENERIC_WRITE attribute would result in denying all its constituent access rights, including SYNCHRONIZE, which unfortunately is also used by GENERIC_READ and GENERIC_EXECUTE, thus causing also read and execute access to fail. In this way instead, the ACL only denies the specific write access rights, causing only the GENERIC_WRITE access to fail, allowing instead a GENERIC_READ or a GENERIC_EXECUTE to succeed, so be careful when writing code to manage access rights on you own.
There are still lots of other things you can do with all the access control architecture, you can for example create your own ACEs and SDs, and set them for a given object, setup your personalized SACL for auditing, and so on, once you have understood the basics of access control you can study the msdn reference to see all the available APIs an see what you can implement.
Final words
The whole architecture for authorization and authentication is very wide and complex, it is very well designed and provides a fine capillarity for controlling every kind of access on every kind of component on the system. As a con, this system is very complicated and not very easy to use, causing the developing of applications that use access control to be difficult and prone to bugs. Of course there are several frameworks offering high level interfaces that will hide all the details about the internals of access controls, in order to ease the burden of developing a bug-free application. Also, the system itself has its own limitations, that's why during time the OS has been extended with the Authorization Manager in Windows Server 2003 and with Mandatory Integrity Control in Vista. The Authorization Manager architecture was implemented to overcome some limitations of the discretionary model, basically the lack of possibility to specify a high level semantic for access control, which instead is possible in a role-based model (and infact Authorization Manager is inspired on a RBAC model). MIC instead, as we have already seen, was added to extend access control by also adding integrity control.
Many applications for Windows do not correctly take in consideration access control when running, causing several problems in systems that have particular specifications for permissions. This can have all sort of side effects: bypassing security applications, fooling sandboxes, hiding data and much more, so it is very important to design and develop applications thinking at the application's needs in terms of access controls. If you want an example, use the regedit tool to remove all the permissions of a registry key, for example the autorun key. As a result, the values contained in that key become invisible in regedit. What is worse, the key values will result invisible to many applications like security tools or loggers, causing serious security concerns (you can call it the poor man rootkit). Of course a user could just reset the permissions to get rid of the problems, but the trick would work on average users / applications. This is just a stupid example of why it is important for applications to deal correctly with system permissions, especially in a system where long time inherited bad habits brought users to constantly run with administrator privilege. You can find a good paper with an interesting attack to access control implementation flaws in the references [22]. Looking at the other side of the mirror, uneducated users are most likely to not understand the security models, nor to get used to a multiuser environment, having passwords to remember and users to switch for privileged operations. So what's the deal? Having a hybrid solution is the direction where actually both Windows and Linux (Vista and Ubuntu) are moving when speaking of a desktop environment. By combining many security models together it is possible to achieve a good degree of security, keeping the OS simple for the user, avoiding him the burden of configuring an environment that he probably doesn't understand, and keeping it safe. Its a good step in a good direction, though still a lot of work needs to be done.
References
- MSV1_0 protocol:
http://msdn2.microsoft.com/en-us/library/aa378753(VS.85).aspx - MIT page about Kerberos protocol:
http://web.mit.edu/kerberos/ - Formal specification of NTLM protocol:
http://msdn2.microsoft.com/en-us/library/cc207842.aspx - Digest protocol rfc:
http://tools.ietf.org/html/rfc2617
http://tools.ietf.org/html/rfc2831 - Microsoft page about GINA:
http://tools.ietf.org/html/rfc2831 - CredSSP formal specification:
http://download.microsoft.com/download/ - A guide to understanding discretionary access control in trusted systems
http://www.fas.org/irp/nsa/rainbow/tg003.htm (from the rainbow series!) - Mandatory access control, from wikipedia
http://en.wikipedia.org/wiki/Mandatory_access_control - Nist source for role based access control
http://csrc.nist.gov/groups/SNS/rbac/ - Nsa source for SELinux:
http://www.nsa.gov/selinux/ - DOD TCSEC:
http://nsi.org/Library/Compsec/orangebo.txt - Bell Lapadula model
http://www.albany.edu/acc/courses/ia/classics/belllapadula1.pdf - Microsoft technet source for Authorization Manager:
http://www.albany.edu/acc/courses/ia/classics/belllapadula1.pdf - Microsoft source for ADSI
http://msdn2.microsoft.com/en-us/library/aa772170.aspx - Very good article from Russinovich on UAC architecture:
http://technet.microsoft.com/en-us/magazine/cc138019.aspx - Microsoft documentation for MIC:
http://msdn2.microsoft.com/en-us/library/bb625964.aspx - Biba model:
http://nob.cs.ucdavis.edu/classes/ecs235-2004-02/slides/2004-05-04.pdf - Microsoft report on shatter attack:
http://www.microsoft.com/technet/archive/security/news/htshat.mspx?mfr=true - Remote Procedure Call (RPC):
http://technet2.microsoft.com/windowsserver/ - Microsoft documentation for SDDL:
http://msdn2.microsoft.com/en-us/library/aa379567.aspx - List of well known SIDs from Microsoft:
http://support.microsoft.com/kb/243330/en-us - Good paper on flaws in the implementations of access control in Windows:
http://www.cs.princeton.edu/~sudhakar/papers/winval.pdf
Disclaimer
I documenti qui pubblicati sono da considerarsi pubblici e liberamente distribuibili, a patto che se ne citi la fonte di provenienza. Tutti i documenti presenti su queste pagine sono stati scritti esclusivamente a scopo di ricerca, nessuna di queste analisi è stata fatta per fini commerciali, o dietro alcun tipo di compenso. I documenti pubblicati presentano delle analisi puramente teoriche della struttura di un programma, in nessun caso il software è stato realmente disassemblato o modificato; ogni corrispondenza presente tra i documenti pubblicati e le istruzioni del software oggetto dell'analisi, è da ritenersi puramente casuale. Tutti i documenti vengono inviati in forma anonima ed automaticamente pubblicati, i diritti di tali opere appartengono esclusivamente al firmatario del documento (se presente), in nessun caso il gestore di questo sito, o del server su cui risiede, può essere ritenuto responsabile dei contenuti qui presenti, oltretutto il gestore del sito non è in grado di risalire all'identità del mittente dei documenti. Tutti i documenti ed i file di questo sito non presentano alcun tipo di garanzia, pertanto ne è sconsigliata a tutti la lettura o l'esecuzione, lo staff non si assume alcuna responsabilità per quanto riguarda l'uso improprio di tali documenti e/o file, è doveroso aggiungere che ogni riferimento a fatti cose o persone è da considerarsi PURAMENTE casuale. Tutti coloro che potrebbero ritenersi moralmente offesi dai contenuti di queste pagine, sono tenuti ad uscire immediatamente da questo sito.
Vogliamo inoltre ricordare che il Reverse Engineering è uno strumento tecnologico di grande potenza ed importanza, senza di esso non sarebbe possibile creare antivirus, scoprire funzioni malevoli e non dichiarate all'interno di un programma di pubblico utilizzo. Non sarebbe possibile scoprire, in assenza di un sistema sicuro per il controllo dell'integrità, se il "tal" programma è realmente quello che l'utente ha scelto di installare ed eseguire, né sarebbe possibile continuare lo sviluppo di quei programmi (o l'utilizzo di quelle periferiche) ritenuti obsoleti e non più supportati dalle fonti ufficiali.