简体   繁体   中英

How to perform Windows Authentication?

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 :

在此输入图像描述

  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.

在此输入图像描述

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.

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, eg, 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM