首页 文章

如何执行Windows身份验证?

提问于
浏览
3

SQL Server,文件和打印机共享,Exchange以及许多其他应用程序都能够根据用户的Windows身份对用户进行身份验证 .

他们如何做到这一点?特别是,我该怎么做?

作为一个具体示例,请在以下方法中完成本机Windows代码:

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

我可以让我们开始:

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: 从这里开始的一切都是"shows research effort" . 你现在可以停止阅读了 . 我只有它来记录我自己的研究工作(其中一些在其他情况下非常有用) . 我还想要预先采取一些更常见,更不安全的方法(这是我陷入困境的陷阱) . 如果我可以帮助其他人避免相同的陷阱 - 那就更好了 .

背景

当用户连接到SQL Server时,他们可以选择使用集成身份验证:

集成安全性使用在操作系统线程上 Build 的当前Windows标识来访问SQL Server数据库

以及更多来自SQL Server:

当用户通过Windows用户帐户连接时,SQL Server使用操作系统中的Windows主体令牌验证帐户名和密码 . 这意味着Windows确认用户身份 . SQL Server不要求输入密码,也不执行身份验证 .

Result: 我无需输入用户名和密码即可登录SQL Server

如果我连接到远程网络共享,我自己的用户凭据用于验证我是远程服务器上的用户 . 我可以浏览到远程计算机,并且连接隐式使用我登录的用户帐户来验证访问权限 .

Result :我可以连接到网络共享,而无需输入用户名和密码 .

SQL Server如何验证我作为用户?
文件和打印机共享如何验证我作为用户?

让我说我正在编写自己的数据库引擎,我想支持 "Windows Authentication" 我该怎么做?

假设我的SQL数据库引擎在一个非域加入的PC上运行,并带有本地用户帐户 . 我 can 获取有关它们的各种信息

本地用户

让我们说我的SQL数据库引擎在一个域加入PC上运行,具有域用户帐户(并且为了使其有趣,用户来自与加入机器的域不同的域) . 我 can 获取有关用户的各种信息:

这里有足够的信息来正确实现Windows Authentication吗?

SQL Server是如何做到的?资源管理器是如何做到的? Internet Explorer和IIS如何做到这一点?

是不是有一些流行语,比如Ticket-Granting-Ticket我必须包含?

糟糕的想法

我有一些不好的,不安全的想法 . 我想为什么不只是从 GetUsernameEx 返回名称

CONTOSO\forest

并将其分为两部分:

Username: forest
Domain:   CONTOSO

那样我 know 用户确实是来自 CONTOSO 域的用户 forest . 我知道它确实是 contoso\forest 因为Windows在登录时验证了他们的凭据 .

除了没有 . 因为用户在他们的独立,非域加入,笔记本,可以将其工作组的名称从 HYDROGEN 更改为 CONTOSO . 现在当我读他们的用户名时:

CONTOSO\forest

我相信他们是:

CONTOSO域的林

实际上他们是:

独立机器的森林

好的,请使用SID

由于我无法信任各种Windows函数返回的"domain name",因此我可以使用用户的 SID

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

