3

SQL Server, File and Printer Sharing, Exchange, and a number of other applications are able to authenticate the user based on their Windows identity.

How do they do this? In particular, how can i do this?

As a concrete example, complete the native Windows code inside the following method:

Boolean IsCurrentUserValidForDomain(String domainName)
{
   //TODO: Ask Stackoverflow to fill in the code here
}

I can get us started off:

Boolean IsCurrentUserValidForDomain(String domainName)
{
    //Get the security token associated with the thread
    TOKEN userToken;

    // Get the calling thread's access token.
    if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, true, out userToken)
    {
       if (GetLastError != ERROR_NO_TOKEN)
          throw new Exception("Could not get current thread security token");

       // Retry against process token since no thread token exists.
       if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, out userToken)           
          throw new Exception("Could not get current process security token");
    }

    //We now have the security token of the running user (userToken)

    //From this, we can get the SID of the user
    PSID sidUser = null;

    DWORD cbBuf = 0;
    Boolean bsuccess = GetTokenInformation(hToken, TokenUser, null, 0, ref cbBuf);
    PTOKEN_USER ptiUser = null;
    while ((!bSuccess) && (GetLastError() = ERROR_INSUFFICIENT_BUFFER))
    {
       ReallocMem(ref ptiUser, cbBuf);
       bSuccess = GetTokenInformation(hToken, TokenUser, ptiUser, cbBuf, ref cbBuf);
    }
    sidUser = ptiUser.User.Sid;

    //Now that we have the user's SID, we can get the SID of their domain
    PSID sidDomain = null;
    GetWindowsAccountDomainSid(sidUser, null, ref cbBuff);
    ReallocMem(sidDomain, cbBuff);
    GetWindowsAccountDomainSid(sidUser, sidDomain, ref cbBuff);

    //We now have
    //TOKEN userToken: security token of the running user
    //PSID sidUser (S-1-5-21-2154378322-3929449213-1104335884-1006)
    //PSID sidDomain (S-1-5-21-2154378322-3929449213-1104335884)

    //TODO: ask stackoverflow if anything i've computed so far can help 
    //answer the question

    //TODO: Ask Stackoverflow to fill in the code here
}


    

Note: everything from here down is "shows research effort". You can stop reading now. I only have it to document my own research effort (some of which can be very useful in other situations). I also want to preëmpt some of the more common, and insecure, approaches (which is a trap i fell into myself). If i can help anyone else avoid the same trap - all the better.

Background

When a user connects to SQL Server, they have an option of using Integrated Authentication:

Integrated security uses the current Windows identity established on the operating system thread to access the SQL Server database

and more from SQL Server:

When a user connects through a Windows user account, SQL Server validates the account name and password using the Windows principal token in the operating system. This means that the user identity is confirmed by Windows. SQL Server does not ask for the password, and does not perform the identity validation.

Result: I can login to SQL Server without having to type a username and password

And if i connect to a remote network share, my own user credentials are used to validate that i am a user on the remote server. I can browse to the remote machine and the connection implicitly used my logged in user account to validate access.

Result: I can connect to a network share without having to type a username and password.

How does SQL Server validate me as a user?
How does File and Printer Sharing validate me as a user?

Lets say i am writing my own database engine, and i want to support "Windows Authentication" how would i do it?

Lets say my SQL database engine is running on a non-domain joined PC, with a local user account. I can get various pieces of information about them

Local user

Lets say my SQL database engine is running on a domain joined PC, with a domain user account (and to make it interesting, the user is from a different domain than the machine-joined domain). I can get various pieces of information about the user:

Is there enough information in here to correctly implement Windows Authentication?

How does SQL Server do it? How does Explorer do it? How does Internet Explorer along with IIS do it?

Aren't there some buzzwords, like Ticket-Granting-Ticket i have to include?

Bad Ideas

I had some bad, insecure, ideas. I figured why not just take name returned from GetUsernameEx

CONTOSO\forest

and split it into two parts:

Username: forest
Domain:   CONTOSO

That way i know that the user really is the user forest from the CONTOSO domain. I know it really is contoso\forest because Windows validated their credentials at login.

Except no. Because the user on their standalone, non-domain joined, notebook, can change the name of their workgroup from HYDROGEN to CONTOSO. Now when i read their username:

CONTOSO\forest

I will believe they are:

