4

Here's the code where I'me trying to impersonate a user and then create a mutex. The mutex is not getting created. I get ERROR_ACCESS_DENIED error.

void Impersonate()
{
    DWORD logonType = LOGON32_LOGON_INTERACTIVE;
    DWORD logonProvider = LOGON32_PROVIDER_DEFAULT;
    HANDLE userToken;
    HANDLE hMutex;
    DWORD err;

    LPSTR user = "zoom"; // the user I created myself on my machine. 
    // It has Administrator privileges, and my account, 
    // from which I start the app, is Admin too
    LPSTR password = "zoom";
    LPSTR domain = ".";
    hMutex = NULL;

    LogonUserA(user, domain, password, logonType, logonProvider,&userToken);

    // just to make sure that mutexes are created fine before impersonation
    hMutex = CreateMutexA( NULL, FALSE, "mutex_good" );  

    ImpersonateLoggedOnUser(userToken);

    hMutex = CreateMutexA( NULL, FALSE, "mutex_797" ); // I can set any 
                                                       // random name, no difference
    if( hMutex == NULL )
    {
        err = GetLastError();
        // here err is ERROR_ACCESS_DENIED 
    }

    CloseHandle(userToken);
}

I've found several similar topics, but all of them were discussing creating same-name mutex from two different user contexts, i.e. a mutex "MUTEX_1" was already created before impersonation, and attempting to call CreateMutex with the same name but from an impersonated user was failing due to lack of privileges.

This is not the case here, as I am pretty sure that there is no mutex with the same name (or any mutex at all) being created before this code.

I guess I should pass something non-null into CreateMutex, but what exactly?

I'm not very good in Windows security. I understand passing NULL as a first parameter of CreateMutex means that 'default' security attributes will be used. In this case it will be security parameters associated to the thread, i.e. with impersonated user.

Am I correct with my assumptions?

K. A. Buhr
  • 45,621
  • 3
  • 45
  • 71
  • 1
    RbMm's answer explains the technical reasons why this doesn't work, and also provides a workaround (albeit an unsupported one). Since both accounts are admin, and assuming you are running the program with elevated privilege, a better workaround would be to specify `LOGON32_LOGON_BATCH` (instead of interactive) so the impersonation token has admin privilege. – Harry Johnston Dec 29 '16 at 02:57
  • 1
    BUT, better still would be to take a step back and ask why you are trying to create a mutex while impersonating in the first place. That's a rather odd thing to do; what are you trying to achieve? – Harry Johnston Dec 29 '16 at 02:58
  • @HarryJohnston - "albeit an unsupported one" - really we not need use `Restricted` subfolder. we can simply use `CreateMutexA(0, 0, "Global\\mutex_797");` with "Global\\" prefix only which is documented - https://msdn.microsoft.com/en-us/library/windows/desktop/aa382954(v=vs.85).aspx . i confuse `\BaseNamedObjects` with `\GLOBAL??` initially and forget that really `Everyone` **have write access** to `\BaseNamedObjects`. sorry – RbMm Dec 29 '16 at 15:14
  • @RbMm: huh. I thought you needed admin privilege to create any object in the global namespace; I wasn't aware that only applied to file-mapping objects. Thanks! I agree that creating a global mutex is at least as good a workaround as using admin privilege, but I'm still doubtful that it makes sense for the OP to create a mutex while impersonating. See also https://blogs.msdn.microsoft.com/oldnewthing/20110928-00/?p=9533 – Harry Johnston Dec 29 '16 at 19:59
  • @HarryJohnston - really `Everyone` can create objects in `\BaseNamedObjects` ( `Global` ), even LowIntegrity process can do this. this is unlike `\GLOBAL??` which is privileged place. so global namespace is not "privileged" space and almost any can create objects here (except `Anonymous`). think this fact not well known – RbMm Dec 29 '16 at 20:12
  • @HarryJohnston, well, I don't know exactly why our customer needs impersonation, but they call ImpersonateLoggedOnUser and then use our toolkit, which creates mutexes inside. The provided code is just a reduced snippet to illustrate the problem, but actually there are som layers between. Since I can't control the impersonation parameters, I prefer to change the mutex creation code to create it in \\Global than make changes to documentation and explain our customers why they should use batch logon instead of interactive. Thank you for your help! – Vladimir Navrotsky Dec 30 '16 at 12:39
  • 1
    Ah. Sounds like an unreasonable customer request to me, but of course it is your prerogative to cater to it anyway. :-) RbMm's suggestion of creating global mutexes is definitely your best bet, but make sure you've thought about the consequences if more than one person is logged in and running your code. You might also want to check out the blog post I mentioned above, I believe it mentions a number of other potential issues with impersonation you might want to watch out for. – Harry Johnston Dec 30 '16 at 21:56

