【问题标题】:How to invoke SSPI's 'AcquireCredentialsHandle' with supplied credentials?如何使用提供的凭据调用 SSPI 的“AcquireCredentialsHandle”?
【发布时间】:2017-12-30 15:31:46
【问题描述】:

背景

Windows SSPI API 是 Windows 安全服务的接口,允许您相互验证客户端和服务器。 API 的主要用途之一是提供 Windows 集成身份验证,即单点登录 - 应用程序能够使用用户登录时的凭据自动对服务器进行身份验证。

这个过程的正常流程:

  • 用户通常使用用户名和密码登录到他们的计算机。
  • 用户运行一个应用程序,该应用程序使用 SSPI 对用户进行服务验证。
  • 该应用程序调用AcquireCredentialsHandle() 以获取用户登录时创建的现有凭据的句柄。
  • 应用程序使用AcquireCredentialsHandle() 返回的句柄通过InitializeSecurityContext() API 参与与服务器的SSPI 身份验证周期。
  • 应用程序和服务器使用InitializeSecurityContext()(客户端)和AcceptSecurityContext()(服务器端)API 交换不透明的字节数组(“令牌”),以验证彼此的令牌并继续身份验证周期。李>
  • 当来回传递足够多的令牌时,身份验证周期会在某个时间点完成。
  • 应用程序已代表用户向服务器验证了自己。身份验证完成。

这是使用 API 作为单点登录的一部分时此过程的正常流程。

AcquireCredentialsHandle() 的签名是:

SECURITY_STATUS SEC_Entry AcquireCredentialsHandle(
  _In_  SEC_CHAR       *pszPrincipal,
  _In_  SEC_CHAR       *pszPackage,
  _In_  ULONG          fCredentialUse,
  _In_  PLUID          pvLogonID,
  _In_  PVOID          pAuthData,
  _In_  SEC_GET_KEY_FN pGetKeyFn,
  _In_  PVOID          pvGetKeyArgument,
  _Out_ PCredHandle    phCredential,
  _Out_ PTimeStamp     ptsExpiry
);

在上述典型的 Windows 集成身份验证案例中使用时,通常不提供 pAuthData 参数 - 而是提供空引用。

问题

我希望能够以直接向其提供用户名和密码的方式使用AcquireCredentialsHandle()。它似乎可以处理这个问题,因为pAuthData 参数(上面为空)可以引用SEC_WINNT_AUTH_IDENTITY 结构,它允许您直接指定用户名和密码。

我试图以这种方式调用AcquireCredentialsHandle(),并为它提供一个填写完整的SEC_WINNT_AUTH_IDENTITY 结构以及我的用户名和密码。但是,每次调用它时,我都会返回成功,即使我使用虚构的用户名或密码。作为健全性检查,我尝试使用相同的凭据调用 LogonUser(),它们要么工作要么失败,正如预期的那样,这取决于我是否给了它一个有效的用户名/密码组合。

我做错了什么? 为什么AcquireCredentialsHandle() 总是返回成功,即使凭据完全不正确?

下面显示了我用来调用 AcquireCredentialsHandle() 的代码的基础知识:

public class SuppliedCredential : Credential
{
    public SuppliedCredential( string securityPackage, CredentialUse use, string username, string domain, string password ) :
        base( securityPackage )
    { 
        Init( use, username, domain, password );
    }

    private void Init( CredentialUse use, string username, string domain, string password )
    {
        // Copy off for the call, since this.SecurityPackage is a property.
        string packageName = this.SecurityPackage;

        TimeStamp rawExpiry = new TimeStamp();

        SecurityStatus status = SecurityStatus.InternalError;

        AuthIdentity auth = new AuthIdentity();

        auth.User = username;
        auth.UserLength = username.Length;
        auth.Domain = domain;
        auth.DomainLength = domain.Length;
        auth.Password = password;
        auth.PasswordLength = password.Length;
        auth.Flags = 0x2; // unicode

        this.Handle = new SafeCredentialHandle();

        RuntimeHelpers.PrepareConstrainedRegions();
        try { }
        finally
        {
            status = CredentialNativeMethods.AcquireCredentialsHandle_2(
               "",
               packageName,
               use,
               IntPtr.Zero,
               ref auth,
               IntPtr.Zero,
               IntPtr.Zero,
               ref this.Handle.rawHandle,
               ref rawExpiry
           );
        }

        if( status != SecurityStatus.OK )
        {
            throw new SSPIException( "Failed to call AcquireCredentialHandle", status );
        }

        this.Expiry = rawExpiry.ToDateTime();
    }
}

...

[StructLayout( LayoutKind.Sequential )]
public struct AuthIdentity
{
    [MarshalAs(UnmanagedType.LPWStr)]
    public string User;
    public int UserLength;
    [MarshalAs(UnmanagedType.LPWStr)]
    public string Domain;
    public int DomainLength;
    [MarshalAs(UnmanagedType.LPWStr)]
    public string Password;
    public int PasswordLength;
    public int Flags;
};

...

[ReliabilityContract( Consistency.WillNotCorruptState, Cer.MayFail )]
[DllImport( "Secur32.dll", EntryPoint = "AcquireCredentialsHandle", CharSet = CharSet.Unicode )]
internal static extern SecurityStatus AcquireCredentialsHandle_2(
    string principleName,
    string packageName,
    CredentialUse credentialUse,
    IntPtr loginId,
    ref AuthIdentity packageData,
    IntPtr getKeyFunc,
    IntPtr getKeyData,
    ref RawSspiHandle credentialHandle,
    ref TimeStamp expiry
);

【问题讨论】:

    标签: c# windows authentication winapi sspi


    【解决方案1】:

    总之

    AcquireCredentialsHandle() 即使凭证是伪造的也会返回 true;只有当客户端尝试调用InitializeSecurityContext() 时,API 才会验证用户名和密码。 AcquireCredentialsHandle() 只执行参数验证(指针值有效,结构正确填写,参数相互之间有意义等)。由于我正确提供了错误的参数,AcquireCredentialsHandle() 不在乎。

    ...

    长篇

    总而言之,客户端应用程序参与 SSPI 身份验证的正常周期如下:

    • 客户端调用某种形式的AcquireCredentialsHandle()
    • 客户端调用InitializeSecurityContext(),它返回一些令牌发送到服务器。
    • 服务器获取接收到的令牌并调用AcceptSecurityContext(),返回一些令牌发送回客户端。
    • 客户端收到令牌并调用InitializeSecurityContext()
    • ...循环一直持续到两端之间的身份验证周期完成为止。

    在上述情况下,通过使用提供的SEC_WINNT_AUTH_IDENTITY 结构(依次填写有效的用户名、域和密码)调用AcquireCredentialsHandle() 获得的凭据在客户端调用 @ 之前不会在客户端验证987654332@ 第一次,在它向服务器发送它的第一个令牌之前。

    2003 年 9 月 19 日,新闻组“microsoft.public.platformsdk.security”中的 Dave Christiansen(Microsoft 员工?)posted the following 回答了一个类似问题:

    您如何确定凭据是本地的凭据 用户? SSPI 有时以这种方式很棘手。你用的是什么包 (听起来像 NTLM、Kerberos 或 Negotiate,如果您正在使用 SEC_WINNT_AUTH_IDENTITY)?

    请注意,AcquireCredentialsHandle 将成功,即使 提供的凭据不正确(例如密码错误),因为它 在调用 InitializeSecurityContext 之前不会真正使用它们。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-31
      • 1970-01-01
      • 2019-10-29
      • 2013-04-12
      相关资源
      最近更新 更多