summaryrefslogtreecommitdiff
path: root/auth/ntlmssp
diff options
context:
space:
mode:
authorStefan Metzmacher <metze@samba.org>2015-11-19 16:26:49 +0100
committerStefan Metzmacher <metze@samba.org>2016-04-12 19:25:23 +0200
commit0d641ee36ae2c2e47708587c5fc20eb1dc5d92d0 (patch)
tree007bd2cbd042e8371724e17c364e37f00d899469 /auth/ntlmssp
parentc0fc6a6d7f7a9d709f35c1a7e4812c0a89285977 (diff)
downloadsamba-0d641ee36ae2c2e47708587c5fc20eb1dc5d92d0.tar.gz
CVE-2016-2110: auth/ntlmssp: implement new_spnego support including MIC generation (as client)
We now detect a MsvAvTimestamp in target info as indication of the server to support NTLMSSP_MIC in the AUTH_MESSAGE. If the client uses NTLMv2 we provide NTLMSSP_AVFLAG_MIC_IN_AUTHENTICATE_MESSAGE and valid MIC. BUG: https://bugzilla.samba.org/show_bug.cgi?id=11644 Signed-off-by: Stefan Metzmacher <metze@samba.org> Reviewed-by: Günther Deschner <gd@samba.org>
Diffstat (limited to 'auth/ntlmssp')
-rw-r--r--auth/ntlmssp/ntlmssp.h1
-rw-r--r--auth/ntlmssp/ntlmssp_client.c206
2 files changed, 202 insertions, 5 deletions
diff --git a/auth/ntlmssp/ntlmssp.h b/auth/ntlmssp/ntlmssp.h
index bc2a8225386..24127686312 100644
--- a/auth/ntlmssp/ntlmssp.h
+++ b/auth/ntlmssp/ntlmssp.h
@@ -80,6 +80,7 @@ struct ntlmssp_state
struct {
const char *netbios_name;
const char *netbios_domain;
+ struct AV_PAIR_LIST av_pair_list;
} client;
struct {
diff --git a/auth/ntlmssp/ntlmssp_client.c b/auth/ntlmssp/ntlmssp_client.c
index af4d2498e18..b4196157c81 100644
--- a/auth/ntlmssp/ntlmssp_client.c
+++ b/auth/ntlmssp/ntlmssp_client.c
@@ -90,6 +90,12 @@ NTSTATUS ntlmssp_client_initial(struct gensec_security *gensec_security,
}
}
+ ntlmssp_state->negotiate_blob = data_blob_dup_talloc(ntlmssp_state,
+ *out);
+ if (ntlmssp_state->negotiate_blob.length != out->length) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
ntlmssp_state->expected_state = NTLMSSP_CHALLENGE;
return NT_STATUS_MORE_PROCESSING_REQUIRED;
@@ -113,8 +119,15 @@ NTSTATUS gensec_ntlmssp_resume_ccache(struct gensec_security *gensec_security,
if (in.length == 0) {
/*
* This is compat code for older callers
- * which were missing the "initial_blob"
+ * which were missing the "initial_blob"/"negotiate_blob".
+ *
+ * That means we can't calculate the NTLMSSP_MIC
+ * field correctly and need to force the
+ * old_spnego behaviour.
*/
+ DEBUG(10, ("%s: in.length==%u force_old_spnego!\n",
+ __func__, (unsigned int)in.length));
+ ntlmssp_state->force_old_spnego = true;
ntlmssp_state->neg_flags |= ntlmssp_state->required_flags;
ntlmssp_state->required_flags = 0;
ntlmssp_state->expected_state = NTLMSSP_CHALLENGE;
@@ -187,6 +200,12 @@ NTSTATUS gensec_ntlmssp_resume_ccache(struct gensec_security *gensec_security,
}
}
+ ntlmssp_state->negotiate_blob = data_blob_dup_talloc(ntlmssp_state,
+ in);
+ if (ntlmssp_state->negotiate_blob.length != in.length) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
ntlmssp_state->expected_state = NTLMSSP_CHALLENGE;
return NT_STATUS_MORE_PROCESSING_REQUIRED;
@@ -229,6 +248,9 @@ NTSTATUS ntlmssp_client_challenge(struct gensec_security *gensec_security,
bool is_anonymous = false;
const DATA_BLOB version_blob = ntlmssp_version_blob();
const NTTIME *server_timestamp = NULL;
+ uint8_t mic_buffer[NTLMSSP_MIC_SIZE] = { 0, };
+ DATA_BLOB mic_blob = data_blob_const(mic_buffer, sizeof(mic_buffer));
+ HMACMD5Context ctx;
TALLOC_CTX *mem_ctx = talloc_new(out_mem_ctx);
if (!mem_ctx) {
@@ -266,7 +288,7 @@ NTSTATUS ntlmssp_client_challenge(struct gensec_security *gensec_security,
chal_parse_string = "CdUdbdd";
chal_parse_string_short = "CdUdb";
}
- auth_gen_string = "CdBBUUUBdb";
+ auth_gen_string = "CdBBUUUBdbb";
} else {
if (chal_flags & NTLMSSP_NEGOTIATE_TARGET_INFO) {
chal_parse_string = "CdAdbddB";
@@ -275,7 +297,7 @@ NTSTATUS ntlmssp_client_challenge(struct gensec_security *gensec_security,
chal_parse_string_short = "CdAdb";
}
- auth_gen_string = "CdBBAAABdb";
+ auth_gen_string = "CdBBAAABdbb";
}
if (!msrpc_parse(mem_ctx,
@@ -386,11 +408,12 @@ NTSTATUS ntlmssp_client_challenge(struct gensec_security *gensec_security,
struct wbcCredentialCacheParams params;
struct wbcCredentialCacheInfo *info = NULL;
struct wbcAuthErrorInfo *error = NULL;
- struct wbcNamedBlob auth_blobs[1];
+ struct wbcNamedBlob auth_blobs[2];
const struct wbcBlob *wbc_auth_blob = NULL;
const struct wbcBlob *wbc_session_key = NULL;
wbcErr wbc_status;
int i;
+ bool new_spnego = false;
params.account_name = user;
params.domain_name = domain;
@@ -400,6 +423,10 @@ NTSTATUS ntlmssp_client_challenge(struct gensec_security *gensec_security,
auth_blobs[0].flags = 0;
auth_blobs[0].blob.data = in.data;
auth_blobs[0].blob.length = in.length;
+ auth_blobs[1].name = "negotiate_blob";
+ auth_blobs[1].flags = 0;
+ auth_blobs[1].blob.data = ntlmssp_state->negotiate_blob.data;
+ auth_blobs[1].blob.length = ntlmssp_state->negotiate_blob.length;
params.num_blobs = ARRAY_SIZE(auth_blobs);
params.blobs = auth_blobs;
@@ -416,6 +443,9 @@ NTSTATUS ntlmssp_client_challenge(struct gensec_security *gensec_security,
if (strequal(info->blobs[i].name, "session_key")) {
wbc_session_key = &info->blobs[i].blob;
}
+ if (strequal(info->blobs[i].name, "new_spnego")) {
+ new_spnego = true;
+ }
}
if ((wbc_auth_blob == NULL) || (wbc_session_key == NULL)) {
wbcFreeMemory(info);
@@ -436,6 +466,7 @@ NTSTATUS ntlmssp_client_challenge(struct gensec_security *gensec_security,
wbcFreeMemory(info);
return NT_STATUS_NO_MEMORY;
}
+ ntlmssp_state->new_spnego = new_spnego;
wbcFreeMemory(info);
goto done;
@@ -454,6 +485,150 @@ NTSTATUS ntlmssp_client_challenge(struct gensec_security *gensec_security,
flags |= CLI_CRED_LANMAN_AUTH;
}
+ if (target_info.length != 0 && !is_anonymous) {
+ struct AV_PAIR *pairs = NULL;
+ uint32_t count = 0;
+ enum ndr_err_code err;
+ struct AV_PAIR *timestamp = NULL;
+ struct AV_PAIR *eol = NULL;
+ uint32_t i = 0;
+ const char *service = NULL;
+ const char *hostname = NULL;
+
+ err = ndr_pull_struct_blob(&target_info,
+ ntlmssp_state,
+ &ntlmssp_state->server.av_pair_list,
+ (ndr_pull_flags_fn_t)ndr_pull_AV_PAIR_LIST);
+ if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+ return ndr_map_error2ntstatus(err);
+ }
+
+ count = ntlmssp_state->server.av_pair_list.count;
+ /*
+ * We need room for Flags, SingleHost,
+ * ChannelBindings and Target
+ */
+ pairs = talloc_zero_array(ntlmssp_state, struct AV_PAIR,
+ count + 4);
+ if (pairs == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i < count; i++) {
+ pairs[i] = ntlmssp_state->server.av_pair_list.pair[i];
+ }
+
+ ntlmssp_state->client.av_pair_list.count = count;
+ ntlmssp_state->client.av_pair_list.pair = pairs;
+
+ eol = ndr_ntlmssp_find_av(&ntlmssp_state->client.av_pair_list,
+ MsvAvEOL);
+ if (eol == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ timestamp = ndr_ntlmssp_find_av(&ntlmssp_state->client.av_pair_list,
+ MsvAvTimestamp);
+ if (timestamp != NULL) {
+ uint32_t sign_features =
+ GENSEC_FEATURE_SESSION_KEY |
+ GENSEC_FEATURE_SIGN |
+ GENSEC_FEATURE_SEAL;
+
+ server_timestamp = &timestamp->Value.AvTimestamp;
+
+ if (ntlmssp_state->force_old_spnego) {
+ sign_features = 0;
+ }
+
+ if (gensec_security->want_features & sign_features) {
+ struct AV_PAIR *av_flags = NULL;
+
+ av_flags = ndr_ntlmssp_find_av(&ntlmssp_state->client.av_pair_list,
+ MsvAvFlags);
+ if (av_flags == NULL) {
+ av_flags = eol;
+ eol++;
+ count++;
+ *eol = *av_flags;
+ av_flags->AvId = MsvAvFlags;
+ av_flags->Value.AvFlags = 0;
+ }
+
+ av_flags->Value.AvFlags |= NTLMSSP_AVFLAG_MIC_IN_AUTHENTICATE_MESSAGE;
+ ntlmssp_state->new_spnego = true;
+ }
+ }
+
+ {
+ struct AV_PAIR *SingleHost = NULL;
+
+ SingleHost = eol;
+ eol++;
+ count++;
+ *eol = *SingleHost;
+
+ /*
+ * This is not really used, but we want to
+ * add some more random bytes and match
+ * Windows.
+ */
+ SingleHost->AvId = MsvAvSingleHost;
+ SingleHost->Value.AvSingleHost.token_info.Flags = 0;
+ SingleHost->Value.AvSingleHost.token_info.TokenIL = 0;
+ generate_random_buffer(SingleHost->Value.AvSingleHost.token_info.MachineId,
+ sizeof(SingleHost->Value.AvSingleHost.token_info.MachineId));
+ SingleHost->Value.AvSingleHost.remaining = data_blob_null;
+ }
+
+ {
+ struct AV_PAIR *ChannelBindings = NULL;
+
+ ChannelBindings = eol;
+ eol++;
+ count++;
+ *eol = *ChannelBindings;
+
+ /*
+ * gensec doesn't support channel bindings yet,
+ * but we want to match Windows on the wire
+ */
+ ChannelBindings->AvId = MsvChannelBindings;
+ memset(ChannelBindings->Value.ChannelBindings, 0,
+ sizeof(ChannelBindings->Value.ChannelBindings));
+ }
+
+ service = gensec_get_target_service(gensec_security);
+ hostname = gensec_get_target_hostname(gensec_security);
+ if (service != NULL && hostname != NULL) {
+ struct AV_PAIR *target = NULL;
+
+ target = eol;
+ eol++;
+ count++;
+ *eol = *target;
+
+ target->AvId = MsvAvTargetName;
+ target->Value.AvTargetName = talloc_asprintf(pairs, "%s/%s",
+ service,
+ hostname);
+ if (target->Value.AvTargetName == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ ntlmssp_state->client.av_pair_list.count = count;
+ ntlmssp_state->client.av_pair_list.pair = pairs;
+
+ err = ndr_push_struct_blob(&target_info,
+ ntlmssp_state,
+ &ntlmssp_state->client.av_pair_list,
+ (ndr_push_flags_fn_t)ndr_push_AV_PAIR_LIST);
+ if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
nt_status = cli_credentials_get_ntlm_response(gensec_security->credentials, mem_ctx,
&flags, challenge_blob,
server_timestamp, target_info,
@@ -522,13 +697,34 @@ NTSTATUS ntlmssp_client_challenge(struct gensec_security *gensec_security,
workstation,
encrypted_session_key.data, encrypted_session_key.length,
ntlmssp_state->neg_flags,
- version_blob.data, version_blob.length);
+ version_blob.data, version_blob.length,
+ mic_blob.data, mic_blob.length);
if (!NT_STATUS_IS_OK(nt_status)) {
talloc_free(mem_ctx);
return nt_status;
}
+ /*
+ * We always include the MIC, even without:
+ * av_flags->Value.AvFlags |= NTLMSSP_AVFLAG_MIC_IN_AUTHENTICATE_MESSAGE;
+ * ntlmssp_state->new_spnego = true;
+ *
+ * This matches a Windows client.
+ */
+ hmac_md5_init_limK_to_64(session_key.data,
+ session_key.length,
+ &ctx);
+ hmac_md5_update(ntlmssp_state->negotiate_blob.data,
+ ntlmssp_state->negotiate_blob.length,
+ &ctx);
+ hmac_md5_update(in.data, in.length, &ctx);
+ hmac_md5_update(out->data, out->length, &ctx);
+ hmac_md5_final(mic_buffer, &ctx);
+ memcpy(out->data + NTLMSSP_MIC_OFFSET, mic_buffer, NTLMSSP_MIC_SIZE);
+
done:
+ data_blob_free(&ntlmssp_state->negotiate_blob);
+
ntlmssp_state->session_key = session_key;
talloc_steal(ntlmssp_state, session_key.data);