forest of the CONTOSO domain

when in reality they are:

forest of a standalone machine

Ok, so use the SID

Since i cannot trust the "domain name" returned by various Windows functions, then i could use the user's SID:

domain user contoso\forest: S-1-5-21-1708537768-854245398-2146844275-3110

There's no way a user on a standalone PC can fake that, right? Right? :(

Yes, they can:

local user hydrogen\ginger: S-1-5-21-1708537768-854245398-2146844275-3110

You see where i'm going with this? I'm trying to invent a way to perform authentication - and failing. Meanwhile Windows and SQL Server teams both already solved this problem twenty years ago. Dave Cutler designed this system in 1994, and knew exactly what i should be doing.

I just don't know what that something is.

User SIDs are not used for authentication

In researching this, i discovered some interesting concepts. Such as the SID of a domain is the machine SID of the first machine to become the domain's controller. I also discovered that users in that domain are a suffix of the domain SID:

Machine SIDs and Domain SIDs

| Machine SID for computer DEMOSYSTEM | S-1-5-21-3419697060-3810377854-678604692      |
| DEMOSYSTEM\Administrator            | S-1-5-21-3419697060-3810377854-678604692-500  |
| DEMOSYSTEM\Guest                    | S-1-5-21-3419697060-3810377854-678604692-501  |
| DEMOSYSTEM\CustomAccount1           | S-1-5-21-3419697060-3810377854-678604692-1000 |
| DEMOSYSTEM\CustomAccount2           | S-1-5-21-3419697060-3810377854-678604692-1001 |

On a workgroup system, local accounts and groups are all there are. Authentication to a remote system using a local account requires a user name and password known to the remote system, and that SIDs are not used. The only way anything resembling single sign on happens with local accounts is that if the remote system has the same user name and password that the caller is using. SIDs are not transmitted and are not used for remote authentication.

This is an important point, and you have to realize that duplicated SIDs are perfectly valid. SIDs must be unique within the authority in which they are used. So while DEMOSYSTEM must have only one local account with the SID S-1-5-21-3419697060-3810377854-678604692-1000, it doesn’t matter if another computer uses the same SID to refer to a local account of its own.

This makes sense, and reinforces the idea that it is credentials that authorize a user, not their SID.

Which is why another of my ideas sucks

SQL Server stored Windows logins by their SID in syslogins table:

sid                                                         name            isntuser
----------------------------------------------------------  --------------  ---------
0x010500000000000515000000A837D66516C0EA32733EF67F260C0000  CONTOSO\forest  1

My SQL Database engine cannot read the SID of the current user and check if it exists in my syslogins table, because there can be more than one user with the same sid connecting to my database engine over TCP port 1434.

Windows stores credentials somewhere

I was reading about "Pass the Hash" attacks against NTML and Kerberos. There was one interesting snippet:

Windows caches the hashed passwords in memory to implement Single Sign On or SSO, which is an essential feature of Windows enterprise environments.

I just have to figure out how to convince Windows to tell me if the user really is domain\BillG.

Windows Authentication in Chrome and Windows Explorer

Chrome is able to transparently supply my Windows credentails to someone asking for them. If i were to request on a server, the server will deny me access (401) and indicate that i should use Negotiate authentication:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Negotiate

My client then performs some magic, and re-issues the request, this time with my identity attached:

