使用SSPI的Kerberos模拟:没有错误,但不起作用 [英] Kerberos Impersonation using SSPI: no errors, but not working

查看:229
本文介绍了使用SSPI的Kerberos模拟:没有错误,但不起作用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要模拟Java应用程序服务器中的用户,并使用该用户的权限向IIS中的ASP应用程序执行http请求.我正试图为此目的改编Apache HttpClient的WindowsNegotiateScheme类.这使用JNA直接访问Windows SSPI身份验证功能,因此,该问题可能并非特定于Java.因为我需要协议转换",所以我没有模拟用户的密码,只有名称.但是使用S4U功能,应该可以实现.

I need to impersonate a user in a Java application server, and execute an http request to an ASP application in IIS with the permissions of that user. I am trying to adapt the WindowsNegotiateScheme class of the Apache HttpClient for this purpose. This uses JNA to directly access the Windows SSPI authentication functions, hence the problem is probably not specific to Java. As I need a "protocol transition", I do not have the password of the user to impersonate available, just the name. But using the S4U features, this should be possible.

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

All SSPI function calls either return 0 (success) or 590610 (SEC_I_CONTINUE_NEEDED), never an error code, and the code runs, but the http request arrives at the ASP side with the user running the application server, no matter if I run it locally on my workstation under my user id, or in the application server (which happens to be the machine also running the IIS/ASP application), which runs as local system.

代码的相关部分如下:

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()用于为模拟用户获取服务本身的上下文.它看起来如下:

Method getLocalContext() is meant to get a context to the service itself for the impersonating user. It looks as follows:

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).

I found that it runs two complete rounds (InitializeSecurityContext, AcceptSecurityContext, InitializeSecurityContext, AcceptSecurityContext) before returning.

getToken方法只是从原始WinHttpClient WindowsNegotiateScheme类复制而来供参考:

And for reference, the getToken method is just copied from the original WinHttpClient WindowsNegotiateScheme class:

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,并且我意识到即使我使用不存在的用户也没有关系,其行为是相同的:AcquireCredentialsHandle返回0(= SEC_E_OK).这告诉我,该方法不是不检查域控制器的用户名,而是仅检查其参数的语法,但是为什么对InitializeSecurityContext的初始调用如果第一个参数是不存在的用户的凭据则不抱怨呢?还是该方法完全忽略了它的第一个参数,但是为什么呢?

I checked in the debugger that the username used for the impersonation is in fact something like DOMAINNAME\johndoe, and I realized it does not even matter if I use a nonexistig user, the behavior is just the same: AcquireCredentialsHandle returns 0 (= SEC_E_OK). This tells me that either this method does not check the user name against the domain controller, but only the syntax of its arguments, but then why does the initial call to InitializeSecurityContext not complain if it first argument are credentials for a nonexistent user? Or the method somehow ignores its first argument at all, but why?

假设我有一个上下文可以让用户模仿AcquireCredentialsHandle调用,而忽略对getLocalContextImpersonateSecurityContext的调用也无法正常工作.

Assuming I have a context for the user to impersonate already from the AcquireCredentialsHandle call and omitting the call to getLocalContext and ImpersonateSecurityContext did not work either.

我发现没有太多关于纯SSPI用于模仿的文档.我发现的大多数示例(例如)都依赖于.net类WindowsIdentity,在纯SSPI接口中不可用.并且该类的源代码看起来并不像如果它仅由几个SSPI调用组成,但是引用了许多其他.net基础结构.

I found there is not much documentation on pure SSPI usage for impersonation. Most samples I found (like this) rely on a .net class WindowsIdentity, which is not available in the pure SSPI interface. And the source code of that class does not look as if it just consists of a few SSPI calls, but a lot of other .net infrastructure is referenced.

我认为Windows操作系统模拟可能无法与Java正常协作,但是根据这个问题,Windows模拟是针对每个线程的,并根据

I thought that maybe the Windows OS impersonation does not collaborate properly with Java, but according to this question, the Windows impersonation is per thread, and according to this question, Java uses the OS threads.

我如何才能使模拟工作正常进行,或者您至少有一些提示可以尝试吗?

How can I get the impersonation working, or do you at least have some hints what to try?

推荐答案

您的代码将永远不会执行您所描述的事情.您的摘要仅执行凭据委派(无约束委派),而转发的TGT嵌入在安全上下文中.由于要进行协议转换(S4U2Self),因此需要调用 LsaLogonUser

Your code will never do what you have described. Your snippets only do credential delegation (unconstrained delegation) where the forwarded TGT is embedded in the security context. Since you want protocol transition (S4U2Self), you are required to call LsaLogonUser with KERB_S4U_LOGON.

阅读博客文章.在C语言中,这将是很痛苦的.永远都无法理解为什么使用GSS-API如此简单,但是在Windows上却是如此.

Read this blog post. It is going to be a lot of pain in C. Never understood why this is so easy with GSS-API, but such a pain on Windows.

祝你好运.

这篇关于使用SSPI的Kerberos模拟:没有错误,但不起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