The Windows Security Model Is Ridiculous
At work I’ve been writing some code to do some basic interactions with the Windows security subsystem, such as testing if a user has the Log On As a Service privilege. Every time I venture in this area of Windows I cringe, because the security APIs are disjointed, incoherent, and vastly over-complicated. Presumably this is due to the evolution of the Windows security subsystem as it diverged from its VMS roots, but that’s no excuse.
The latest example pertains to the ‘Log on as a service’ privilege, called SeServiceLogonRight, which corresponds to a #define in the Windows SDK called SE_SERVICE_LOGON_NAME. I need to know if a given account has this privilege, because if not I need to grant it before the account can run a service. Easy, right?
You’d think so. There’s an API function, PrivilegeCheck, that will test if a given token has a set of privileges. The only problem is that PrivilegeCheck wants the privileges described as LUIDs, which are the binary identity of each privilege. All I know is the so-called “programmatic name”, that is, SeServiceLogonRight. No problem; there’s an API for that, called LookupPrivilegeValue, that returns the LUID for a privilege given its name.
So I wrote that up, but something’s wrong. LookupPrivilegeValue is failing with error ERROR_NO_SUCH_PRIVILEGE (that’s error number 1313). The text associated with that error code is A specified privilege does not exist. This is, of course, bullshit, since I got the priv name from the Windows headers.
After a bit of spelunking, though, I found this MSDN document.aspx) that notes certain privileges, including SeServiceLogonRight as well as other favorites like SeInteractiveLogonRight and SeNetworkLogonRight, are so-called “account rights”. What’s the difference between an “account right” and normal privileges? Well:
All of the LSA functions mentioned in the introduction above support both account rights and privileges. Unlike privileges, however, account rights are not supported by the LookupPrivilegeValue and LookupPrivilegeName functions.
Nice. So, how the hell do I test for this privilege–I mean–account right?
The LsaEnumerateAccountRights function enumerates the account rights held by a specified account.
Lovely. The reason that sucks is that I can pass a token from OpenProcessToken to PrivilegeCheck, but LsaEnumerateAccountRights wants a SID, plus I have to open a handle to the local security policy. Why the FUCK are account rights and privileges treated as equivalent if they have different semantics and APIs? And don’t get me started on the epic FAIL that is LSA_UNICODE_STRING!
December 4th, 2008 - 02:09
good lordy, i feel your pain as i have to do the exact same thing. need my installer to query log on as service before i setup a service with the credentials the user supplied. care to share the code you ended up with?
December 4th, 2008 - 02:32
I’m afraid I can’t share the code, but I can point you in the right direction:
Given a username, domain, and password:
* Call LogonUser to log the user on and get a token. Note you must have the Act As Part of the Operating System priv to do this. My code attempts both LOGON32_LOGON_SERVICE and LOGON32_LOGON_INTERACTIVE
* Call GetTokenInformation to get the SID of the user
* Call GetTokenInformation to get the token groups
* Use AllocateAndInitializeSid to construct the SID corresponding to the built-in administrators group (2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS)
* Iterate through the token group SIDs and use EqualSid to compare each one to the build-in admins group sid; if you find a match, the user is a local admin
Hope this helps…
August 12th, 2010 - 09:19
This is killing me today.
Almost threw the monitor out the window when I got “A specified privilege does not exist.” on privileges I just got back from LsaEnumerateAccountRights().