GET http://contoso.com/foo HTTP/1.1
Authorization: Negotiate YIIFzwYGKwYBBQUCoIIFwzCCBb+gMDAuBgkqhkiC9xIBAgIGCSqGSIb3EgECAgYKKwYBBAGCNwICHgYKKwYBBAGCNwICCqKCBYkEggWFYIIFgQYJKoZIhvcSAQICAQBuggVwMIIFbKADAgEFoQMCAQ6iBwMFACAAAACjggP4YYID9DCCA/CgAwIBBaEOGwxBVkFUT1BJQS5DT02iJTAjoAMCAQKhHDAaGwRIVFRQGxJ2YWRlci5hdmF0b3BpYS5jb22jggOwMIIDrKADAgEXoQMCATCiggOeBIIDmjMA0SnAUdqmbf8+UXZHsipRqPKt2yxqQaFia8hBF3TuQVDBgqGk8yL+CoDGnvkyGqpZK3UBsS/EuXP4Z+/0y49ZyDDQnDFqcJpF5ZY87t+u/kYQy+dr42GxEYQIjb096AQzDZio0dRWqbHleS5DlR7wCEaJ+a0CG6/vLEXL6tT20aj3avFibZc++5OKhynoxtyh10tJO3iwun2usJT+p1IfTD9yVDhfplMchLBgyp803+6IUwzm0zcwcqt7R1KnCv1i+baw3e/dhkIJz8cnoh1oNuivSXf4zOqlvp8FDlQMQEGqa9OA7LBmhg1rWDTOdyB4E9oZtVG8ipHyFYzDcyvIpWOMf9S68TTE78TgEhWjVq7g6BoH+O6IW14QIItxVk1GbSd2Ke9n9We0pbMjRxiZIMqyOvvFBgU5NlUUksdlG/yv0BTai7SILbVfNPsVwHeus//UfKQenX6YEnKUVi+XutY0kjLyp6l1L3Ce/ovkpDVmmYFebfdIT8Xbya9Zksa2nF8+7OL5S7I0tZaZUBL2Bzca9VJiGioRFvpgBXxKiChv71SukROreic+ylxHOfOWwXsEa0+ISHV6Uvhd44y3UA2VKtI3xoF8+3SZ184hIZ4fbahkfrBa1Zu5FqQ9M0rxAPgmsBZ2PwuMDWWLtraK7gJsAh+DxXGAaSTiPWaRhms59mfetBmzSnkzWBCr63G8rL71TiDgevoxhv0FP5s1JmWzWsnluJ95f9fphItuiDRI0C1358LMai9B1ZFWf9CRooeMAH4YUuL4SZ0r61/zQVnWFF1ngyt/ko/9UQ3mErLFeA/9Oq6BYfI/ExhVl9VVue0irM1vk09pIdUMS9MvQdW7YCg/C9LtOiJVpYw/aEVakn74l7TM71bIfjucDddDCBNuup41bWy5Nqkci8AHEMyoVyG9BxHmTm8NZ3FSujl+MeDAANKSt3a6P2k0C/W4Mley76ZoAGf6IYXf/9THQucvQGkasUkIN6PwIZIaxEdVt1BXiVXu1ADgt2/+0UB8rzYq+kt53R16rjev4Exvt7jpHIWUxjbDTxo2CvW0+Eh+mFyMj3CS2xQlhjrU2Q9ADQqA8wf8H88Dzp4PPWPxJnB4tC+Ecd9ZYlQwal00UX6aN47+dKPYDCp4piq6dvr2BhpzpsXxyR8QOZRKqAoXXLmb4Y1eGFWiUqH56J3Wju5h+cyzhMq+otpI4s77lfIecM41HccPrTKkggFZMIIBVaADAgEXooIBTASCAUigcKId1qR+UzSz8R00q+0o2M4+2dLnNW2vPU+uLeG9SqLJgJWsgBWUGtt6TRvPLF/GoHxP+sqST8fKJf0EHfycGfH/VJR6bnfpQYCWCgWRHjfdUpll51G/xKYqJYyy5xtNQvtKkzp+IB6CVKe1q3wopAY+uDsUk9XUvaIbUtHDEcWDATwi8BKGggVunw/idxKaZjaRmRko/Nsj5p38fiBk+OCN3yKDNSFCTDn+HUiCoCbDsv03zt2EO1eTJUPxXNhqJUjZMKYodgcsLMzNhSiyySH+kvgQZci3b8LGY1sCHMXopaL0Ysu4QgPD8UDD7dIBZ0ORmGf9srdZMgKjLIoEhXOmg+y5kqJpoPAwQaooHDizKQ8bmhFX2pOp7NjXoJ/wRvTB98seUNlDXDl5ySrt7P3Xf1Ybj7PpgMuqJykou2lKxirVhYYJ

Both IE, and Chrome, are able to turn around and do something can be used to prove i am who i am. How Windows Authentication is not documented, but there are some hints. From MSDN: HTTP-Based Cross-Platform Authentication via the Negotiate Protocol Part I - Network Infrastructure:

