-->

How to validate user's cached credentials agai

2020-07-13 10:46发布

问题:

When you logon to Windows, your credentials are cached. This allows you to use single sign-on. If you were to then browse to another computer, e.g.:

\\hydrogen

you would not be prompted for credentials.

Windows will take your:

  • current username
  • (hashed) password

and attempt to authenticate you automatically. The interesting thing is that this even works if your workstation is not on the domain. Windows will automatically use your username and password when connecting to the server. And if your:

  • local username/password matches a
  • domain username/password

you are automatically let in.

Pretty picture:

This is called Single Sign-On. You sign-on to Windows once, and your cached credentials are used to validate you as you use other things on the network.

Browsers also do this

Chrome, Internet Explorer, and Firefox also do a variation of this. If you need to login to a web-site, and the server supports Negotiation authorization, the server will send you back an indication that you should try the user's Windows/Domain/Kerberos credentials:

HTTP/1.1 401 Unauthorized
Server: Microsoft-IIS/7.5
WWW-Authenticate: Negotiate
Date: Thu, 09 Jul 2015 14:35:58 GMT
Content-Length: 0

Chrome will then take your cached credentials, and (after some intermediate magic) forward them to the web-server:

GET http://hr.woodglue.com HTTP/1.1
Host: hr.woodglue.com
Authorization: Negotiate YIIFzwYGKwYBBQUCoIIFwzCCBb....

Microsoft talks about this mechanism in Internet Explorer in the old article:

HTTP-Based Cross-Platform Authentication by Using the Negotiate Protocol

  • The client calls AcquireCredentialsHandle() and InitializeSecurityContext() with the SPN to build the Security Context that requests the session ticket from the TGS/KDC.

You validate against a domain, not servers

A final point i want to mention is that servers, web-servers, workstations, file servers, don't validate credentials against a server, they validate against a domain controller. You have a nebulous forest of many domain servers, and one of them handles your request.

In other words, you don't validate credentials against:

  • \\uranium (a domain controller on the woodglue.com domain)

you validate credentials against:

  • the woodglue.com domain

We have the important concept that someone can:

  • validate your cached credentials
  • against a domain
  • without having to enter a username or password

How can I do this?

How can I validate someone's cached credentials? How can I:

  • validate the user's cached credentials
  • against a domain
  • without the user having to enter a username or password (i.e. using the Windows cached credentials)

The important point is that is don't know (or care):

  • if the user's machine is joined to a domain
  • if the user's machine is joined to a workgroup
  • if the user's machine is joined to the woodglue.com or some other domain (e.g. superglue.com)
  • the names of the servers that power the superglue.com domain

I don't know how to do it.
I don't know what API technologies are involved.

I know there is an API called the Security Support Provider Interface (SSPI). This is what powers WWW-Authenticate: Negotiate (although i don't know if it is what powers SMB from a non-domain joined PC).

Chromium's open-source might be able to start us off with a snippet from their http_auth_sspi_win.cc. They use the SSPI function AcquireCredentialsHandle:

int AcquireDefaultCredentials(CredHandle* cred) 
{
   TimeStamp expiry;
   // Pass the username/password to get the credentials handle.
   // Note: Since the 5th argument is NULL, it uses the default
   // cached credentials for the logged in user, which can be used
   // for a single sign-on.
   SECURITY_STATUS status = library->AcquireCredentialsHandle(
         NULL,  // pszPrincipal
         const_cast<SEC_WCHAR*>(package),  // pszPackage
         SECPKG_CRED_OUTBOUND,  // fCredentialUse
         NULL,  // pvLogonID
         NULL,  // pAuthData
         NULL,  // pGetKeyFn (not used)
         NULL,  // pvGetKeyArgument (not used)
         cred,  // phCredential
         &expiry);  // ptsExpiry
}

Pass the username/password to get the credentials handle.

Note: Since the 5th argument is NULL, it uses the default cached credentials for the logged in user, which can be used for a single sign-on.

This "outbound" call to AcquireCredentialsHandle is followed up by a call to InitializeSecurityContext. The idea is that InitializeSecurityContext generates an opaque blob that represents the client.

You can then perform a parallel set of calls:

  • "inbound" call to AcquireCredentialsHandle
  • call AcceptSecurityContext, passing the blob returned earlier from InitializeSecurityContext

To steal rehost Daniel Doubrovkine's excellent image:

Note: In this case "client" and "server" are used to refer to context of producer and consumer. In my case the "client" and "server" are on the same machine.

But this line of "shows research effort" falls apart because i don't see anywhere in InitializeSecurityContext where i can specify woodglue.com as the domain to validate against.

I know that InitializeSecurityContext contacts a kerberos server, and obtains a "ticket" blob. That ticket blob is then passed to the "server" through AcceptSecurityContext. Sometimes the blob can be passed over a network; in my case it is passed around in memory on the same machine.

But i don't see how to specify the domain server that it should be contacting for that ticket.

Not to imply that SSPI is at all useful to solve my problem. It's just "research effort".

Older Research Effort

  • What TargetName to use when calling InitializeSecurityContext (Negotiate)?
  • How to validate domain credentials (from native code)?
  • Validate a user's password using the hash?
  • Win32: How to validate credentials against Active Directory?
  • How to perform Windows Authentication?

Of course, during all this, if the cached credentials are not valid on the specified domain, i would have to prompt the user for a username and password. But usernames and passwords are evil, the bane of computing, and i want to avoid them.

i'm using native code; not C#.