使用SSPI进行Kerberos模拟:没有错误,但没有工作

时间:2018-01-26 10:58:05

标签: java kerberos jna impersonation sspi

我需要在Java应用程序服务器中模拟用户,并使用该用户的权限对IIS中的ASP应用程序执行http请求。我正在尝试为此目的调整Apache HttpClient的WindowsNegotiateScheme类。这使用JNA直接访问Windows SSPI身份验证功能,因此问题可能不是特定于Java。由于我需要“协议转换”,我没有用户的密码来模拟可用,只是名称。但是使用S4U功能,这应该是可能的。

所有SSPI函数调用返回0(成功)或590610(SEC_I_CONTINUE_NEEDED),从不出现错误代码,代码运行,但http请求到达ASP端,用户运行应用程序服务器,无论我是否在我的工作站上以我的用户ID在本地运行它,或者在应用程序服务器(恰好是运行IIS / ASP应用程序的机器)上运行它,它作为本地系统运行。

代码的相关部分如下:

public Header authenticate(Credentials credentials, HttpRequest request, HttpContext context) throws AuthenticationException {
    final String response;
    if (clientCred == null) { // first time
        boolean impersonate = ...; // logic not relevant here

        try {
            final String username = impersonate ? credentials.getUserPrincipal().getName() : CurrentWindowsCredentials.getCurrentUsername();
            final Sspi.TimeStamp lifetime = new Sspi.TimeStamp();
            this.clientCred = new Sspi.CredHandle();
            int credUse = impersonate ? (Sspi.SECPKG_CRED_OUTBOUND | Sspi.SECPKG_CRED_INBOUND) : Sspi.SECPKG_CRED_OUTBOUND;

            int rc = Secur32.INSTANCE.AcquireCredentialsHandle(username,
                    scheme, credUse, null, null, null, null,
                    clientCred, lifetime);
            if (WinError.SEC_E_OK != rc) {
                throw new Win32Exception(rc);
            }

            if(impersonate) {
                impersonationContext = getLocalContext(clientCred);
                rc = Secur32.INSTANCE.ImpersonateSecurityContext(impersonationContext);
            } else {
                impersonationContext = null;
            }

            String targetSpn = getServicePrincipalName(context);
            response = getToken(null, null, targetSpn);
        } catch (RuntimeException ex) {
            ...
        }
    } else {
        ... // second round
    }
    ... // build header form response and return it
}

方法getLocalContext()旨在为模拟用户获取服务本身的上下文。它看起来如下:

private static Sspi.CtxtHandle getLocalContext(final Sspi.CredHandle initialCredentials) {
    final IntByReference attr = new IntByReference();

    String localSpn = "HOST/" + Kernel32Util.getComputerName().toLowerCase();

    Sspi.CtxtHandle clientContext = null;
    Sspi.CtxtHandle serverContext = null;

    Sspi.SecBufferDesc clientToServerToken;
    Sspi.SecBufferDesc serverToClientToken = null;
    while(true) {
        // get clientContext and clientToServerToken:
        Sspi.CtxtHandle outClientContext = (clientContext != null) ? clientContext : new Sspi.CtxtHandle();
        clientToServerToken = new Sspi.SecBufferDesc(Sspi.SECBUFFER_TOKEN, Sspi.MAX_TOKEN_SIZE);
        int rc = Secur32.INSTANCE.InitializeSecurityContext(initialCredentials,
                clientContext, localSpn, Sspi.ISC_REQ_DELEGATE | Sspi.ISC_REQ_MUTUAL_AUTH, 0,
                Sspi.SECURITY_NATIVE_DREP, serverToClientToken, 0, outClientContext, clientToServerToken,
                attr, null);
        clientContext = outClientContext; // make them same for next round
        switch (rc) {
            case WinError.SEC_I_CONTINUE_NEEDED:
                break;
            case WinError.SEC_E_OK:
                return serverContext != null ? serverContext : clientContext;
            default:
                freeContexts(clientContext, serverContext);
                throw new Win32Exception(rc);
        }

        // get serverContext and serverToClientToken:
        Sspi.CtxtHandle outServerContext = (serverContext != null) ? serverContext : new Sspi.CtxtHandle();
        serverToClientToken = new Sspi.SecBufferDesc(Sspi.SECBUFFER_TOKEN, Sspi.MAX_TOKEN_SIZE);
        rc = Secur32.INSTANCE.AcceptSecurityContext(initialCredentials,
                serverContext, clientToServerToken, Sspi.ISC_REQ_DELEGATE | Sspi.ISC_REQ_MUTUAL_AUTH,
                Sspi.SECURITY_NATIVE_DREP, outServerContext, serverToClientToken, attr, null);
        serverContext = outServerContext; // make them same for next round
        switch (rc) {
            case WinError.SEC_I_CONTINUE_NEEDED:
                break;
            case WinError.SEC_E_OK:
                return serverContext;
            default:
                freeContexts(clientContext, serverContext);
                throw new Win32Exception(rc);
        }
    }
}