enter image description here

  1. When the logged-on user requests a resource from the web server, it sends the initial HTTP GET verb.
  2. The web server, running the SPNEGO Token Handler code, requires authentication and issues a 401 Access Denied, WWW-Authenticate: Negotiate response.
  3. The client calls AcquireCredentialsHandle() and InitializeSecurityContext() with the SPN to build the Security Context that requests the session ticket from the TGS/KDC.
  4. The TGS/KDC supplies the client with the necessary Kerberos Ticket (assuming the client is authorized) wrapped in a SPNEGO Token.
  5. The client re-sends the HTTP GET request + the Negotiate SPNEGO Token in an Authorization: Negotiate base64(token) header.
  6. The web server's SPNEGO Token Handler code accepts and processes the token through GSS API, authenticates the user and responds with the requested URL.

enter image description here

So there is some code that the client can use to generate a proof that they are who they say they are. Which means there is some way for me, on the client, to generate proof that domain user is the domain user that they say they are.

I, of course, don't need to wrap up the ticket from Kerberos in a encrypted, base-64'd, SPNEGO blob. I just need the correct API calls, in the right order, to know that i am who i say i am.

Community
  • 1
  • 1
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
  • 1
    It is not really clear what you are asking. I could answer your with a thosand which would not help either. Please cut down your question to the problem you are trying to solve. – Michael-O Jun 17 '15 at 19:34
  • 2
    I don't know, it seems pretty clear. He's asking what the best way to authenticate using Windows credentials is. – Feign Jun 17 '15 at 19:57
  • I must respectfully concur with @Michael-O. I've written two lengthy answers and dumped both because I realized I wasn't answering the question, then I realized I'm not really sure what's being asked. You aren't going to "invent" Windows Authentication. Your app will never have authoritative credentials to positively state that anyone is anybody. All you can do is leverage the authentication and security protocols already present; and that's abstracted fairly generally under things like SSPI... – David W Jun 17 '15 at 20:13
  • 4
    I agree with @Feign, the question seems clear enough. SQL Server lets you authenticate with your Windows credentials (see http://i.imgur.com/pHiVsQ5.png) - the question is simply "how?". – Jonathan Potter Jun 17 '15 at 20:24
  • @Michael-O If it will help clear up your confusion, stop reading everything after the word **Background**. The question is in the title, and in the first paragraph. The remainder is *"shows research effort"* and also attempts to pre-empt anyone who would suggest the (invalid) approaches i already tried. I could delete everything after the word **Background**, but then i think the question would be less clear. – Ian Boyd Jun 17 '15 at 22:39
  • @DavidW [I've done work with the SSPI](http://stackoverflow.com/questions/9788318/what-targetname-to-use-when-calling-initializesecuritycontext-negotiate) before. But it was always done to validate a username and password provided by the user. (which is the antithesis of *Windows Authentication*). Which SSPI functions do i call, in what order, to perform *authentication* without a username and password (as products such as the [Microsoft ODBC driver for Linux](https://technet.microsoft.com/en-us/library/hh568450(v=sql.110).aspx) manage to do? – Ian Boyd Jun 17 '15 at 22:45
  • 1
    The stuff you've provided as background seems rather confused; to take it from the top after "Bad Ideas", if you can use the GetUsernameEx function, then the user is already authenticated, and you really can trust the domain and username, *provided you trust the machine your code is running on*. (And if you *don't* trust the machine your code is running on, you've already lost.) I'm unclear on whether you're talking about code that runs on a server, or code that runs on the user's local machine. – Harry Johnston Jun 18 '15 at 00:56
  • 1
    Assuming the code runs on a trusted server, then the simplest approach is to use a communications mechanism that already includes authentication, such as named pipes or authenticated web services. Is that acceptable? (If you really can call GetUsernameEx and see the client's username rather than a service account or something, you're already doing this.) – Harry Johnston Jun 18 '15 at 01:00
  • @HarryJohnston If i create a local user called `hjohnston` it doesn't mean i'm actually `HarryJohnston`, it just means i have that same username. – Ian Boyd Jul 07 '15 at 15:27
  • @IanBoyd: if you're running on a trusted server, only the server's administrators can create a local account. And the local accounts on the client can't be used to authenticate to the server. – Harry Johnston Jul 07 '15 at 22:38
  • @HarryJohnston I'm running on a client – Ian Boyd Jul 08 '15 at 17:10
  • ... and according to your question, a client that isn't joined to a domain. In that situation, most software (including all the applications you mention as examples in the question) simply trusts the account database on the local machine. (One reason why it usually doesn't make sense to distrust the local account database is that anyone with the access necessary to create a new account also has the access necessary to modify your software to remove whatever identity checking you might try to use.) – Harry Johnston Jul 08 '15 at 20:57
  • On the other hand, if the local machine *is* joined to a domain, and access is tied to a domain account, then the software trusts that the machine is joined to the correct domain and that the authentication/authorization processes haven't been subverted. It is certainly possible for the local administrator to trick SQL Server, for example, into thinking a local account is a particular domain account; but what would be the point? You're the administrator, so SQL Server will do whatever you ask it to, including giving you access to the domain user's database. – Harry Johnston Jul 08 '15 at 21:15
  • Re your new edit to the question: if you have a web server you trust to verify the user's identity, the information you've already found should be pretty much all you need. You could look at the source code for Chromium to find out exactly how they do it. You should connect via SSL and verify that the server is using the expected certificate. If you don't want to use a full-blown web server, it would be easy enough to build a pipe-based service. (It still doesn't really make sense IMO to go to all this trouble, since the security benefit is marginal. But whatever.) – Harry Johnston Jul 09 '15 at 23:32
  • @HarryJohnston I don't have a web-server, nor am i using SSL. I'm just noting that Internet Explorer and Chrome manage to get identify the user without prompting for a username or password. The trick is to figure out which SSPI functions they are calling, in what order, with what parameters, and which options. And from that see if i can identify the locally logged in domain user. It's strange, but the SSPI API require a client->server->client->server->client conversation to authenticate. While HTTP Auth is able to do it in one trip ("here's the opaque blob") – Ian Boyd Jul 10 '15 at 02:59
  • But if you don't have an external server to query, what's the point of using SSPI? SSPI allows you to construct a token at the server end representing the user who is logged in at the client end - but if the client end and the server end are part of the same process, all you're getting is a copy of your own token. You've already *got* your own token. If you use SSPI to get a username, it will *always* be the same username GetUsernameEx would give you, and similarly for the SID. – Harry Johnston Jul 10 '15 at 04:31
  • To put it another way, if the operating system is lying to you about the identity of the user, it will *keep* lying to you *no matter what you do*. You can't trick it into telling the truth by doing things the long way around or by asking it the same question over and over. Heck, it doesn't even know what the truth is - it trusts the information that is fed into it. You can build your own password system into the application itself, or you can query a remote system, or you can trust the operating system - those are the only options. What you're trying to do is logically impossible. – Harry Johnston Jul 10 '15 at 04:38
  • ... of course, the remote system doesn't have to be a web server. In principle, it could be one of the domain controllers, and you could use a built-in service such as file sharing or LDAP. The tricky part would be validating that it really is a domain controller for the domain that you want to authenticate against - I'm not sure if there's any sensible way to do that for any of the built-in services. Perhaps ldaps, but I'm not sure that's turned on by default. – Harry Johnston Jul 10 '15 at 04:47
  • 1
    Did you see this article? https://msdn.microsoft.com/en-us/library/ms973911.aspx It seems to explain both protocols and Windows APIs in detail. They even have a .NET library. – usr Jul 18 '15 at 08:58

1 Answers1

1

After some clarifications, this can be achieved quite easily on Windows and Unix with SSPI and GSS-API.

  1. Register a SPN for the service on the target host

Client view:

  1. Use C/C++ with SSPI/GSS-API in your app/client.
  2. Acquire a credentials handle (outbound/initiator) for the current user. GetUsername... is not ncessary.
  3. Create a SSPI/GSS context for a given mechanism. Kerberos or SPNEGO.

Server view:

  1. Use C/C++ with SSPI/GSS-API on your target host/server.
  2. Acquire a credentials handle (inbound/acceptor) for the machine/servce account to which the SPN is bound. GetUsername... is not ncessary.

Client view:

  1. Let the context generate an opaque token and send that to the server via socket

Server view:

  1. Accept that token and respond. ... repeat that in a loop until the context has been established.
  2. Query context attributes for the authenticated user, e.g., michael-o@STACKOVERFLOW.COM
  3. Dispose context and credential handle

Client view:

  1. Dispose context and credential handle
  2. Perform communication on the authenticated socket

Security advice: do not rely on stoneage NTLM, use Kerberos wherever possible. Also use always UPNs.

Michael-O
  • 18,123
  • 6
  • 55
  • 121