1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-22 13:34:15 +03:00

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>
This commit is contained in:
Stefan Metzmacher 2015-11-19 16:26:49 +01:00
parent c0fc6a6d7f
commit 0d641ee36a
2 changed files with 202 additions and 5 deletions

View File

@ -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 {

View File

@ -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);