1 Answers1

5

first of all you need understand NT Namespaces and use WinObj tool.

this is like small file system in memory with folders and different "files" in it (here under "files" mean different object types - Event, Mutant(mutex), Section, Device, ...). every time when you create named object - it placed to some folder in NT Namespaces. folders here (like folders in NTFS) have security descriptors. as result not everybody can create object under any folder.

to paraphrase what you do on the file system language (maybe it will be more clear):

i (John) try create file "mutex_good" under %USERPROFILE%\Documents and this is ok. because this is my personal folder and I have write access to it.

then i login(impersonate) as zoom and try create file "mutex_797" again under %USERPROFILE%\Documents (%USERPROFILE% in both case expand to same path, say c:\Users\John impersonation not affect this)

and zoom fail to create file. why ? simply he have no rights to do this. only John, Administartors, SYSTEM have write access to c:\Users\John but not zoom.

now let return to NT Namespaces. when we call CreateMutexA( NULL, FALSE, "mutex_797" ); where is "mutex_797" will be placed ?

if you not appcontainer and not run in system session 0 - you run in some user session <N> and your named objects will be placed in \Sessions\<N>\BaseNamedObjects directory, where N=1,2..

so call CreateMutexA( NULL, FALSE, "mutex_797" );

try create mutex at \Sessions\<N>\BaseNamedObjects\mutex_797

however in \Sessions\<N>\BaseNamedObjects exist next SymbolicLinks (this is like in NTFS file system):

Global -> \BaseNamedObjects
Local  -> \Sessions\<N>\BaseNamedObjects
Session -> \Sessions\BNOLINKS

so say if you call CreateMutexA( NULL, FALSE, "Global\\mutex_797" );

Object Manager try place your mutex under \BaseNamedObjects\mutex_797

for more info about this read Kernel object namespaces

and of course we must understand How AccessCheck Works

for directory objects defined the next access rights:

//
// Object Manager Directory Specific Access Rights.
//

#define DIRECTORY_QUERY                 (0x0001)
#define DIRECTORY_TRAVERSE              (0x0002)
#define DIRECTORY_CREATE_OBJECT         (0x0004)
#define DIRECTORY_CREATE_SUBDIRECTORY   (0x0008)

also can more read about this at DirectoryObject DesiredAccess Flags

we need DIRECTORY_CREATE_OBJECT access (Name-creation access to the directory object) for create mutex (or event or any object) in Directory

now for understand why you can create mutex in \Sessions\<N>\BaseNamedObjects but zoom can not - need look for Security Descriptor for this folder. i dump it:

T FL AcessMsK Sid
0 00 000F000F S-1-5-90-0-1 DWM-1
0 00 000F000F S-1-5-18 SYSTEM
0 0B 10000000 S-1-5-18 SYSTEM
0 0B 10000000 S-1-3-0 CREATOR OWNER
0 00 000F000F S-1-5-21-4026734978-3280735129-2412320105-1001 John
0 0B 10000000 S-1-5-5-0-294807 LogonSessionId_0_294807
0 00 0002000F S-1-5-5-0-294807 LogonSessionId_0_294807
0 00 0002000F S-1-5-32-544 Administrators
0 02 00000003 S-1-1-0 Everyone
0 00 00000002 S-1-5-12 RESTRICTED
17 00 00000001 S-1-16-4096 Low Mandatory Level

so who have DIRECTORY_CREATE_OBJECT (4) here ? DWM-1, SYSTEM, Administrators, current logon session users (LogonSessionId_0_294807 ), current user (John) - and all. zoom not have this access.

for example Everyone have (3) - DIRECTORY_QUERY|DIRECTORY_TRAVERSE - Name lookup and Query but not Name creation

you can ask where in this case I can create mutex after impersonation ? need use \BaseNamedObjects ( global namespace )OR \BaseNamedObjects\Restricted directory - I test it security descriptor and result :

for \BaseNamedObjects

T FL AcessMsK Sid
0 00 0002000F S-1-1-0 Everyone
0 00 00000002 S-1-5-12 RESTRICTED
0 00 000F000F S-1-5-90-0-0 
0 00 000F000F S-1-5-18 SYSTEM
17 00 00000001 S-1-16-4096 Low Mandatory Level