独立PC上的用户无法伪装,对吧?对? :(

是的他们可以:

当地用户氢\姜:S-1-5-21-1708537768-854245398-2146844275-3110

你看到我在哪里尝试 invent 一种执行身份验证的方法 - 并且失败了 . 与此同时,Windows和SQL Server团队在二十年前就已经解决了这个问题 . 戴夫卡特勒于1994年设计了这个系统,并且知道正是我应该做的 .

我只是不知道那是什么东西 .

用户SID不用于身份验证

在研究这个时,我发现了一些有趣的概念 . 例如域的SID是第一台机器的机器SID成为域的控制器 . 我还发现该域中的用户是域SID的后缀:

机器SID和域SID |计算机DEMOSYSTEM的机器SID | 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 |
在工作组系统上,本地帐户和组都是 . 使用本地帐户对远程系统进行身份验证需要远程系统已知的用户名和密码,并且不使用SID . 如果远程系统具有与呼叫者使用的用户名和密码相同的用户名和密码,则本地帐户发生任何类似单点登录的方式 . SID不会传输,也不会用于远程验证 .

这是一个重要的观点,您必须意识到重复的SID是 perfectly valid . SID在使用它们的权限范围内必须是唯一的 . 因此,虽然DEMOSYSTEM必须只有一个具有SID S-1-5-21-3419697060-3810377854-678604692-1000的本地帐户,但是另一台计算机是否使用相同的SID来引用其自己的本地帐户并不重要 .

这是有道理的,并强化了授权用户而不是他们的SID的想法.117166_ .

这就是为什么我的另一个想法很糟糕

SQL Server通过 syslogins 表中的SID存储Windows登录:

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

我的SQL数据库引擎无法读取当前用户的SID并检查它是否存在于我的 syslogins 表中,因为可能有多个用户使用相同的sid通过TCP端口1434连接到我的数据库引擎 .

Windows将凭据存储在某处

我正在阅读有关针对NTML和Kerberos的"Pass the Hash"攻击 . There was one interesting snippet

Windows将散列密码缓存在内存中以实现单点登录或SSO,这是Windows企业环境的基本功能 .

我只需要弄清楚如何说服Windows告诉我用户是否真的是 domain\BillG .

Chrome和Windows资源管理器中的Windows身份验证

Chrome可以透明地向我要求提供的Windows凭据提供 . 如果我要在服务器上请求,服务器将拒绝我访问(401)并指示我应该使用 Negotiate 身份验证:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Negotiate

我的客户端然后执行一些魔法,并重新发出请求,这次附加了我的身份:

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

IE和Chrome都能够转身并做一些事情来证明我就是我自己 . 如何记录Windows身份验证,但有一些提示 . 来自MSDN: HTTP-Based Cross-Platform Authentication via the Negotiate Protocol Part I - Network Infrastructure

enter image description here

  • 当登录用户从Web服务器请求资源时,它会发送初始HTTP GET谓词 .

  • 运行SPNEGO令牌处理程序代码的Web服务器需要身份验证并发出401 Access Denied,WWW-Authenticate:Negotiate响应 .

  • 客户端使用SPN调用 AcquireCredentialsHandle()InitializeSecurityContext() 以构建从TGS / KDC请求会话票证的安全上下文 .

  • TGS / KDC为客户端提供必要的Kerberos票证(假设客户端已获得授权)包装在SPNEGO令牌中 .

  • 客户端在授权:Negotiate base64(令牌)标头中重新发送HTTP GET请求协商SPNEGO令牌 .

  • Web服务器的SPNEGO令牌处理程序代码通过GSS API接受并处理令牌,对用户进行身份验证并使用请求的URL进行响应 .

enter image description here

因此,客户端可以使用一些代码来生成证明他们就是他们所说的人 . 这意味着在我的客户端上有一些方法可以生成证明域用户是他们所说的域用户的证据 .

当然,我不需要将Kerberos中的票据包装在加密的,基础64位的SPNEGO blob中 . 我只是需要正确的API调用,以正确的顺序,知道我是谁,我说我 .

1 回答

  • 1

    经过一些澄清后,这可以通过SSPI和GSS-API在Windows和Unix上轻松实现 .

    • 注册一个SPN用于目标主机上的服务

    客户视图:

    • 在您的应用/客户端中使用C / C和SSPI / GSS-API .

    • 获取当前用户的凭证句柄(出站/发起人) . GetUsername... 不是必需的 .

    • 为给定机制创建SSPI / GSS上下文 . Kerberos或SPNEGO .

    服务器视图:

    • 在目标主机/服务器上使用C / C和SSPI / GSS-API .

    • 获取SPN绑定到的计算机/服务帐户的凭据句柄(入站/接受者) . GetUsername... 不是必需的 .

    客户视图:

    • 让上下文生成不透明令牌并通过套接字将其发送到服务器

    服务器视图:

    • 接受该令牌并作出回应 . ...在循环中重复这一过程,直到确定了上下文 .

    • 查询经过身份验证的用户的上下文属性,例如michael-o@STACKOVERFLOW.COM

    • 配置上下文和凭证句柄

    客户视图:

    • 配置上下文和凭证句柄

    • 在经过身份验证的套接字上执行通信

    安全建议:不要依赖stoneage NTLM,尽可能使用Kerberos . 也总是使用UPN .

相关问题