diff options
author | Joseph Sutton <josephsutton@catalyst.net.nz> | 2022-11-09 13:45:13 +1300 |
---|---|---|
committer | Stefan Metzmacher <metze@samba.org> | 2022-12-14 11:39:17 +0000 |
commit | 25918f9c16c1e74d9fd5ea9fd1901f4eba157324 (patch) | |
tree | cb7b1c79aab84a116b5e17938d23f68e978225b9 /python | |
parent | 6ff9fc58cd3a4cea1cf2c565e0060427c6e9af77 (diff) | |
download | samba-25918f9c16c1e74d9fd5ea9fd1901f4eba157324.tar.gz |
CVE-2022-37967 Add new PAC checksum
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15231
Pair-Programmed-With: Andrew Bartlett <abartlet@samba.org>
Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Signed-off-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
(similar to commit a50a2be622afaa7a280312ea12f5eb9c9a0c41da)
[jsutton@samba.org Fixed conflicts in krb5pac.idl and raw_testcase.py]
Diffstat (limited to 'python')
-rwxr-xr-x | python/samba/tests/krb5/compatability_tests.py | 22 | ||||
-rw-r--r-- | python/samba/tests/krb5/kdc_base_test.py | 8 | ||||
-rwxr-xr-x | python/samba/tests/krb5/kdc_tgs_tests.py | 59 | ||||
-rw-r--r-- | python/samba/tests/krb5/raw_testcase.py | 78 | ||||
-rwxr-xr-x | python/samba/tests/krb5/rodc_tests.py | 4 | ||||
-rwxr-xr-x | python/samba/tests/krb5/s4u_tests.py | 61 |
6 files changed, 215 insertions, 17 deletions
diff --git a/python/samba/tests/krb5/compatability_tests.py b/python/samba/tests/krb5/compatability_tests.py index b862f381bc5..72be7b34c4a 100755 --- a/python/samba/tests/krb5/compatability_tests.py +++ b/python/samba/tests/krb5/compatability_tests.py @@ -167,6 +167,28 @@ class SimpleKerberosTests(KDCBaseTest): self.verify_ticket(service_ticket, key, service_ticket=True, expect_ticket_checksum=False) + def test_full_signature(self): + # Ensure that a DC correctly issues tickets signed with its krbtgt key. + user_creds = self.get_client_creds() + target_creds = self.get_service_creds() + + krbtgt_creds = self.get_krbtgt_creds() + key = self.TicketDecryptionKey_from_creds(krbtgt_creds) + + # Get a TGT from the DC. + tgt = self.get_tgt(user_creds) + + # Ensure the PAC contains the expected checksums. + self.verify_ticket(tgt, key, service_ticket=False) + + # Get a service ticket from the DC. + service_ticket = self.get_service_ticket(tgt, target_creds) + + # Ensure the PAC contains the expected checksums. + self.verify_ticket(service_ticket, key, service_ticket=True, + expect_ticket_checksum=True, + expect_full_checksum=True) + def as_pre_auth_req(self, creds, etypes): user = creds.get_username() realm = creds.get_realm() diff --git a/python/samba/tests/krb5/kdc_base_test.py b/python/samba/tests/krb5/kdc_base_test.py index adf67fe7241..f5bfa68c433 100644 --- a/python/samba/tests/krb5/kdc_base_test.py +++ b/python/samba/tests/krb5/kdc_base_test.py @@ -1518,11 +1518,15 @@ class KDCBaseTest(RawKerberosTest): krbtgt_creds = self.get_krbtgt_creds() krbtgt_key = self.TicketDecryptionKey_from_creds(krbtgt_creds) + is_tgs_princ = self.is_tgs_principal(sname) expect_ticket_checksum = (self.tkt_sig_support - and not self.is_tgs_principal(sname)) + and not is_tgs_princ) + expect_full_checksum = (self.full_sig_support + and not is_tgs_princ) self.verify_ticket(service_ticket_creds, krbtgt_key, service_ticket=True, expect_pac=expect_pac, - expect_ticket_checksum=expect_ticket_checksum) + expect_ticket_checksum=expect_ticket_checksum, + expect_full_checksum=expect_full_checksum) self.tkt_cache[cache_key] = service_ticket_creds diff --git a/python/samba/tests/krb5/kdc_tgs_tests.py b/python/samba/tests/krb5/kdc_tgs_tests.py index 3643644cf0a..f3779099596 100755 --- a/python/samba/tests/krb5/kdc_tgs_tests.py +++ b/python/samba/tests/krb5/kdc_tgs_tests.py @@ -1594,6 +1594,43 @@ class KdcTgsTests(KdcTgsBaseTests): self._fast(tgt, creds, expected_error=KDC_ERR_TGT_REVOKED, expected_sname=self.get_krbtgt_sname()) + # Test making a TGS request with an RC4-encrypted TGT. + def test_tgs_rc4(self): + creds = self._get_creds() + tgt = self._get_tgt(creds, etype=kcrypto.Enctype.RC4) + self._run_tgs(tgt, expected_error=KDC_ERR_GENERIC) + + def test_renew_rc4(self): + creds = self._get_creds() + tgt = self._get_tgt(creds, renewable=True, etype=kcrypto.Enctype.RC4) + self._renew_tgt(tgt, expected_error=KDC_ERR_GENERIC, + expect_pac_attrs=True, + expect_pac_attrs_pac_request=True, + expect_requester_sid=True) + + def test_validate_rc4(self): + creds = self._get_creds() + tgt = self._get_tgt(creds, invalid=True, etype=kcrypto.Enctype.RC4) + self._validate_tgt(tgt, expected_error=KDC_ERR_GENERIC, + expect_pac_attrs=True, + expect_pac_attrs_pac_request=True, + expect_requester_sid=True) + + def test_s4u2self_rc4(self): + creds = self._get_creds() + tgt = self._get_tgt(creds, etype=kcrypto.Enctype.RC4) + self._s4u2self(tgt, creds, expected_error=KDC_ERR_GENERIC) + + def test_user2user_rc4(self): + creds = self._get_creds() + tgt = self._get_tgt(creds, etype=kcrypto.Enctype.RC4) + self._user2user(tgt, creds, expected_error=KDC_ERR_GENERIC) + + def test_fast_rc4(self): + creds = self._get_creds() + tgt = self._get_tgt(creds, etype=kcrypto.Enctype.RC4) + self._fast(tgt, creds, expected_error=KDC_ERR_GENERIC) + # Test user-to-user with incorrect service principal names. def test_user2user_matching_sname_host(self): creds = self._get_creds() @@ -2605,7 +2642,9 @@ class KdcTgsTests(KdcTgsBaseTests): can_modify_logon_info=True, can_modify_requester_sid=True, remove_pac_attrs=False, - remove_requester_sid=False): + remove_requester_sid=False, + etype=None, + cksum_etype=None): self.assertFalse(renewable and invalid) if remove_pac: @@ -2624,7 +2663,9 @@ class KdcTgsTests(KdcTgsBaseTests): can_modify_logon_info=can_modify_logon_info, can_modify_requester_sid=can_modify_requester_sid, remove_pac_attrs=remove_pac_attrs, - remove_requester_sid=remove_requester_sid) + remove_requester_sid=remove_requester_sid, + etype=None, + cksum_etype=cksum_etype) def _modify_tgt(self, tgt, @@ -2639,7 +2680,9 @@ class KdcTgsTests(KdcTgsBaseTests): can_modify_logon_info=True, can_modify_requester_sid=True, remove_pac_attrs=False, - remove_requester_sid=False): + remove_requester_sid=False, + etype=None, + cksum_etype=None): if from_rodc: krbtgt_creds = self.get_mock_rodc_krbtgt_creds() else: @@ -2678,13 +2721,19 @@ class KdcTgsTests(KdcTgsBaseTests): else: change_sid_fn = None - krbtgt_key = self.TicketDecryptionKey_from_creds(krbtgt_creds) + krbtgt_key = self.TicketDecryptionKey_from_creds(krbtgt_creds, + etype) if remove_pac: checksum_keys = None else: + if etype == cksum_etype: + cksum_key = krbtgt_key + else: + cksum_key = self.TicketDecryptionKey_from_creds(krbtgt_creds, + cksum_etype) checksum_keys = { - krb5pac.PAC_TYPE_KDC_CHECKSUM: krbtgt_key + krb5pac.PAC_TYPE_KDC_CHECKSUM: cksum_key } if renewable: diff --git a/python/samba/tests/krb5/raw_testcase.py b/python/samba/tests/krb5/raw_testcase.py index 7b9444d7425..b6752fb2080 100644 --- a/python/samba/tests/krb5/raw_testcase.py +++ b/python/samba/tests/krb5/raw_testcase.py @@ -547,7 +547,8 @@ class RawKerberosTest(TestCaseInTempDir): pac_checksum_types = {krb5pac.PAC_TYPE_SRV_CHECKSUM, krb5pac.PAC_TYPE_KDC_CHECKSUM, - krb5pac.PAC_TYPE_TICKET_CHECKSUM} + krb5pac.PAC_TYPE_TICKET_CHECKSUM, + krb5pac.PAC_TYPE_FULL_CHECKSUM} etypes_to_test = ( {"value": -1111, "name": "dummy", }, @@ -641,6 +642,12 @@ class RawKerberosTest(TestCaseInTempDir): tkt_sig_support = '0' cls.tkt_sig_support = bool(int(tkt_sig_support)) + full_sig_support = samba.tests.env_get_var_value('FULL_SIG_SUPPORT', + allow_missing=True) + if full_sig_support is None: + full_sig_support = '0' + cls.full_sig_support = bool(int(full_sig_support)) + gnutls_pbkdf2_support = samba.tests.env_get_var_value( 'GNUTLS_PBKDF2_SUPPORT', allow_missing=True) @@ -2416,6 +2423,7 @@ class RawKerberosTest(TestCaseInTempDir): unexpected_flags=None, ticket_decryption_key=None, expect_ticket_checksum=None, + expect_full_checksum=None, generate_fast_fn=None, generate_fast_armor_fn=None, generate_fast_padata_fn=None, @@ -2478,6 +2486,7 @@ class RawKerberosTest(TestCaseInTempDir): 'unexpected_flags': unexpected_flags, 'ticket_decryption_key': ticket_decryption_key, 'expect_ticket_checksum': expect_ticket_checksum, + 'expect_full_checksum': expect_full_checksum, 'generate_fast_fn': generate_fast_fn, 'generate_fast_armor_fn': generate_fast_armor_fn, 'generate_fast_padata_fn': generate_fast_padata_fn, @@ -2536,6 +2545,7 @@ class RawKerberosTest(TestCaseInTempDir): unexpected_flags=None, ticket_decryption_key=None, expect_ticket_checksum=None, + expect_full_checksum=None, generate_fast_fn=None, generate_fast_armor_fn=None, generate_fast_padata_fn=None, @@ -2599,6 +2609,7 @@ class RawKerberosTest(TestCaseInTempDir): 'unexpected_flags': unexpected_flags, 'ticket_decryption_key': ticket_decryption_key, 'expect_ticket_checksum': expect_ticket_checksum, + 'expect_full_checksum': expect_full_checksum, 'generate_fast_fn': generate_fast_fn, 'generate_fast_armor_fn': generate_fast_armor_fn, 'generate_fast_padata_fn': generate_fast_padata_fn, @@ -3075,7 +3086,8 @@ class RawKerberosTest(TestCaseInTempDir): self.check_pac_buffers(pac_data, kdc_exchange_dict) expect_ticket_checksum = kdc_exchange_dict['expect_ticket_checksum'] - if expect_ticket_checksum: + expect_full_checksum = kdc_exchange_dict['expect_full_checksum'] + if expect_ticket_checksum or expect_full_checksum: self.assertIsNotNone(ticket_decryption_key) if ticket_decryption_key is not None: @@ -3085,7 +3097,9 @@ class RawKerberosTest(TestCaseInTempDir): service_ticket=service_ticket, expect_pac=expect_pac, expect_ticket_checksum=expect_ticket_checksum - or self.tkt_sig_support) + or self.tkt_sig_support, + expect_full_checksum=expect_full_checksum + or self.full_sig_support) kdc_exchange_dict['rep_ticket_creds'] = ticket_creds @@ -3123,12 +3137,15 @@ class RawKerberosTest(TestCaseInTempDir): if rep_msg_type == KRB_TGS_REP: if not self.is_tgs_principal(expected_sname): expected_types.append(krb5pac.PAC_TYPE_TICKET_CHECKSUM) + expected_types.append(krb5pac.PAC_TYPE_FULL_CHECKSUM) require_strict = {krb5pac.PAC_TYPE_CLIENT_CLAIMS_INFO, krb5pac.PAC_TYPE_DEVICE_INFO, krb5pac.PAC_TYPE_DEVICE_CLAIMS_INFO} if not self.tkt_sig_support: require_strict.add(krb5pac.PAC_TYPE_TICKET_CHECKSUM) + if not self.full_sig_support: + require_strict.add(krb5pac.PAC_TYPE_FULL_CHECKSUM) expect_extra_pac_buffers = self.is_tgs(expected_sname) @@ -3839,7 +3856,8 @@ class RawKerberosTest(TestCaseInTempDir): def verify_ticket(self, ticket, krbtgt_keys, service_ticket, expect_pac=True, - expect_ticket_checksum=True): + expect_ticket_checksum=True, + expect_full_checksum=None): # Decrypt the ticket. key = ticket.decryption_key @@ -3884,6 +3902,8 @@ class RawKerberosTest(TestCaseInTempDir): checksums = {} + full_checksum_buffer = None + for pac_buffer, raw_pac_buffer in zip(pac.buffers, raw_pac.buffers): buffer_type = pac_buffer.type if buffer_type in self.pac_checksum_types: @@ -3898,7 +3918,9 @@ class RawKerberosTest(TestCaseInTempDir): checksums[buffer_type] = checksum, ctype - if buffer_type != krb5pac.PAC_TYPE_TICKET_CHECKSUM: + if buffer_type == krb5pac.PAC_TYPE_FULL_CHECKSUM: + full_checksum_buffer = raw_pac_buffer + elif buffer_type != krb5pac.PAC_TYPE_TICKET_CHECKSUM: # Zero the checksum field so that we can later verify the # checksums. The ticket checksum field is not zeroed. @@ -3912,6 +3934,17 @@ class RawKerberosTest(TestCaseInTempDir): # Re-encode the PAC. pac_data = ndr_pack(raw_pac) + if full_checksum_buffer is not None: + signature = ndr_unpack( + krb5pac.PAC_SIGNATURE_DATA, + full_checksum_buffer.info.remaining) + signature.signature = bytes(len(checksum)) + full_checksum_buffer.info.remaining = ndr_pack( + signature) + + # Re-encode the PAC. + full_pac_data = ndr_pack(raw_pac) + # Verify the signatures. server_checksum, server_ctype = checksums[ @@ -3940,6 +3973,7 @@ class RawKerberosTest(TestCaseInTempDir): if not service_ticket: self.assertNotIn(krb5pac.PAC_TYPE_TICKET_CHECKSUM, checksums) + self.assertNotIn(krb5pac.PAC_TYPE_FULL_CHECKSUM, checksums) else: ticket_checksum, ticket_ctype = checksums.get( krb5pac.PAC_TYPE_TICKET_CHECKSUM, @@ -3958,6 +3992,19 @@ class RawKerberosTest(TestCaseInTempDir): ticket_ctype, ticket_checksum) + full_checksum, full_ctype = checksums.get( + krb5pac.PAC_TYPE_FULL_CHECKSUM, + (None, None)) + if expect_full_checksum: + self.assertIsNotNone(full_checksum) + elif expect_full_checksum is False: + self.assertIsNone(full_checksum) + if full_checksum is not None: + krbtgt_key.verify_rodc_checksum(KU_NON_KERB_CKSUM_SALT, + full_pac_data, + full_ctype, + full_checksum) + def modified_ticket(self, ticket, *, new_ticket_key=None, @@ -4015,6 +4062,14 @@ class RawKerberosTest(TestCaseInTempDir): checksum_keys[krb5pac.PAC_TYPE_TICKET_CHECKSUM] = ( kdc_checksum_key) + if krb5pac.PAC_TYPE_FULL_CHECKSUM not in checksum_keys: + # If the full signature key is not present, fall back to the key + # used for the KDC signature. + kdc_checksum_key = checksum_keys.get(krb5pac.PAC_TYPE_KDC_CHECKSUM) + if kdc_checksum_key is not None: + checksum_keys[krb5pac.PAC_TYPE_FULL_CHECKSUM] = ( + kdc_checksum_key) + # Decrypt the ticket. enc_part = ticket.ticket['enc-part'] @@ -4167,6 +4222,19 @@ class RawKerberosTest(TestCaseInTempDir): # Add the new checksum buffers to the PAC. pac.buffers = pac_buffers + # Calculate the full checksum and insert it into the PAC. + full_checksum_buffer = checksum_buffers.get( + krb5pac.PAC_TYPE_FULL_CHECKSUM) + if full_checksum_buffer is not None: + full_checksum_key = checksum_keys[krb5pac.PAC_TYPE_FULL_CHECKSUM] + + pac_data = ndr_pack(pac) + full_checksum = full_checksum_key.make_checksum( + KU_NON_KERB_CKSUM_SALT, + pac_data) + + full_checksum_buffer.info.signature = full_checksum + # Calculate the server and KDC checksums and insert them into the PAC. server_checksum_buffer = checksum_buffers.get( diff --git a/python/samba/tests/krb5/rodc_tests.py b/python/samba/tests/krb5/rodc_tests.py index 83ee35d650a..3e0e2a7712e 100755 --- a/python/samba/tests/krb5/rodc_tests.py +++ b/python/samba/tests/krb5/rodc_tests.py @@ -65,7 +65,9 @@ class RodcKerberosTests(KDCBaseTest): to_rodc=True) # Ensure the PAC contains the expected checksums. - self.verify_ticket(service_ticket, rodc_key, service_ticket=True) + self.verify_ticket(service_ticket, rodc_key, service_ticket=True, + expect_ticket_checksum=True, + expect_full_checksum=True) if __name__ == "__main__": diff --git a/python/samba/tests/krb5/s4u_tests.py b/python/samba/tests/krb5/s4u_tests.py index 76e8bbe990e..dcdd67e2b64 100755 --- a/python/samba/tests/krb5/s4u_tests.py +++ b/python/samba/tests/krb5/s4u_tests.py @@ -1126,8 +1126,8 @@ class S4UKerberosTests(KDCBaseTest): def test_constrained_delegation_missing_service_checksum(self): # Present the service's ticket without the required checksums. - for checksum in filter(lambda x: x != krb5pac.PAC_TYPE_TICKET_CHECKSUM, - self.pac_checksum_types): + for checksum in (krb5pac.PAC_TYPE_SRV_CHECKSUM, + krb5pac.PAC_TYPE_KDC_CHECKSUM): with self.subTest(checksum=checksum): self._run_delegation_test( { @@ -1161,8 +1161,8 @@ class S4UKerberosTests(KDCBaseTest): def test_rbcd_missing_service_checksum(self): # Present the service's ticket without the required checksums. - for checksum in filter(lambda x: x != krb5pac.PAC_TYPE_TICKET_CHECKSUM, - self.pac_checksum_types): + for checksum in (krb5pac.PAC_TYPE_SRV_CHECKSUM, + krb5pac.PAC_TYPE_KDC_CHECKSUM): with self.subTest(checksum=checksum): self._run_delegation_test( { @@ -1351,6 +1351,33 @@ class S4UKerberosTests(KDCBaseTest): checksum=checksum, ctype=ctype) }) + def test_constrained_delegation_rc4_client_checksum(self): + # Present a user ticket with RC4 checksums. + expected_error_mode = (KDC_ERR_GENERIC, + KDC_ERR_INAPP_CKSUM) + + self._run_delegation_test( + { + 'expected_error_mode': expected_error_mode, + 'allow_delegation': True, + 'modify_client_tkt_fn': self.rc4_pac_checksums, + 'expect_edata': False, + }) + + def test_rbcd_rc4_client_checksum(self): + # Present a user ticket with RC4 checksums. + expected_error_mode = (KDC_ERR_GENERIC, + KDC_ERR_BADOPTION) + + self._run_delegation_test( + { + 'expected_error_mode': expected_error_mode, + 'expected_status': ntstatus.NT_STATUS_NOT_SUPPORTED, + 'allow_rbcd': True, + 'pac_options': '0001', # supports RBCD + 'modify_client_tkt_fn': self.rc4_pac_checksums, + }) + def remove_pac_checksum(self, ticket, checksum): checksum_keys = self.get_krbtgt_checksum_key() @@ -1392,6 +1419,7 @@ class S4UKerberosTests(KDCBaseTest): krb5pac.PAC_TYPE_SRV_CHECKSUM: server_key, krb5pac.PAC_TYPE_KDC_CHECKSUM: krbtgt_key, krb5pac.PAC_TYPE_TICKET_CHECKSUM: krbtgt_key, + krb5pac.PAC_TYPE_FULL_CHECKSUM: krbtgt_key, } # Make a copy of the existing key and change the ctype. @@ -1404,6 +1432,31 @@ class S4UKerberosTests(KDCBaseTest): checksum_keys=checksum_keys, include_checksums={checksum: True}) + def rc4_pac_checksums(self, ticket): + krbtgt_creds = self.get_krbtgt_creds() + rc4_krbtgt_key = self.TicketDecryptionKey_from_creds( + krbtgt_creds, etype=Enctype.RC4) + + server_key = ticket.decryption_key + + checksum_keys = { + krb5pac.PAC_TYPE_SRV_CHECKSUM: server_key, + krb5pac.PAC_TYPE_KDC_CHECKSUM: rc4_krbtgt_key, + krb5pac.PAC_TYPE_TICKET_CHECKSUM: rc4_krbtgt_key, + krb5pac.PAC_TYPE_FULL_CHECKSUM: rc4_krbtgt_key, + } + + include_checksums = { + krb5pac.PAC_TYPE_SRV_CHECKSUM: True, + krb5pac.PAC_TYPE_KDC_CHECKSUM: True, + krb5pac.PAC_TYPE_TICKET_CHECKSUM: True, + krb5pac.PAC_TYPE_FULL_CHECKSUM: True, + } + + return self.modified_ticket(ticket, + checksum_keys=checksum_keys, + include_checksums=include_checksums) + def add_delegation_info(self, ticket, services=None): def modify_pac_fn(pac): pac_buffers = pac.buffers |