From 4d056974dd531b8d01a5453f9c37715c7ea5f9df Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Wed, 12 Jul 2017 17:48:46 +1200 Subject: [PATCH] s4-smbtorture: Add test krb5.kdc to prove fix for CVE-2017-11103 BUG: https://bugzilla.samba.org/show_bug.cgi?id=12894 Signed-off-by: Andrew Bartlett Reviewed-by: Garming Sam --- source4/torture/krb5/kdc-heimdal.c | 171 ++++++++++++++++++++++++++++++++++++- 1 file changed, 169 insertions(+), 2 deletions(-) diff --git a/source4/torture/krb5/kdc-heimdal.c b/source4/torture/krb5/kdc-heimdal.c index 7d5a16702c3..4ea779b90ed 100644 --- a/source4/torture/krb5/kdc-heimdal.c +++ b/source4/torture/krb5/kdc-heimdal.c @@ -45,6 +45,14 @@ enum torture_krb5_test { TORTURE_KRB5_TEST_AES, TORTURE_KRB5_TEST_RC4, TORTURE_KRB5_TEST_AES_RC4, + + /* + * This is in and out of the client. + * Out refers to requests, in refers to replies + */ + TORTURE_KRB5_TEST_CHANGE_SERVER_OUT, + TORTURE_KRB5_TEST_CHANGE_SERVER_IN, + TORTURE_KRB5_TEST_CHANGE_SERVER_BOTH, }; struct torture_krb5_context { @@ -76,12 +84,16 @@ static bool torture_krb5_pre_send_test(struct torture_krb5_context *test_context case TORTURE_KRB5_TEST_AES: case TORTURE_KRB5_TEST_RC4: case TORTURE_KRB5_TEST_AES_RC4: + case TORTURE_KRB5_TEST_CHANGE_SERVER_IN: torture_assert_int_equal(test_context->tctx, decode_AS_REQ(send_buf->data, send_buf->length, &test_context->as_req, &used), 0, "decode_AS_REQ failed"); torture_assert_int_equal(test_context->tctx, used, send_buf->length, "length mismatch"); torture_assert_int_equal(test_context->tctx, test_context->as_req.pvno, 5, "Got wrong as_req->pvno"); break; + case TORTURE_KRB5_TEST_CHANGE_SERVER_OUT: + case TORTURE_KRB5_TEST_CHANGE_SERVER_BOTH: + break; } return true; } @@ -191,7 +203,7 @@ static bool torture_check_krb5_as_rep_enctype(struct torture_krb5_context *test_ * */ -static bool torture_krb5_post_recv_test(struct torture_krb5_context *test_context, const krb5_data *recv_buf) +static bool torture_krb5_post_recv_test(struct torture_krb5_context *test_context, krb5_data *recv_buf) { KRB_ERROR error; size_t used; @@ -199,6 +211,7 @@ static bool torture_krb5_post_recv_test(struct torture_krb5_context *test_contex switch (test_context->test) { + case TORTURE_KRB5_TEST_CHANGE_SERVER_OUT: case TORTURE_KRB5_TEST_PLAIN: if (test_context->packet_count == 0) { ok = torture_check_krb5_error(test_context, @@ -428,7 +441,65 @@ static bool torture_krb5_post_recv_test(struct torture_krb5_context *test_contex test_context->packet_count < 3, "Too many packets"); break; + case TORTURE_KRB5_TEST_CHANGE_SERVER_IN: + case TORTURE_KRB5_TEST_CHANGE_SERVER_BOTH: + { + AS_REP mod_as_rep; + krb5_error_code k5ret; + krb5_data modified_recv_buf; + if (test_context->packet_count == 0) { + ok = torture_check_krb5_error(test_context, + recv_buf, + KRB5KDC_ERR_PREAUTH_REQUIRED, + false); + torture_assert(test_context->tctx, + ok, + "torture_check_krb5_error failed"); + } else if ((decode_KRB_ERROR(recv_buf->data, recv_buf->length, &error, &used) == 0) + && (test_context->packet_count == 1)) { + torture_assert_int_equal(test_context->tctx, used, recv_buf->length, "length mismatch"); + torture_assert_int_equal(test_context->tctx, error.pvno, 5, "Got wrong error.pvno"); + torture_assert_int_equal(test_context->tctx, error.error_code, KRB5KRB_ERR_RESPONSE_TOO_BIG - KRB5KDC_ERR_NONE, + "Got wrong error.error_code"); + free_KRB_ERROR(&error); + } else { + torture_assert_int_equal(test_context->tctx, + decode_AS_REP(recv_buf->data, recv_buf->length, &test_context->as_rep, &used), 0, + "decode_AS_REP failed"); + torture_assert_int_equal(test_context->tctx, used, recv_buf->length, "length mismatch"); + torture_assert_int_equal(test_context->tctx, + test_context->as_rep.pvno, 5, + "Got wrong as_rep->pvno"); + torture_assert_int_equal(test_context->tctx, + test_context->as_rep.ticket.tkt_vno, 5, + "Got wrong as_rep->ticket.tkt_vno"); + torture_assert_int_equal(test_context->tctx, + test_context->as_rep.ticket.sname.name_string.len, 2, + "Got wrong as_rep->ticket.sname.name_string.len"); + free(test_context->as_rep.ticket.sname.name_string.val[0]); + free(test_context->as_rep.ticket.sname.name_string.val[1]); + test_context->as_rep.ticket.sname.name_string.val[0] = strdup("bad"); + test_context->as_rep.ticket.sname.name_string.val[1] = strdup("mallory"); + + mod_as_rep = test_context->as_rep; + + ASN1_MALLOC_ENCODE(AS_REP, modified_recv_buf.data, modified_recv_buf.length, + &mod_as_rep, &used, k5ret); + torture_assert_int_equal(test_context->tctx, + k5ret, 0, + "encode_AS_REQ failed"); + krb5_data_free(recv_buf); + + *recv_buf = modified_recv_buf; + free_AS_REQ(&test_context->as_req); + } + torture_assert(test_context->tctx, test_context->packet_count < 3, "too many packets"); + + break; + } } + + return true; } @@ -527,14 +598,23 @@ static bool torture_krb5_as_req_creds(struct torture_context *tctx, krb5_creds my_creds; krb5_principal principal; struct smb_krb5_context *smb_krb5_context; + krb5_context k5_context; enum credentials_obtained obtained; const char *error_string; const char *password = cli_credentials_get_password(credentials); + const char *expected_principal_string; krb5_get_init_creds_opt *krb_options = NULL; + const char *realm; ok = torture_krb5_init_context(tctx, test, &smb_krb5_context); torture_assert(tctx, ok, "torture_krb5_init_context failed"); + k5_context = smb_krb5_context->krb5_context; + + expected_principal_string + = cli_credentials_get_principal(credentials, + tctx); + realm = strupper_talloc(tctx, cli_credentials_get_realm(credentials)); k5ret = principal_from_credentials(tctx, credentials, smb_krb5_context, &principal, &obtained, &error_string); torture_assert_int_equal(tctx, k5ret, 0, error_string); @@ -542,6 +622,9 @@ static bool torture_krb5_as_req_creds(struct torture_context *tctx, switch (test) { case TORTURE_KRB5_TEST_PLAIN: + case TORTURE_KRB5_TEST_CHANGE_SERVER_OUT: + case TORTURE_KRB5_TEST_CHANGE_SERVER_IN: + case TORTURE_KRB5_TEST_CHANGE_SERVER_BOTH: break; case TORTURE_KRB5_TEST_PAC_REQUEST: @@ -618,13 +701,55 @@ static bool torture_krb5_as_req_creds(struct torture_context *tctx, switch (test) { case TORTURE_KRB5_TEST_PLAIN: + case TORTURE_KRB5_TEST_CHANGE_SERVER_IN: case TORTURE_KRB5_TEST_PAC_REQUEST: case TORTURE_KRB5_TEST_AES: case TORTURE_KRB5_TEST_RC4: case TORTURE_KRB5_TEST_AES_RC4: + { + char *got_principal_string; + char *assertion_message; torture_assert_int_equal(tctx, k5ret, 0, "krb5_get_init_creds_password failed"); - break; + torture_assert_int_equal(tctx, + krb5_principal_get_type(k5_context, + my_creds.client), + KRB5_NT_PRINCIPAL, + "smb_krb5_init_context gave incorrect client->name.name_type"); + + torture_assert_int_equal(tctx, + krb5_unparse_name(k5_context, + my_creds.client, + &got_principal_string), 0, + "krb5_unparse_name failed"); + + assertion_message = talloc_asprintf(tctx, + "krb5_get_init_creds_password returned a different principal %s to what was expected %s", + got_principal_string, expected_principal_string); + krb5_free_unparsed_name(k5_context, got_principal_string); + + torture_assert(tctx, krb5_principal_compare(k5_context, + my_creds.client, + principal), + assertion_message); + + + torture_assert_str_equal(tctx, + my_creds.server->name.name_string.val[0], + "krbtgt", + "Mismatch in name between AS_REP and expected response, expected krbtgt"); + torture_assert_str_equal(tctx, + my_creds.server->name.name_string.val[1], + realm, + "Mismatch in realm part of krbtgt/ in AS_REP, expected krbtgt/REALM@REALM"); + + torture_assert_str_equal(tctx, + my_creds.server->realm, + realm, + "Mismatch in server realm in AS_REP, expected krbtgt/REALM@REALM"); + + break; + } case TORTURE_KRB5_TEST_BREAK_PW: torture_assert_int_equal(tctx, k5ret, KRB5KDC_ERR_PREAUTH_FAILED, "krb5_get_init_creds_password should have failed"); return true; @@ -633,6 +758,10 @@ static bool torture_krb5_as_req_creds(struct torture_context *tctx, torture_assert_int_equal(tctx, k5ret, KRB5KRB_AP_ERR_SKEW, "krb5_get_init_creds_password should have failed"); return true; + case TORTURE_KRB5_TEST_CHANGE_SERVER_OUT: + case TORTURE_KRB5_TEST_CHANGE_SERVER_BOTH: + torture_assert_int_equal(tctx, k5ret, 0, "krb5_get_init_creds_password failed"); + break; } k5ret = krb5_free_cred_contents(smb_krb5_context->krb5_context, &my_creds); @@ -689,6 +818,28 @@ static bool torture_krb5_as_req_aes_rc4(struct torture_context *tctx) TORTURE_KRB5_TEST_AES_RC4); } +/* Checking for the "Orpheus' Lyre" attack */ +static bool torture_krb5_as_req_change_server_out(struct torture_context *tctx) +{ + return torture_krb5_as_req_creds(tctx, + popt_get_cmdline_credentials(), + TORTURE_KRB5_TEST_CHANGE_SERVER_OUT); +} + +static bool torture_krb5_as_req_change_server_in(struct torture_context *tctx) +{ + return torture_krb5_as_req_creds(tctx, + popt_get_cmdline_credentials(), + TORTURE_KRB5_TEST_CHANGE_SERVER_IN); +} + +static bool torture_krb5_as_req_change_server_both(struct torture_context *tctx) +{ + return torture_krb5_as_req_creds(tctx, + popt_get_cmdline_credentials(), + TORTURE_KRB5_TEST_CHANGE_SERVER_BOTH); +} + NTSTATUS torture_krb5_init(TALLOC_CTX *ctx) { struct torture_suite *suite = torture_suite_create(ctx, "krb5"); @@ -720,6 +871,22 @@ NTSTATUS torture_krb5_init(TALLOC_CTX *ctx) "as-req-aes-rc4", torture_krb5_as_req_aes_rc4); + /* + * This is in and out of the client. + * Out refers to requests, in refers to replies + */ + torture_suite_add_simple_test(kdc_suite, + "as-req-change-server-in", + torture_krb5_as_req_change_server_in); + + torture_suite_add_simple_test(kdc_suite, + "as-req-change-server-out", + torture_krb5_as_req_change_server_out); + + torture_suite_add_simple_test(kdc_suite, + "as-req-change-server-both", + torture_krb5_as_req_change_server_both); + torture_suite_add_suite(kdc_suite, torture_krb5_canon(kdc_suite)); torture_suite_add_suite(suite, kdc_suite); -- 2.11.4.GIT