gssapi / gss-ntlmssp

A complete implementation of the MS-NLMP documents as a GSSAPI mechanism
ISC License
30 stars 26 forks source link

Using gss-ntlmssp from Java #94

Open nir2 opened 1 year ago

nir2 commented 1 year ago

I have a problem, maybe you can help me. I am using the gss-ntlmssp GSS plugin from Java on Linux by setting the system property "-Dsun.security.jgss.native=true".

I am testing HTTP proxy authorization with Negotiate (Kerberos), NTLM and Basic. I have set up a test system with multiple docker containers, one with a Samba DC, one with a Squid proxy and one development container with which I test the authorization.

The test setup seems to be ok, I can succesfully do:

[CORP\user1@develop1 proxytest]$ curl -v --proxy-negotiate -u: -x squidserver.corp.example.com:3128 https://<destination>
[CORP\user1@develop1 proxytest]$ curl -v --proxy-ntlm --proxy-user CORP\\user1 -u: -x squidserver.corp.example.com:3128 https://<destination>

Though I must admit challenge/response authorization with wbinfo is only successful if I run it as root, not as CORP\user1 (maybe that's the problem?):

[CORP\user1@develop1 proxytest]$ wbinfo -a CORP\\user1
Enter CORP\user1's password:
plaintext password authentication succeeded
Enter CORP\user1's password:
challenge/response password authentication failed
Could not authenticate user CORP\user1 with challenge/response

In my Java program I create a GSS context with the NTLMSSP Oid and create a token with:

        Oid ntlmOid = new Oid("1.3.6.1.4.1.311.2.2.10");
        Oid oid = ntlmOid;
        String service = "HTTP@squidserver.corp.example.com";
        GSSName serverName = manager.createName(service, GSSName.NT_HOSTBASED_SERVICE);
        GSSContext context = manager.createContext(serverName,
                oid,
                null,
                GSSContext.DEFAULT_LIFETIME);

This is successful, and I can create a token which I send Base64 encoded to Squid:

        byte[] token = new byte[0];
        token = context.initSecContext(token, 0, token.length);
        String encodedToken = Base64.getEncoder().encodeToString(token);

The Proxy-authenticate line I send to Squid looks like this:

Proxy-Authorization: NTLM TlRMTVNTUAABAAAAN4II4gAAAAAAAAAAAAAAAAAAAAAGAgAAAAAADw==

Then I get back the following token from Squid:

Proxy-Authenticate: NTLM TlRMTVNTUAACAAAACAAIADgAAAA1goniYTUVUwP3TmIAAAAAAAAAAJYAlgBAAAAABgEAAAAAAA9DAE8AUgBQAAIACABDAE8AUgBQAAEAFgBTAFEAVQBJAEQAUwBFAFIAVgBFAFIABAAgAGMAbwByAHAALgBlAHgAYQBtAHAAbABlAC4AYwBvAG0AAwA4AHMAcQB1AGkAZABzAGUAcgB2AGUAcgAuAGMAbwByAHAALgBlAHgAYQBtAHAAbABlAC4AYwBvAG0ABwAIAHAs/DxdjdkBAAAAAA==

But if I Base64 decode this token and feed it into the GSSContext I get an exception:

...
byte[] tokenBin = Base64.getDecoder().decode(token64);
byte[] token2 = context.initSecContext(tokenBin, 0, tokenBin.length);

Exception:

Exception in thread "main" GSSException: Defective token detected (Mechanism level: Failed to decode data)
        at java.security.jgss/sun.security.jgss.wrapper.GSSLibStub.initContext(Native Method)
        at java.security.jgss/sun.security.jgss.wrapper.NativeGSSContext.initSecContext(NativeGSSContext.java:277)
        at java.security.jgss/sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:266)
        at java.security.jgss/sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:196)
        at proxytest.HttpClient.main(HttpClient.java:136)

The gss-ntlmssp Log looks like this:

[CORP\user1@develop1 proxytest]$ cat gssntlm.log
[1684837520] ALLOK: gssntlm_import_name_by_mech() @ src/gss_names.c:355 [0:0]
[1684837520] ALLOK: gssntlm_acquire_cred_from() @ src/gss_creds.c:501 [0:0]
[1684837520] ALLOK: gssntlm_import_name_by_mech() @ src/gss_names.c:355 [0:0]
[1684837520] ALLOK: gssntlm_init_sec_context() @ src/gss_sec_ctx.c:246 [1:0]
[1684837520] ALLOK: gssntlm_release_name() @ src/gss_names.c:633 [0:0]
[1684837520] ALLOK: gssntlm_release_name() @ src/gss_names.c:633 [0:0]
[1684837520] ALLOK: gssntlm_import_name_by_mech() @ src/gss_names.c:355 [0:0]
[1684837520] ALLOK: gssntlm_acquire_cred_from() @ src/gss_creds.c:501 [0:0]
[1684837520] ERROR: gssntlm_init_sec_context() @ src/gss_sec_ctx.c:293 [589824:1314127873]
[1684837520] ALLOK: gssntlm_delete_sec_context() @ src/gss_sec_ctx.c:483 [0:0]
[1684837520] ALLOK: gssntlm_release_name() @ src/gss_names.c:633 [0:0]
[1684837520] ALLOK: gssntlm_release_name() @ src/gss_names.c:633 [0:0]
[1684837520] ALLOK: gssntlm_display_status() @ src/gss_err.c:139 [0:0]