我发现它在返回之前会运行两轮(InitializeSecurityContextAcceptSecurityContextInitializeSecurityContextAcceptSecurityContext)。

作为参考,getToken方法只是从原始的WinHttpClient WindowsNegotiateScheme类复制而来:

String getToken(
        final CtxtHandle continueCtx,
        final SecBufferDesc continueToken,
        final String targetName) {
    final IntByReference attr = new IntByReference();
    final SecBufferDesc token = new SecBufferDesc(
            Sspi.SECBUFFER_TOKEN, Sspi.MAX_TOKEN_SIZE);

    sspiContext = new CtxtHandle();
    final int rc = Secur32.INSTANCE.InitializeSecurityContext(clientCred,
            continueCtx, targetName, Sspi.ISC_REQ_DELEGATE | Sspi.ISC_REQ_MUTUAL_AUTH, 0,
            Sspi.SECURITY_NATIVE_DREP, continueToken, 0, sspiContext, token,
            attr, null);
    switch (rc) {
        case WinError.SEC_I_CONTINUE_NEEDED:
            continueNeeded = true;
            break;
        case WinError.SEC_E_OK:
            dispose(); // Don't keep the context
            continueNeeded = false;
            break;
        default:
            dispose();
            throw new Win32Exception(rc);
    }
    return Base64.encodeBase64String(token.getBytes());
}

我在调试器中检查过用于模拟的用户名实际上类似于DOMAINNAME\johndoe,我意识到如果我使用的是非xx用户,行为也是一样的:{{ 1}}返回0(= SEC_E_OK)。这告诉我,这个方法不是针对域控制器检查用户名,而是仅检查其参数的语法,但是为什么对AcquireCredentialsHandle的初始调用不会抱怨如果第一个参数是不存在的凭据用户?或者该方法在某种程度上忽略了它的第一个参数,但为什么呢?

假设我有一个上下文供用户通过InitializeSecurityContext来电进行模拟,而忽略对AcquireCredentialsHandlegetLocalContext的调用也不起作用。

我发现没有太多关于模拟纯SSPI使用的文档。我发现的大多数样本(如this)依赖于.net类ImpersonateSecurityContext,这在纯SSPI接口中不可用。并且该类的source code看起来并不像是由几个SSPI调用组成,而是引用了许多其他.net基础结构。

我认为Windows操作系统模拟可能无法与Java正常协作,但根据this question,Windows模拟是每个线程,根据this question,Java使用操作系统线程。 / p>

我如何让模仿工作,或者你至少有一些提示要尝试什么?

1 个答案:

答案 0 :(得分:1)

您的代码永远不会执行您所描述的内容。您的代码段仅执行凭据委派(无约束委派),其中转发的TGT嵌入在安全上下文中。由于您需要协议转换(S4U2Self),因此您需要使用LsaLogonUser致电KERB_S4U_LOGON

阅读this博文。在C中会有很多痛苦。永远不要理解为什么GSS-API会如此简单,但在Windows上会如此痛苦。

祝你好运。