for \BaseNamedObjects\Restricted

T FL AcessMsK Sid
0 00 0002000F S-1-1-0 Everyone
0 00 0002000F S-1-5-12 RESTRICTED
0 00 000F000F S-1-5-90-0-0 
0 00 000F000F S-1-5-18 SYSTEM
17 00 00000001 S-1-16-4096 Low Mandatory Level

so how you can view Everyone here have 2000F - all needed access. hope zoom is member of Everyone ? next code I sure will be worked

CreateMutexA(0, 0, "Global\\mutex_797");

for \BaseNamedObjects (global namespace) exist one exception:

The creation of a file-mapping object in the global namespace, by using CreateFileMapping, from a session other than session zero is a privileged operation. Because of this, an application running in an arbitrary Remote Desktop Session Host (RD Session Host) server session must have SeCreateGlobalPrivilege enabled in order to create a file-mapping object in the global namespace successfully. The privilege check is limited to the creation of file-mapping objects, and does not apply to opening existing ones. For example, if a service or the system creates a file-mapping object, any process running in any session can access that file-mapping object provided that the user has the necessary access.

but for Mutex or say Event - not need SeCreateGlobalPrivilege enabled


you also can say, but zoom is Administrator account and Administrator have access to \Sessions\<N>\BaseNamedObjects - so why this not work ? because with LOGON32_LOGON_INTERACTIVE and UAC system assign to zoom filtered token. Administrator Group (S-1-5-32-544) exist in token but with SE_GROUP_USE_FOR_DENY_ONLY attribute only - as result it ignores access-allowed ACEs for the SID. and zoom have another LogonSessionId_0_XXX SID - as result and ERROR_ACCESS_DENIED

as noted @Harry Johnston - if we will use LOGON32_LOGON_BATCH instead LOGON32_LOGON_INTERACTIVE - we got elevated token - here Administrator Group will be with SE_GROUP_ENABLED attribute - is enabled for access checks for access-allowed ACEs

or how i offered - use Global\ prefix before name - for place object to \BaseNamedObjects where Everyone have full access

I understand passing NULL as a first parameter of CreateMutex means that 'default' security attributes will be used. In this case it will be security parameters associated to the thread, i.e. with impersonated user.

the first parameter - pointer to SECURITY_ATTRIBUTES let you overwrite default security descriptor for the new object. this is control who will be have access to it. but this not give you more or less access to Directory where you try place object - you must have DIRECTORY_CREATE_OBJECT access granted and we can not affect this by SECURITY_ATTRIBUTES - this affect on new object but not on existing Directory

and finally some visualization of NT Namespace enter image description here

RbMm
  • 31,280
  • 3
  • 35
  • 56
  • @engf-010 - sorry English not my native language and I not good in it. I try describe that simply not all tokens have access to default object directory - as result `zoom` is fail create mutex here. but he can create it at `"Global\Restricted\mutex_797` - here less strict SD – RbMm Dec 28 '16 at 22:09
  • :MUCH better. As a non-native english reader ,this I can understand. – engf-010 Dec 29 '16 at 02:27
  • That's fine, I'm not native speaker too, and your answer is clear and very helpful. – Vladimir Navrotsky Dec 29 '16 at 13:57
  • So, my misake was in assumption that named object ar created in independent namspace, and each user has his own namespace, something like \Users\John\BaseNamedObjects\mutex_good and \Users\zoom\BaseNamedObjects\mutex_797. Now I see this is not the case, and objects are created under "session" namespace, and I can not change the session by impersonation (or in any other way, since a whole process belongs to some session, and this session is owned by user who started the process) – Vladimir Navrotsky Dec 29 '16 at 14:04
  • @VladimirNavrotsky - yes, named objects localized by Sessions `ID`. for system `session 0` - `\BaseNamedObjects` folder used. for `session N` (where N > 0) - `\Sessions\N\BaseNamedObjects` used. however by using `Global` SymbolicLink you can "redirect" place to `\BaseNamedObjects` - `CreateMutexA(0, 0, "Global\\mutex_797");` - https://msdn.microsoft.com/en-us/library/windows/desktop/aa382954(v=vs.85).aspx – RbMm Dec 29 '16 at 14:29
  • 1
    @VladimirNavrotsky - I make update - add more info - really we can use and `CreateMutexA(0, 0, "Global\\mutex_797"); ` - without `Restricted` subfolder. I confuse this with `\GLOBAL??` initially – RbMm Dec 29 '16 at 15:08