Any idea what I am doing wrong ? Isn't base64 decoding the token I get and feeding it into gss-ntlmssp enough, do I have to do something else ? Btw., if I use the Kerberos Oid, and do a Proxy-Authenticate: Negotiate instead of NTLM, my test program works, so I think principally the Java GSSAPI is working.

simo5 commented 1 year ago

sounds to me the Java wrapper is doing something wrong.

For example it is very od to see gssntlm_acquire_cred_from(0 called twice, are you sure your code is performing a continuation and not trying to start a new ISC operation ?

Not knowing how the Java bindings works, I can't tell.

Decoding the base64 you posted shows a valid NTLMSSP challenge token.

As for access to windbind, asking it to decode tokens is indeed a privileged operation. You do not have to be root, but your user need to be given permission to access the privileged winbind pipe.

nir2 commented 1 year ago

Thanks, good to know the base64 encoded challenge is valid.

It seems, the Java GSS wrapper isn't the problem. I wrote a small test program in C/C++ based on the example in RFC 7546, and get exactly the same error (in src/gss_sec_ctx.c:277). I must be doing something fundamentally wrong, perhaps linking with the wrong libraries. The duplicate gssntlm_acquire_cred_from is caused, because I pass GSS_C_NO_CREDENTIAL into the gss_init_sec_context; if I pass a credential in, I get it only once but the error in gss_sec_ctx.c:277 persists.

I attached the source of my small test, maybe (if you have a moment of time), you at a first glance see what I am doing wrong or missing. I was developing and testing on Fedora 38.

proxytest.zip

Ps: Just saw a bug in my code, it should be "memcpy((char *)(tokenResult->value), token.data(), token.size());" instead of "strcpy(...", This makes a change, the decoded token in the gss_buffer_desc looks like this now:

x tokenResult->value tokenResult->value+214
0x004689b0: 4e 54 4c 4d 53 53 50 00 02 00 00 00 08 00 08 00  NTLMSSP.........
0x004689c0: 38 00 00 00 35 82 89 e2 14 e6 aa a6 dd 66 7d 95  8...5........f}.
0x004689d0: 00 00 00 00 00 00 00 00 96 00 96 00 40 00 00 00  ............@...
0x004689e0: 06 01 00 00 00 00 00 0f 43 00 4f 00 52 00 50 00  ........C.O.R.P.
0x004689f0: 02 00 08 00 43 00 4f 00 52 00 50 00 01 00 16 00  ....C.O.R.P.....
0x00468a00: 53 00 51 00 55 00 49 00 44 00 53 00 45 00 52 00  S.Q.U.I.D.S.E.R.
0x00468a10: 56 00 45 00 52 00 04 00 00 63 00 6f 00 72 00 70  V.E.R....c.o.r.p
0x00468a20: 00 2e 00 65 00 78 00 61 00 6d 00 70 00 6c 00 65  ...e.x.a.m.p.l.e
0x00468a30: 00 2e 00 63 00 6f 00 6d 00 03 00 38 00 73 00 71  ...c.o.m...8.s.q
0x00468a40: 00 75 00 69 00 64 00 73 00 65 00 72 00 76 00 65  .u.i.d.s.e.r.v.e
0x00468a50: 00 72 00 2e 00 63 00 6f 00 72 00 70 00 2e 00 65  .r...c.o.r.p...e
0x00468a60: 00 78 00 61 00 6d 00 70 00 6c 00 65 00 2e 00 63  .x.a.m.p.l.e...c
0x00468a70: 00 6f 00 6d 00 07 00 08 00 5c c7 50 38 ee 8e d9  .o.m.....\.P8...
0x00468a80: 01 00 00 00 00 00

Now I get an error in line src/gss_sec_ctx.c:293 instead of src/gss_sec_ctx.c:277, like in Java.

simo5 commented 1 year ago

The NTLMSSP protol is a multistep challenge response protocol. It mean you need to preserve the security context between calls to gss_init_sec_context and you need to provide input credentials.

The credentials either store internally the password used to authenticate the user, or provide the linkage to talk to winbindd.

Without credentials you can't complete authentication. Was your gss-ntlmssp built with winbindd support?

That said, looking again at the error log your code seem to be failing in ntlm_decode_chal_msg(), if you could GDB the binary reproducer and step through that function and tell me how it fails, I can probably give you more hints on what is wrong.