From de452fa4d5dff5b7ad0079e86d1bc3716a7bd8be Mon Sep 17 00:00:00 2001 From: Jeroen van Erp Date: Tue, 16 May 2023 10:58:08 +0200 Subject: [PATCH] NtlmNegotiate sends Domain/Workstation/Version fields --- .../java/com/hierynomus/ntlm/NtlmConfig.java | 8 +++ .../ntlm/messages/NtlmNegotiate.java | 60 +++++++++++++++---- .../smbj/auth/NtlmAuthenticator.java | 49 +++++++++------ .../hierynomus/spnego/NegTokenInitSpec.groovy | 3 +- 4 files changed, 88 insertions(+), 32 deletions(-) diff --git a/src/main/java/com/hierynomus/ntlm/NtlmConfig.java b/src/main/java/com/hierynomus/ntlm/NtlmConfig.java index cc9fa2e0..fff2c3d4 100644 --- a/src/main/java/com/hierynomus/ntlm/NtlmConfig.java +++ b/src/main/java/com/hierynomus/ntlm/NtlmConfig.java @@ -23,6 +23,7 @@ public class NtlmConfig { private WindowsVersion windowsVersion; private String workstationName; + private boolean omitVersion; public static NtlmConfig defaultConfig() { return builder().build(); @@ -38,6 +39,7 @@ private NtlmConfig() { private NtlmConfig(NtlmConfig other) { this.windowsVersion = other.windowsVersion; this.workstationName = other.workstationName; + this.omitVersion = other.omitVersion; } public WindowsVersion getWindowsVersion() { @@ -48,12 +50,17 @@ public String getWorkstationName() { return workstationName; } + public boolean isOmitVersion() { + return omitVersion; + } + public static class Builder { private NtlmConfig config; public Builder() { config = new NtlmConfig(); config.windowsVersion = new WindowsVersion(ProductMajorVersion.WINDOWS_MAJOR_VERSION_6, ProductMinorVersion.WINDOWS_MINOR_VERSION_1, 7600, NtlmRevisionCurrent.NTLMSSP_REVISION_W2K3); + config.omitVersion = false; } public Builder withWindowsVersion(WindowsVersion windowsVersion) { @@ -71,6 +78,7 @@ public Builder withIntegrity(boolean integrity) { } public Builder withOmitVersion(boolean omitVersion) { + config.omitVersion = omitVersion; return this; } diff --git a/src/main/java/com/hierynomus/ntlm/messages/NtlmNegotiate.java b/src/main/java/com/hierynomus/ntlm/messages/NtlmNegotiate.java index dcc0080d..784e33b0 100644 --- a/src/main/java/com/hierynomus/ntlm/messages/NtlmNegotiate.java +++ b/src/main/java/com/hierynomus/ntlm/messages/NtlmNegotiate.java @@ -15,6 +15,10 @@ */ package com.hierynomus.ntlm.messages; +import static com.hierynomus.ntlm.messages.Utils.EMPTY; +import static com.hierynomus.ntlm.messages.Utils.writeOffsettedByteArrayFields; + +import com.hierynomus.ntlm.functions.NtlmFunctions; import com.hierynomus.protocol.commons.Charsets; import com.hierynomus.protocol.commons.buffer.Buffer; @@ -27,8 +31,15 @@ */ public class NtlmNegotiate extends NtlmMessage { - public NtlmNegotiate(Set negotiateFlags) { - super(negotiateFlags, null); + private byte[] domain; + private byte[] workstation; + private boolean omitVersion; + + public NtlmNegotiate(Set flags, String domain, String workstation, WindowsVersion version, boolean omitVersion) { + super(flags, version); + this.domain = domain != null ? NtlmFunctions.oem(domain) : EMPTY; + this.workstation = workstation != null ? NtlmFunctions.oem(workstation) : EMPTY; + this.omitVersion = omitVersion; } public void write(Buffer.PlainBuffer buffer) { @@ -39,21 +50,46 @@ public void write(Buffer.PlainBuffer buffer) { // not an integral value buffer.putUInt32(EnumUtils.toLong(negotiateFlags)); // NegotiateFlags (4 bytes) - // DomainNameFields (8 bytes) - buffer.putUInt16(0x0); // DomainNameLen (2 bytes) - buffer.putUInt16(0x0); // DomainNameMaxLen (2 bytes) - buffer.putUInt32(0x0); // DomainNameBufferOffset (4 bytes) - // WorkstationFields (8 bytes) - buffer.putUInt16(0x0); // WorkstationLen (2 bytes) - buffer.putUInt16(0x0); // WorkstationMaxLen (2 bytes) - buffer.putUInt32(0x0); // WorkstationBufferOffset (4 bytes) + int offset = 0x20; + if (!omitVersion) { + offset += 8; // Version (8 bytes) + } + + if (negotiateFlags.contains(NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED)) { + // DomainNameFields (8 bytes) + offset = writeOffsettedByteArrayFields(buffer, domain, offset); + } else { + buffer.putUInt16(0); // DomainNameLen (2 bytes) + buffer.putUInt16(0); // DomainNameMaxLen (2 bytes) + buffer.putUInt32(0); // DomainNameBufferOffset (4 bytes) + } + + if (negotiateFlags.contains(NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED)) { + // WorkstationFields (8 bytes) + offset = writeOffsettedByteArrayFields(buffer, workstation, offset); + } else { + buffer.putUInt16(0); // WorkstationLen (2 bytes) + buffer.putUInt16(0); // WorkstationMaxLen (2 bytes) + buffer.putUInt32(0); // WorkstationBufferOffset (4 bytes) + } + + // if `omitVersion`, omit this field, because some implementations (e.g. Windows + // 2000) don't like it + if (!omitVersion && negotiateFlags.contains(NTLMSSP_NEGOTIATE_VERSION)) { + version.writeTo(buffer); // Version (8 bytes) + } else if (!omitVersion) { + buffer.putUInt64(0); // Reserved (8 bytes) + } + + buffer.putRawBytes(domain); // DomainName (variable) + buffer.putRawBytes(workstation); // Workstation (variable) } @Override public String toString() { return "NtlmNegotiate{\n" + - // " domain='" + NtlmFunctions.oem(domain) + "'',\n" + - // " workstation='" + NtlmFunctions.oem(workstation) + "',\n" + + " domain='" + NtlmFunctions.oem(domain) + "'',\n" + + " workstation='" + NtlmFunctions.oem(workstation) + "',\n" + " negotiateFlags=" + negotiateFlags + ",\n" + " version=" + version + "\n" + "}"; diff --git a/src/main/java/com/hierynomus/smbj/auth/NtlmAuthenticator.java b/src/main/java/com/hierynomus/smbj/auth/NtlmAuthenticator.java index f93aa5a8..1556e992 100644 --- a/src/main/java/com/hierynomus/smbj/auth/NtlmAuthenticator.java +++ b/src/main/java/com/hierynomus/smbj/auth/NtlmAuthenticator.java @@ -64,7 +64,7 @@ import com.hierynomus.spnego.SpnegoException; public class NtlmAuthenticator implements Authenticator { - enum State { NEGOTIATE, AUTHENTICATE, COMPLETE }; + enum State { NEGOTIATE, AUTHENTICATE, COMPLETE; }; private static final Logger logger = LoggerFactory.getLogger(NtlmAuthenticator.class); @@ -93,18 +93,18 @@ public NtlmAuthenticator create() { } @Override - public AuthenticateResponse authenticate(final AuthenticationContext context, final byte[] gssToken, ConnectionContext connectionContext) throws IOException { + public AuthenticateResponse authenticate(final AuthenticationContext context, final byte[] gssToken, + ConnectionContext connectionContext) throws IOException { try { - if (state == State.COMPLETE) { + if (this.state == State.COMPLETE) { return null; - } else if (state == State.NEGOTIATE) { + } else if (this.state == State.NEGOTIATE) { logger.debug("Initialized Authentication of {} using NTLM", context.getUsername()); this.state = State.AUTHENTICATE; return doNegotiate(context, gssToken); } else { logger.debug("Received token: {}", ByteArrayUtils.printHex(gssToken)); NegTokenTarg negTokenTarg = new NegTokenTarg().read(gssToken); - BigInteger negotiationResult = negTokenTarg.getNegotiationResult(); NtlmChallenge serverNtlmChallenge = new NtlmChallenge(); try { serverNtlmChallenge.read(new Buffer.PlainBuffer(negTokenTarg.getResponseToken(), Endian.LE)); @@ -125,20 +125,31 @@ public AuthenticateResponse authenticate(final AuthenticationContext context, fi private AuthenticateResponse doNegotiate(AuthenticationContext context, byte[] gssToken) throws SpnegoException { AuthenticateResponse response = new AuthenticateResponse(); - this.negotiateFlags = EnumSet.of( - NTLMSSP_NEGOTIATE_56, - NTLMSSP_NEGOTIATE_128, - NTLMSSP_NEGOTIATE_TARGET_INFO, - NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY, - NTLMSSP_NEGOTIATE_SIGN, - NTLMSSP_NEGOTIATE_ALWAYS_SIGN, - NTLMSSP_NEGOTIATE_KEY_EXCH, - NTLMSSP_NEGOTIATE_NTLM, - NTLMSSP_NEGOTIATE_NTLM, - NTLMSSP_REQUEST_TARGET, - NTLMSSP_NEGOTIATE_UNICODE); - - this.negotiateMessage = new NtlmNegotiate(negotiateFlags); + this.negotiateFlags = EnumSet.of(NTLMSSP_NEGOTIATE_128, NTLMSSP_REQUEST_TARGET, + NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY); + if (!config.isOmitVersion() && config.getWindowsVersion() != null) { + this.negotiateFlags.add(NtlmNegotiateFlag.NTLMSSP_NEGOTIATE_VERSION); + } + + if (!context.isAnonymous()) { + this.negotiateFlags.add(NTLMSSP_NEGOTIATE_SIGN); + this.negotiateFlags.add(NTLMSSP_NEGOTIATE_ALWAYS_SIGN); + this.negotiateFlags.add(NTLMSSP_NEGOTIATE_KEY_EXCH); + } else if (context.isGuest()) { + this.negotiateFlags.add(NTLMSSP_NEGOTIATE_KEY_EXCH); + } else { + this.negotiateFlags.add(NTLMSSP_NEGOTIATE_ANONYMOUS); + } + + if (context.getDomain() != null && !context.getDomain().isEmpty()) { + this.negotiateFlags.add(NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED); + } + + if (this.config.getWorkstationName() != null && !this.config.getWorkstationName().isEmpty()) { + this.negotiateFlags.add(NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED); + } + + this.negotiateMessage = new NtlmNegotiate(negotiateFlags, context.getDomain(), config.getWorkstationName(), config.getWindowsVersion(), config.isOmitVersion()); logger.trace("Sending NTLM negotiate message: {}", this.negotiateMessage); response.setNegToken(negTokenInit(this.negotiateMessage)); return response; diff --git a/src/test/groovy/com/hierynomus/spnego/NegTokenInitSpec.groovy b/src/test/groovy/com/hierynomus/spnego/NegTokenInitSpec.groovy index 0ac0eabd..437088bf 100644 --- a/src/test/groovy/com/hierynomus/spnego/NegTokenInitSpec.groovy +++ b/src/test/groovy/com/hierynomus/spnego/NegTokenInitSpec.groovy @@ -17,6 +17,7 @@ package com.hierynomus.spnego import com.hierynomus.asn1.types.primitive.ASN1ObjectIdentifier import com.hierynomus.ntlm.messages.NtlmNegotiate +import com.hierynomus.ntlm.messages.WindowsVersion import com.hierynomus.protocol.commons.ByteArrayUtils import com.hierynomus.protocol.commons.buffer.Buffer import com.hierynomus.protocol.commons.buffer.Endian @@ -68,7 +69,7 @@ class NegTokenInitSpec extends Specification { NTLMSSP_NEGOTIATE_UNICODE) when: - new NtlmNegotiate(flags).write(ntlmBuffer) + new NtlmNegotiate(flags, "", "", new WindowsVersion(WINDOWS_MAJOR_VERSION_6, WINDOWS_MINOR_VERSION_1, 7600, NTLMSSP_REVISION_W2K3), true).write(ntlmBuffer) initToken.addSupportedMech(new ASN1ObjectIdentifier("1.3.6.1.4.1.311.2.2.10")) initToken.setMechToken(ntlmBuffer.compactData) initToken.write(spnegoBuffer)