!109 [sync] PR-108: fix CVE-2022-32743

From: @openeuler-sync-bot 
Reviewed-by: @seuzw 
Signed-off-by: @seuzw
This commit is contained in:
openeuler-ci-bot 2022-09-01 01:51:29 +00:00 committed by Gitee
commit c0626ee444
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
16 changed files with 2810 additions and 1 deletions

View File

@ -0,0 +1,815 @@
From d277700710dc118f61065ed9e16e08e76820b66a Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Wed, 1 Jun 2022 16:07:17 +1200
Subject: [PATCH 01/15] CVE-2022-32743 s4-acl: Add tests for validated
dNSHostName write
BUG: https://bugzilla.samba.org/show_bug.cgi?id=14833
Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
selftest/knownfail.d/validated-dns-host-name | 15 +
source4/dsdb/tests/python/acl.py | 757 +++++++++++++++++++++++++++
2 files changed, 772 insertions(+)
create mode 100644 selftest/knownfail.d/validated-dns-host-name
diff --git a/selftest/knownfail.d/validated-dns-host-name b/selftest/knownfail.d/validated-dns-host-name
new file mode 100644
index 0000000..ee51f44
--- /dev/null
+++ b/selftest/knownfail.d/validated-dns-host-name
@@ -0,0 +1,15 @@
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name\(
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_account_no_dollar\(
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_allowed_suffixes\(
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_case\(
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_dollar\(
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_empty_string\(
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_invalid\(
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_no_suffix\(
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_no_value\(
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_spn\(
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_spn_matching_account_name_new\(
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_spn_matching_account_name_original\(
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_wrong_prefix\(
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_wrong_suffix\(
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_spn_matching_dns_host_name_invalid\(
diff --git a/source4/dsdb/tests/python/acl.py b/source4/dsdb/tests/python/acl.py
index 0061d0c..6751934 100755
--- a/source4/dsdb/tests/python/acl.py
+++ b/source4/dsdb/tests/python/acl.py
@@ -300,6 +300,7 @@ class AclModifyTests(AclTests):
delete_force(self.ldb_admin, "CN=test_modify_group1,CN=Users," + self.base_dn)
delete_force(self.ldb_admin, "CN=test_modify_group2,CN=Users," + self.base_dn)
delete_force(self.ldb_admin, "CN=test_modify_group3,CN=Users," + self.base_dn)
+ delete_force(self.ldb_admin, "CN=test_mod_hostname,OU=test_modify_ou1," + self.base_dn)
delete_force(self.ldb_admin, "OU=test_modify_ou1," + self.base_dn)
delete_force(self.ldb_admin, self.get_user_dn(self.user_with_wp))
delete_force(self.ldb_admin, self.get_user_dn(self.user_with_sm))
@@ -651,6 +652,762 @@ Member: CN=test_modify_user2,CN=Users,""" + self.base_dn
else:
self.fail()
+ def test_modify_dns_host_name(self):
+ '''Test modifying dNSHostName with validated write'''
+
+ ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+ account_name = 'test_mod_hostname'
+ dn = f'CN={account_name},{ou_dn}'
+
+ self.ldb_admin.create_ou(ou_dn)
+
+ # Grant Validated Write.
+ mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+ f'{self.user_sid})')
+ self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+ # Create the account.
+ self.ldb_admin.add({
+ 'dn': dn,
+ 'objectClass': 'computer',
+ 'sAMAccountName': f'{account_name}$',
+ })
+
+ host_name = f'{account_name}.{self.ldb_user.domain_dns_name()}'
+
+ m = Message(Dn(self.ldb_user, dn))
+ m['dNSHostName'] = MessageElement(host_name,
+ FLAG_MOD_REPLACE,
+ 'dNSHostName')
+ try:
+ self.ldb_user.modify(m)
+ except LdbError:
+ self.fail()
+
+ def test_modify_dns_host_name_no_validated_write(self):
+ '''Test modifying dNSHostName without validated write'''
+
+ ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+ account_name = 'test_mod_hostname'
+ dn = f'CN={account_name},{ou_dn}'
+
+ self.ldb_admin.create_ou(ou_dn)
+
+ # Create the account.
+ self.ldb_admin.add({
+ 'dn': dn,
+ 'objectClass': 'computer',
+ 'sAMAccountName': f'{account_name}$',
+ })
+
+ host_name = f'{account_name}.{self.ldb_user.domain_dns_name()}'
+
+ m = Message(Dn(self.ldb_user, dn))
+ m['dNSHostName'] = MessageElement(host_name,
+ FLAG_MOD_REPLACE,
+ 'dNSHostName')
+ try:
+ self.ldb_user.modify(m)
+ except LdbError as err:
+ num, estr = err.args
+ self.assertEqual(ERR_INSUFFICIENT_ACCESS_RIGHTS, num)
+ else:
+ self.fail()
+
+ def test_modify_dns_host_name_invalid(self):
+ '''Test modifying dNSHostName to an invalid value'''
+
+ ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+ account_name = 'test_mod_hostname'
+ dn = f'CN={account_name},{ou_dn}'
+
+ self.ldb_admin.create_ou(ou_dn)
+
+ # Grant Validated Write.
+ mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+ f'{self.user_sid})')
+ self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+ # Create the account.
+ self.ldb_admin.add({
+ 'dn': dn,
+ 'objectClass': 'computer',
+ 'sAMAccountName': f'{account_name}$',
+ })
+
+ host_name = 'invalid'
+
+ m = Message(Dn(self.ldb_user, dn))
+ m['dNSHostName'] = MessageElement(host_name,
+ FLAG_MOD_REPLACE,
+ 'dNSHostName')
+ try:
+ self.ldb_user.modify(m)
+ except LdbError as err:
+ num, estr = err.args
+ self.assertEqual(ERR_CONSTRAINT_VIOLATION, num)
+ else:
+ self.fail()
+
+ def test_modify_dns_host_name_invalid_wp(self):
+ '''Test modifying dNSHostName to an invalid value when we have WP'''
+
+ ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+ account_name = 'test_mod_hostname'
+ dn = f'CN={account_name},{ou_dn}'
+
+ self.ldb_admin.create_ou(ou_dn)
+
+ # Grant Write Property.
+ mod = (f'(OA;CI;WP;{security.GUID_DRS_DNS_HOST_NAME};;'
+ f'{self.user_sid})')
+ self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+ # Create the account.
+ self.ldb_admin.add({
+ 'dn': dn,
+ 'objectClass': 'computer',
+ 'sAMAccountName': f'{account_name}$',
+ })
+
+ host_name = 'invalid'
+
+ m = Message(Dn(self.ldb_user, dn))
+ m['dNSHostName'] = MessageElement(host_name,
+ FLAG_MOD_REPLACE,
+ 'dNSHostName')
+ try:
+ self.ldb_user.modify(m)
+ except LdbError:
+ self.fail()
+
+ def test_modify_dns_host_name_invalid_non_computer(self):
+ '''Test modifying dNSHostName to an invalid value on a non-computer'''
+
+ ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+ account_name = 'test_mod_hostname'
+ dn = f'CN={account_name},{ou_dn}'
+
+ self.ldb_admin.create_ou(ou_dn)
+
+ # Grant Validated Write.
+ mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+ f'{self.user_sid})')
+ self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+ # Create the account.
+ self.ldb_admin.add({
+ 'dn': dn,
+ 'objectClass': 'user',
+ 'sAMAccountName': f'{account_name}',
+ })
+
+ host_name = 'invalid'
+
+ m = Message(Dn(self.ldb_user, dn))
+ m['dNSHostName'] = MessageElement(host_name,
+ FLAG_MOD_REPLACE,
+ 'dNSHostName')
+ try:
+ self.ldb_user.modify(m)
+ except LdbError as err:
+ num, estr = err.args
+ self.assertEqual(ERR_INSUFFICIENT_ACCESS_RIGHTS, num)
+ else:
+ self.fail()
+
+ def test_modify_dns_host_name_no_value(self):
+ '''Test modifying dNSHostName with validated write with no value'''
+
+ ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+ account_name = 'test_mod_hostname'
+ dn = f'CN={account_name},{ou_dn}'
+
+ self.ldb_admin.create_ou(ou_dn)
+
+ # Grant Validated Write.
+ mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+ f'{self.user_sid})')
+ self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+ # Create the account.
+ self.ldb_admin.add({
+ 'dn': dn,
+ 'objectClass': 'computer',
+ 'sAMAccountName': f'{account_name}$',
+ })
+
+ m = Message(Dn(self.ldb_user, dn))
+ m['dNSHostName'] = MessageElement([],
+ FLAG_MOD_REPLACE,
+ 'dNSHostName')
+ try:
+ self.ldb_user.modify(m)
+ except LdbError as err:
+ num, estr = err.args
+ self.assertEqual(ERR_OPERATIONS_ERROR, num)
+ else:
+ # Windows accepts this.
+ pass
+
+ def test_modify_dns_host_name_empty_string(self):
+ '''Test modifying dNSHostName with validated write of an empty string'''
+
+ ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+ account_name = 'test_mod_hostname'
+ dn = f'CN={account_name},{ou_dn}'
+
+ self.ldb_admin.create_ou(ou_dn)
+
+ # Grant Validated Write.
+ mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+ f'{self.user_sid})')
+ self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+ # Create the account.
+ self.ldb_admin.add({
+ 'dn': dn,
+ 'objectClass': 'computer',
+ 'sAMAccountName': f'{account_name}$',
+ })
+
+ m = Message(Dn(self.ldb_user, dn))
+ m['dNSHostName'] = MessageElement('\0',
+ FLAG_MOD_REPLACE,
+ 'dNSHostName')
+ try:
+ self.ldb_user.modify(m)
+ except LdbError as err:
+ num, estr = err.args
+ self.assertEqual(ERR_CONSTRAINT_VIOLATION, num)
+ else:
+ self.fail()
+
+ def test_modify_dns_host_name_dollar(self):
+ '''Test modifying dNSHostName with validated write of a value including a dollar'''
+
+ ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+ account_name = 'test_mod_hostname'
+ dn = f'CN={account_name},{ou_dn}'
+
+ self.ldb_admin.create_ou(ou_dn)
+
+ # Grant Validated Write.
+ mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+ f'{self.user_sid})')
+ self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+ # Create the account.
+ self.ldb_admin.add({
+ 'dn': dn,
+ 'objectClass': 'computer',
+ 'sAMAccountName': f'{account_name}$',
+ })
+
+ host_name = f'{account_name}$.{self.ldb_user.domain_dns_name()}'
+
+ m = Message(Dn(self.ldb_user, dn))
+ m['dNSHostName'] = MessageElement(host_name,
+ FLAG_MOD_REPLACE,
+ 'dNSHostName')
+ try:
+ self.ldb_user.modify(m)
+ except LdbError as err:
+ num, estr = err.args
+ self.assertEqual(ERR_CONSTRAINT_VIOLATION, num)
+ else:
+ self.fail()
+
+ def test_modify_dns_host_name_account_no_dollar(self):
+ '''Test modifying dNSHostName with validated write with no dollar in sAMAccountName'''
+
+ ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+ account_name = 'test_mod_hostname'
+ dn = f'CN={account_name},{ou_dn}'
+
+ self.ldb_admin.create_ou(ou_dn)
+
+ # Grant Validated Write.
+ mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+ f'{self.user_sid})')
+ self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+ # Create the account.
+ self.ldb_admin.add({
+ 'dn': dn,
+ 'objectClass': 'computer',
+ 'sAMAccountName': f'{account_name}',
+ })
+
+ host_name = f'{account_name}.{self.ldb_user.domain_dns_name()}'
+
+ m = Message(Dn(self.ldb_user, dn))
+ m['dNSHostName'] = MessageElement(host_name,
+ FLAG_MOD_REPLACE,
+ 'dNSHostName')
+ try:
+ self.ldb_user.modify(m)
+ except LdbError:
+ self.fail()
+
+ def test_modify_dns_host_name_no_suffix(self):
+ '''Test modifying dNSHostName with validated write of a value missing the suffix'''
+
+ ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+ account_name = 'test_mod_hostname'
+ dn = f'CN={account_name},{ou_dn}'
+
+ self.ldb_admin.create_ou(ou_dn)
+
+ # Grant Validated Write.
+ mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+ f'{self.user_sid})')
+ self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+ # Create the account.
+ self.ldb_admin.add({
+ 'dn': dn,
+ 'objectClass': 'computer',
+ 'sAMAccountName': f'{account_name}$',
+ })
+
+ host_name = f'{account_name}'
+
+ m = Message(Dn(self.ldb_user, dn))
+ m['dNSHostName'] = MessageElement(host_name,
+ FLAG_MOD_REPLACE,
+ 'dNSHostName')
+ try:
+ self.ldb_user.modify(m)
+ except LdbError as err:
+ num, estr = err.args
+ self.assertEqual(ERR_CONSTRAINT_VIOLATION, num)
+ else:
+ self.fail()
+
+ def test_modify_dns_host_name_wrong_prefix(self):
+ '''Test modifying dNSHostName with validated write of a value with the wrong prefix'''
+
+ ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+ account_name = 'test_mod_hostname'
+ dn = f'CN={account_name},{ou_dn}'
+
+ self.ldb_admin.create_ou(ou_dn)
+
+ # Grant Validated Write.
+ mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+ f'{self.user_sid})')
+ self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+ # Create the account.
+ self.ldb_admin.add({
+ 'dn': dn,
+ 'objectClass': 'computer',
+ 'sAMAccountName': f'{account_name}$',
+ })
+
+ host_name = f'invalid.{self.ldb_user.domain_dns_name()}'
+
+ m = Message(Dn(self.ldb_user, dn))
+ m['dNSHostName'] = MessageElement(host_name,
+ FLAG_MOD_REPLACE,
+ 'dNSHostName')
+ try:
+ self.ldb_user.modify(m)
+ except LdbError as err:
+ num, estr = err.args
+ self.assertEqual(ERR_CONSTRAINT_VIOLATION, num)
+ else:
+ self.fail()
+
+ def test_modify_dns_host_name_wrong_suffix(self):
+ '''Test modifying dNSHostName with validated write of a value with the wrong suffix'''
+
+ ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+ account_name = 'test_mod_hostname'
+ dn = f'CN={account_name},{ou_dn}'
+
+ self.ldb_admin.create_ou(ou_dn)
+
+ # Grant Validated Write.
+ mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+ f'{self.user_sid})')
+ self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+ # Create the account.
+ self.ldb_admin.add({
+ 'dn': dn,
+ 'objectClass': 'computer',
+ 'sAMAccountName': f'{account_name}$',
+ })
+
+ host_name = f'{account_name}.invalid.example.com'
+
+ m = Message(Dn(self.ldb_user, dn))
+ m['dNSHostName'] = MessageElement(host_name,
+ FLAG_MOD_REPLACE,
+ 'dNSHostName')
+ try:
+ self.ldb_user.modify(m)
+ except LdbError as err:
+ num, estr = err.args
+ self.assertEqual(ERR_CONSTRAINT_VIOLATION, num)
+ else:
+ self.fail()
+
+ def test_modify_dns_host_name_case(self):
+ '''Test modifying dNSHostName with validated write of a value with irregular case'''
+
+ ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+ account_name = 'test_mod_hostname'
+ dn = f'CN={account_name},{ou_dn}'
+
+ self.ldb_admin.create_ou(ou_dn)
+
+ # Grant Validated Write.
+ mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+ f'{self.user_sid})')
+ self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+ # Create the account.
+ self.ldb_admin.add({
+ 'dn': dn,
+ 'objectClass': 'computer',
+ 'sAMAccountName': f'{account_name}$',
+ })
+
+ host_name = f'{account_name}.{self.ldb_user.domain_dns_name()}'
+ host_name = host_name.capitalize()
+
+ m = Message(Dn(self.ldb_user, dn))
+ m['dNSHostName'] = MessageElement(host_name,
+ FLAG_MOD_REPLACE,
+ 'dNSHostName')
+ try:
+ self.ldb_user.modify(m)
+ except LdbError:
+ self.fail()
+
+ def test_modify_dns_host_name_allowed_suffixes(self):
+ '''Test modifying dNSHostName with validated write and an allowed suffix'''
+
+ allowed_suffix = 'suffix.that.is.allowed'
+
+ # Add the allowed suffix.
+
+ res = self.ldb_admin.search(self.base_dn,
+ scope=SCOPE_BASE,
+ attrs=['msDS-AllowedDNSSuffixes'])
+ self.assertEqual(1, len(res))
+ old_allowed_suffixes = res[0].get('msDS-AllowedDNSSuffixes')
+
+ def modify_allowed_suffixes(suffixes):
+ if suffixes is None:
+ suffixes = []
+ flag = FLAG_MOD_DELETE
+ else:
+ flag = FLAG_MOD_REPLACE
+
+ m = Message(Dn(self.ldb_admin, self.base_dn))
+ m['msDS-AllowedDNSSuffixes'] = MessageElement(
+ suffixes,
+ flag,
+ 'msDS-AllowedDNSSuffixes')
+ self.ldb_admin.modify(m)
+
+ self.addCleanup(modify_allowed_suffixes, old_allowed_suffixes)
+
+ if old_allowed_suffixes is None:
+ allowed_suffixes = []
+ else:
+ allowed_suffixes = list(old_allowed_suffixes)
+
+ if (allowed_suffix not in allowed_suffixes and
+ allowed_suffix.encode('utf-8') not in allowed_suffixes):
+ allowed_suffixes.append(allowed_suffix)
+
+ modify_allowed_suffixes(allowed_suffixes)
+
+ # Create the account and run the test.
+
+ ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+ account_name = 'test_mod_hostname'
+ dn = f'CN={account_name},{ou_dn}'
+
+ self.ldb_admin.create_ou(ou_dn)
+
+ # Grant Validated Write.
+ mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+ f'{self.user_sid})')
+ self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+ # Create the account.
+ self.ldb_admin.add({
+ 'dn': dn,
+ 'objectClass': 'computer',
+ 'sAMAccountName': f'{account_name}$',
+ })
+
+ host_name = f'{account_name}.{allowed_suffix}'
+
+ m = Message(Dn(self.ldb_user, dn))
+ m['dNSHostName'] = MessageElement(host_name,
+ FLAG_MOD_REPLACE,
+ 'dNSHostName')
+ try:
+ self.ldb_user.modify(m)
+ except LdbError:
+ self.fail()
+
+ def test_modify_dns_host_name_spn(self):
+ '''Test modifying dNSHostName and SPN with validated write'''
+
+ ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+ account_name = 'test_mod_hostname'
+ dn = f'CN={account_name},{ou_dn}'
+
+ self.ldb_admin.create_ou(ou_dn)
+
+ # Grant Validated Write.
+ mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+ f'{self.user_sid})')
+ self.sd_utils.dacl_add_ace(ou_dn, mod)
+ mod = (f'(OA;CI;SW;{security.GUID_DRS_VALIDATE_SPN};;'
+ f'{self.user_sid})')
+ self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+ # Create the account.
+ self.ldb_admin.add({
+ 'dn': dn,
+ 'objectClass': 'computer',
+ 'sAMAccountName': f'{account_name}$',
+ })
+
+ host_name = f'{account_name}.{self.ldb_user.domain_dns_name()}'
+ spn = f'host/{host_name}'
+
+ m = Message(Dn(self.ldb_user, dn))
+ m['0'] = MessageElement(host_name,
+ FLAG_MOD_REPLACE,
+ 'dNSHostName')
+ m['1'] = MessageElement(spn,
+ FLAG_MOD_ADD,
+ 'servicePrincipalName')
+ try:
+ self.ldb_user.modify(m)
+ except LdbError:
+ self.fail()
+
+ def test_modify_spn_matching_dns_host_name_invalid(self):
+ '''Test modifying SPN with validated write, matching a valid dNSHostName '''
+
+ ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+ account_name = 'test_mod_hostname'
+ dn = f'CN={account_name},{ou_dn}'
+
+ self.ldb_admin.create_ou(ou_dn)
+
+ # Grant Write Property.
+ mod = (f'(OA;CI;WP;{security.GUID_DRS_DNS_HOST_NAME};;'
+ f'{self.user_sid})')
+ self.sd_utils.dacl_add_ace(ou_dn, mod)
+ # Grant Validated Write.
+ mod = (f'(OA;CI;SW;{security.GUID_DRS_VALIDATE_SPN};;'
+ f'{self.user_sid})')
+ self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+ # Create the account.
+ self.ldb_admin.add({
+ 'dn': dn,
+ 'objectClass': 'computer',
+ 'sAMAccountName': f'{account_name}$',
+ })
+
+ invalid_host_name = 'invalid'
+
+ host_name = f'{account_name}.{self.ldb_user.domain_dns_name()}'
+ spn = f'host/{host_name}'
+
+ m = Message(Dn(self.ldb_user, dn))
+ m['0'] = MessageElement(invalid_host_name,
+ FLAG_MOD_REPLACE,
+ 'dNSHostName')
+ m['1'] = MessageElement(spn,
+ FLAG_MOD_ADD,
+ 'servicePrincipalName')
+ m['2'] = MessageElement(host_name,
+ FLAG_MOD_REPLACE,
+ 'dNSHostName')
+ try:
+ self.ldb_user.modify(m)
+ except LdbError:
+ self.fail()
+
+ def test_modify_spn_matching_dns_host_name_original(self):
+ '''Test modifying SPN with validated write, matching the original dNSHostName '''
+
+ ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+ account_name = 'test_mod_hostname'
+ dn = f'CN={account_name},{ou_dn}'
+
+ self.ldb_admin.create_ou(ou_dn)
+
+ # Grant Validated Write.
+ mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+ f'{self.user_sid})')
+ self.sd_utils.dacl_add_ace(ou_dn, mod)
+ mod = (f'(OA;CI;SW;{security.GUID_DRS_VALIDATE_SPN};;'
+ f'{self.user_sid})')
+ self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+ original_host_name = 'invalid_host_name'
+ original_spn = 'host/{original_host_name}'
+
+ # Create the account.
+ self.ldb_admin.add({
+ 'dn': dn,
+ 'objectClass': 'computer',
+ 'sAMAccountName': f'{account_name}$',
+ 'dNSHostName': original_host_name,
+ })
+
+ host_name = f'{account_name}.{self.ldb_user.domain_dns_name()}'
+
+ m = Message(Dn(self.ldb_user, dn))
+ m['0'] = MessageElement(original_spn,
+ FLAG_MOD_ADD,
+ 'servicePrincipalName')
+ m['1'] = MessageElement(host_name,
+ FLAG_MOD_REPLACE,
+ 'dNSHostName')
+ try:
+ self.ldb_user.modify(m)
+ except LdbError as err:
+ num, estr = err.args
+ self.assertEqual(ERR_CONSTRAINT_VIOLATION, num)
+ else:
+ self.fail()
+
+ def test_modify_dns_host_name_spn_matching_account_name_original(self):
+ '''Test modifying dNSHostName and SPN with validated write, matching the original sAMAccountName'''
+
+ ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+ account_name = 'test_mod_hostname'
+ dn = f'CN={account_name},{ou_dn}'
+
+ self.ldb_admin.create_ou(ou_dn)
+
+ sam_account_name = '3e0abfd0-126a-11d0-a060-00aa006c33ed'
+
+ # Grant Write Property.
+ mod = (f'(OA;CI;WP;{sam_account_name};;'
+ f'{self.user_sid})')
+ self.sd_utils.dacl_add_ace(ou_dn, mod)
+ # Grant Validated Write.
+ mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+ f'{self.user_sid})')
+ self.sd_utils.dacl_add_ace(ou_dn, mod)
+ mod = (f'(OA;CI;SW;{security.GUID_DRS_VALIDATE_SPN};;'
+ f'{self.user_sid})')
+ self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+ # Create the account.
+ self.ldb_admin.add({
+ 'dn': dn,
+ 'objectClass': 'computer',
+ 'sAMAccountName': f'{account_name}$',
+ })
+
+ new_account_name = 'test_mod_hostname2'
+ host_name = f'{account_name}.{self.ldb_user.domain_dns_name()}'
+ spn = f'host/{host_name}'
+
+ m = Message(Dn(self.ldb_user, dn))
+ m['0'] = MessageElement(host_name,
+ FLAG_MOD_REPLACE,
+ 'dNSHostName')
+ m['1'] = MessageElement(spn,
+ FLAG_MOD_ADD,
+ 'servicePrincipalName')
+ m['2'] = MessageElement(f'{new_account_name}$',
+ FLAG_MOD_REPLACE,
+ 'sAMAccountName')
+ try:
+ self.ldb_user.modify(m)
+ except LdbError as err:
+ num, estr = err.args
+ self.assertEqual(ERR_CONSTRAINT_VIOLATION, num)
+ else:
+ self.fail()
+
+ def test_modify_dns_host_name_spn_matching_account_name_new(self):
+ '''Test modifying dNSHostName and SPN with validated write, matching the new sAMAccountName'''
+
+ ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+ account_name = 'test_mod_hostname'
+ dn = f'CN={account_name},{ou_dn}'
+
+ self.ldb_admin.create_ou(ou_dn)
+
+ sam_account_name = '3e0abfd0-126a-11d0-a060-00aa006c33ed'
+
+ # Grant Write Property.
+ mod = (f'(OA;CI;WP;{sam_account_name};;'
+ f'{self.user_sid})')
+ self.sd_utils.dacl_add_ace(ou_dn, mod)
+ # Grant Validated Write.
+ mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+ f'{self.user_sid})')
+ self.sd_utils.dacl_add_ace(ou_dn, mod)
+ mod = (f'(OA;CI;SW;{security.GUID_DRS_VALIDATE_SPN};;'
+ f'{self.user_sid})')
+ self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+ # Create the account.
+ self.ldb_admin.add({
+ 'dn': dn,
+ 'objectClass': 'computer',
+ 'sAMAccountName': f'{account_name}$',
+ })
+
+ new_account_name = 'test_mod_hostname2'
+ new_host_name = f'{new_account_name}.{self.ldb_user.domain_dns_name()}'
+ new_spn = f'host/{new_host_name}'
+
+ m = Message(Dn(self.ldb_user, dn))
+ m['0'] = MessageElement(new_spn,
+ FLAG_MOD_ADD,
+ 'servicePrincipalName')
+ m['1'] = MessageElement(new_host_name,
+ FLAG_MOD_REPLACE,
+ 'dNSHostName')
+ m['2'] = MessageElement(f'{new_account_name}$',
+ FLAG_MOD_REPLACE,
+ 'sAMAccountName')
+ try:
+ self.ldb_user.modify(m)
+ except LdbError:
+ self.fail()
+
# enable these when we have search implemented
--
1.8.3.1

View File

@ -0,0 +1,346 @@
From b41691d0e546795bda994d94091b8e0a03ab96d6 Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Tue, 7 Jun 2022 17:35:35 +1200
Subject: [PATCH 02/15] CVE-2022-32743 tests/py_credentials: Add tests for
setting dNSHostName with LogonGetDomainInfo()
Test that the value is properly validated, and that it can be set
regardless of rights on the account.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=14833
Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
python/samba/tests/py_credentials.py | 281 +++++++++++++++++++++++++++-
selftest/knownfail.d/netlogon-dns-host-name | 2 +
2 files changed, 281 insertions(+), 2 deletions(-)
create mode 100644 selftest/knownfail.d/netlogon-dns-host-name
diff --git a/python/samba/tests/py_credentials.py b/python/samba/tests/py_credentials.py
index ecb8271..0c442b8 100644
--- a/python/samba/tests/py_credentials.py
+++ b/python/samba/tests/py_credentials.py
@@ -18,6 +18,8 @@
from samba.tests import TestCase, delete_force
import os
+import ldb
+
import samba
from samba.auth import system_session
from samba.credentials import (
@@ -25,7 +27,7 @@ from samba.credentials import (
CLI_CRED_NTLMv2_AUTH,
CLI_CRED_NTLM_AUTH,
DONT_USE_KERBEROS)
-from samba.dcerpc import netlogon, ntlmssp, srvsvc
+from samba.dcerpc import lsa, netlogon, ntlmssp, security, srvsvc
from samba.dcerpc.netlogon import (
netr_Authenticator,
netr_WorkstationInformation,
@@ -36,10 +38,11 @@ from samba.dsdb import (
UF_WORKSTATION_TRUST_ACCOUNT,
UF_PASSWD_NOTREQD,
UF_NORMAL_ACCOUNT)
-from samba.ndr import ndr_pack
+from samba.ndr import ndr_pack, ndr_unpack
from samba.samdb import SamDB
from samba import NTSTATUSError, ntstatus
from samba.common import get_string
+from samba.sd_utils import SDUtils
import ctypes
@@ -105,6 +108,280 @@ class PyCredentialsTests(TestCase):
(authenticator, subsequent) = self.get_authenticator(c)
self.do_NetrLogonGetDomainInfo(c, authenticator, subsequent)
+ # Test using LogonGetDomainInfo to update dNSHostName to an allowed value.
+ def test_set_dns_hostname_valid(self):
+ c = self.get_netlogon_connection()
+ authenticator, subsequent = self.get_authenticator(c)
+
+ domain_hostname = self.ldb.domain_dns_name()
+
+ new_dns_hostname = f'{self.machine_name}.{domain_hostname}'
+ new_dns_hostname = new_dns_hostname.encode('utf-8')
+
+ query = netr_WorkstationInformation()
+ query.os_name = lsa.String('some OS')
+ query.dns_hostname = new_dns_hostname
+
+ c.netr_LogonGetDomainInfo(
+ server_name=self.server,
+ computer_name=self.user_creds.get_workstation(),
+ credential=authenticator,
+ return_authenticator=subsequent,
+ level=1,
+ query=query)
+
+ # Check the result.
+
+ res = self.ldb.search(self.machine_dn,
+ scope=ldb.SCOPE_BASE,
+ attrs=['dNSHostName'])
+ self.assertEqual(1, len(res))
+
+ got_dns_hostname = res[0].get('dNSHostName', idx=0)
+ self.assertEqual(new_dns_hostname, got_dns_hostname)
+
+ # Test using LogonGetDomainInfo to update dNSHostName to an allowed value,
+ # when we are denied the right to do so.
+ def test_set_dns_hostname_valid_denied(self):
+ c = self.get_netlogon_connection()
+ authenticator, subsequent = self.get_authenticator(c)
+
+ res = self.ldb.search(self.machine_dn,
+ scope=ldb.SCOPE_BASE,
+ attrs=['objectSid'])
+ self.assertEqual(1, len(res))
+
+ machine_sid = ndr_unpack(security.dom_sid,
+ res[0].get('objectSid', idx=0))
+
+ sd_utils = SDUtils(self.ldb)
+
+ # Deny Validated Write and Write Property.
+ mod = (f'(OD;;SWWP;{security.GUID_DRS_DNS_HOST_NAME};;'
+ f'{machine_sid})')
+ sd_utils.dacl_add_ace(self.machine_dn, mod)
+
+ domain_hostname = self.ldb.domain_dns_name()
+
+ new_dns_hostname = f'{self.machine_name}.{domain_hostname}'
+ new_dns_hostname = new_dns_hostname.encode('utf-8')
+
+ query = netr_WorkstationInformation()
+ query.os_name = lsa.String('some OS')
+ query.dns_hostname = new_dns_hostname
+
+ c.netr_LogonGetDomainInfo(
+ server_name=self.server,
+ computer_name=self.user_creds.get_workstation(),
+ credential=authenticator,
+ return_authenticator=subsequent,
+ level=1,
+ query=query)
+
+ # Check the result.
+
+ res = self.ldb.search(self.machine_dn,
+ scope=ldb.SCOPE_BASE,
+ attrs=['dNSHostName'])
+ self.assertEqual(1, len(res))
+
+ got_dns_hostname = res[0].get('dNSHostName', idx=0)
+ self.assertEqual(new_dns_hostname, got_dns_hostname)
+
+ # Ensure we can't use LogonGetDomainInfo to update dNSHostName to an
+ # invalid value, even with Validated Write.
+ def test_set_dns_hostname_invalid_validated_write(self):
+ c = self.get_netlogon_connection()
+ authenticator, subsequent = self.get_authenticator(c)
+
+ res = self.ldb.search(self.machine_dn,
+ scope=ldb.SCOPE_BASE,
+ attrs=['objectSid'])
+ self.assertEqual(1, len(res))
+
+ machine_sid = ndr_unpack(security.dom_sid,
+ res[0].get('objectSid', idx=0))
+
+ sd_utils = SDUtils(self.ldb)
+
+ # Grant Validated Write.
+ mod = (f'(OA;;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+ f'{machine_sid})')
+ sd_utils.dacl_add_ace(self.machine_dn, mod)
+
+ new_dns_hostname = b'invalid'
+
+ query = netr_WorkstationInformation()
+ query.os_name = lsa.String('some OS')
+ query.dns_hostname = new_dns_hostname
+
+ c.netr_LogonGetDomainInfo(
+ server_name=self.server,
+ computer_name=self.user_creds.get_workstation(),
+ credential=authenticator,
+ return_authenticator=subsequent,
+ level=1,
+ query=query)
+
+ # Check the result.
+
+ res = self.ldb.search(self.machine_dn,
+ scope=ldb.SCOPE_BASE,
+ attrs=['dNSHostName'])
+ self.assertEqual(1, len(res))
+
+ got_dns_hostname = res[0].get('dNSHostName', idx=0)
+ self.assertIsNone(got_dns_hostname)
+
+ # Ensure we can't use LogonGetDomainInfo to update dNSHostName to an
+ # invalid value, even with Write Property.
+ def test_set_dns_hostname_invalid_write_property(self):
+ c = self.get_netlogon_connection()
+ authenticator, subsequent = self.get_authenticator(c)
+
+ res = self.ldb.search(self.machine_dn,
+ scope=ldb.SCOPE_BASE,
+ attrs=['objectSid'])
+ self.assertEqual(1, len(res))
+
+ machine_sid = ndr_unpack(security.dom_sid,
+ res[0].get('objectSid', idx=0))
+
+ sd_utils = SDUtils(self.ldb)
+
+ # Grant Write Property.
+ mod = (f'(OA;;WP;{security.GUID_DRS_DNS_HOST_NAME};;'
+ f'{machine_sid})')
+ sd_utils.dacl_add_ace(self.machine_dn, mod)
+
+ new_dns_hostname = b'invalid'
+
+ query = netr_WorkstationInformation()
+ query.os_name = lsa.String('some OS')
+ query.dns_hostname = new_dns_hostname
+
+ c.netr_LogonGetDomainInfo(
+ server_name=self.server,
+ computer_name=self.user_creds.get_workstation(),
+ credential=authenticator,
+ return_authenticator=subsequent,
+ level=1,
+ query=query)
+
+ # Check the result.
+
+ res = self.ldb.search(self.machine_dn,
+ scope=ldb.SCOPE_BASE,
+ attrs=['dNSHostName'])
+ self.assertEqual(1, len(res))
+
+ got_dns_hostname = res[0].get('dNSHostName', idx=0)
+ self.assertIsNone(got_dns_hostname)
+
+ # Show we can't use LogonGetDomainInfo to set the dNSHostName to just the
+ # machine name.
+ def test_set_dns_hostname_to_machine_name(self):
+ c = self.get_netlogon_connection()
+ authenticator, subsequent = self.get_authenticator(c)
+
+ new_dns_hostname = self.machine_name.encode('utf-8')
+
+ query = netr_WorkstationInformation()
+ query.os_name = lsa.String('some OS')
+ query.dns_hostname = new_dns_hostname
+
+ c.netr_LogonGetDomainInfo(
+ server_name=self.server,
+ computer_name=self.user_creds.get_workstation(),
+ credential=authenticator,
+ return_authenticator=subsequent,
+ level=1,
+ query=query)
+
+ # Check the result.
+
+ res = self.ldb.search(self.machine_dn,
+ scope=ldb.SCOPE_BASE,
+ attrs=['dNSHostName'])
+ self.assertEqual(1, len(res))
+
+ got_dns_hostname = res[0].get('dNSHostName', idx=0)
+ self.assertIsNone(got_dns_hostname)
+
+ # Show we can't use LogonGetDomainInfo to set dNSHostName with an invalid
+ # suffix.
+ def test_set_dns_hostname_invalid_suffix(self):
+ c = self.get_netlogon_connection()
+ authenticator, subsequent = self.get_authenticator(c)
+
+ domain_hostname = self.ldb.domain_dns_name()
+
+ new_dns_hostname = f'{self.machine_name}.foo.{domain_hostname}'
+ new_dns_hostname = new_dns_hostname.encode('utf-8')
+
+ query = netr_WorkstationInformation()
+ query.os_name = lsa.String('some OS')
+ query.dns_hostname = new_dns_hostname
+
+ c.netr_LogonGetDomainInfo(
+ server_name=self.server,
+ computer_name=self.user_creds.get_workstation(),
+ credential=authenticator,
+ return_authenticator=subsequent,
+ level=1,
+ query=query)
+
+ # Check the result.
+
+ res = self.ldb.search(self.machine_dn,
+ scope=ldb.SCOPE_BASE,
+ attrs=['dNSHostName'])
+ self.assertEqual(1, len(res))
+
+ got_dns_hostname = res[0].get('dNSHostName', idx=0)
+ self.assertIsNone(got_dns_hostname)
+
+ # Test that setting the HANDLES_SPN_UPDATE flag inhibits the dNSHostName
+ # update, but other attributes are still updated.
+ def test_set_dns_hostname_with_flag(self):
+ c = self.get_netlogon_connection()
+ authenticator, subsequent = self.get_authenticator(c)
+
+ domain_hostname = self.ldb.domain_dns_name()
+
+ new_dns_hostname = f'{self.machine_name}.{domain_hostname}'
+ new_dns_hostname = new_dns_hostname.encode('utf-8')
+
+ operating_system = 'some OS'
+
+ query = netr_WorkstationInformation()
+ query.os_name = lsa.String(operating_system)
+
+ query.dns_hostname = new_dns_hostname
+ query.workstation_flags = netlogon.NETR_WS_FLAG_HANDLES_SPN_UPDATE
+
+ c.netr_LogonGetDomainInfo(
+ server_name=self.server,
+ computer_name=self.user_creds.get_workstation(),
+ credential=authenticator,
+ return_authenticator=subsequent,
+ level=1,
+ query=query)
+
+ # Check the result.
+
+ res = self.ldb.search(self.machine_dn,
+ scope=ldb.SCOPE_BASE,
+ attrs=['dNSHostName',
+ 'operatingSystem'])
+ self.assertEqual(1, len(res))
+
+ got_dns_hostname = res[0].get('dNSHostName', idx=0)
+ self.assertIsNone(got_dns_hostname)
+
+ got_os = res[0].get('operatingSystem', idx=0)
+ self.assertEqual(operating_system.encode('utf-8'), got_os)
+
def test_SamLogonEx(self):
c = self.get_netlogon_connection()
diff --git a/selftest/knownfail.d/netlogon-dns-host-name b/selftest/knownfail.d/netlogon-dns-host-name
new file mode 100644
index 0000000..2d0a0ec
--- /dev/null
+++ b/selftest/knownfail.d/netlogon-dns-host-name
@@ -0,0 +1,2 @@
+^samba.tests.py_credentials.samba.tests.py_credentials.PyCredentialsTests.test_set_dns_hostname_invalid_suffix\(
+^samba.tests.py_credentials.samba.tests.py_credentials.PyCredentialsTests.test_set_dns_hostname_with_flag\(
--
1.8.3.1

View File

@ -0,0 +1,65 @@
From e38b75a50f79c1d1ea2d7d4489896ca5aa16d9d9 Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Tue, 14 Jun 2022 17:19:00 +1200
Subject: [PATCH 03/15] CVE-2022-32743 s4:torture/rpc: Fix tests to match
Windows
BUG: https://bugzilla.samba.org/show_bug.cgi?id=14833
Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
selftest/knownfail.d/netlogon-dns-host-name | 9 +++++++++
source4/torture/rpc/netlogon.c | 12 +++++++-----
2 files changed, 16 insertions(+), 5 deletions(-)
diff --git a/selftest/knownfail.d/netlogon-dns-host-name b/selftest/knownfail.d/netlogon-dns-host-name
index 2d0a0ec..0164a7c 100644
--- a/selftest/knownfail.d/netlogon-dns-host-name
+++ b/selftest/knownfail.d/netlogon-dns-host-name
@@ -1,2 +1,11 @@
^samba.tests.py_credentials.samba.tests.py_credentials.PyCredentialsTests.test_set_dns_hostname_invalid_suffix\(
^samba.tests.py_credentials.samba.tests.py_credentials.PyCredentialsTests.test_set_dns_hostname_with_flag\(
+^samba4.rpc.netlogon on ncacn_ip_tcp with bigendian.netlogon.GetDomainInfo\(
+^samba4.rpc.netlogon on ncacn_ip_tcp with seal,padcheck.netlogon.GetDomainInfo\(
+^samba4.rpc.netlogon on ncacn_ip_tcp with validate.netlogon.GetDomainInfo\(
+^samba4.rpc.netlogon on ncacn_np with bigendian.netlogon.GetDomainInfo\(
+^samba4.rpc.netlogon on ncacn_np with seal,padcheck.netlogon.GetDomainInfo\(
+^samba4.rpc.netlogon on ncacn_np with validate.netlogon.GetDomainInfo\(
+^samba4.rpc.netlogon with bigendian.netlogon.GetDomainInfo\(
+^samba4.rpc.netlogon with seal,padcheck.netlogon.GetDomainInfo\(
+^samba4.rpc.netlogon with validate.netlogon.GetDomainInfo\(
diff --git a/source4/torture/rpc/netlogon.c b/source4/torture/rpc/netlogon.c
index 11f950d..59d7feb 100644
--- a/source4/torture/rpc/netlogon.c
+++ b/source4/torture/rpc/netlogon.c
@@ -5251,9 +5251,9 @@ static bool test_GetDomainInfo(struct torture_context *tctx,
torture_assert(tctx,
ldb_msg_find_attr_as_string(res[0], "operatingSystemServicePack", NULL) == NULL,
"'operatingSystemServicePack' shouldn't stick!");
- torture_assert(tctx,
- ldb_msg_find_attr_as_string(res[0], "operatingSystemVersion", NULL) == NULL,
- "'operatingSystemVersion' shouldn't stick!");
+ torture_assert_str_equal(tctx,
+ ldb_msg_find_attr_as_string(res[0], "operatingSystemVersion", NULL),
+ version_str, "'operatingSystemVersion' wrong!");
/* The DNS host name shouldn't have been updated by the server */
@@ -5387,9 +5387,11 @@ static bool test_GetDomainInfo(struct torture_context *tctx,
torture_assert(tctx, odiT->domainname.string != NULL,
"trust_list domainname should be valid");
- if (texT->trust_type == LSA_TRUST_TYPE_DOWNLEVEL) {
+ if (texT->trust_type == LSA_TRUST_TYPE_DOWNLEVEL ||
+ texT->trust_type == LSA_TRUST_TYPE_MIT)
+ {
torture_assert(tctx, odiT->dns_domainname.string == NULL,
- "trust_list dns_domainname should be NULL for downlevel");
+ "trust_list dns_domainname should be NULL for downlevel or MIT");
} else {
torture_assert(tctx, odiT->dns_domainname.string != NULL,
"trust_list dns_domainname should be valid for uplevel");
--
1.8.3.1

View File

@ -0,0 +1,140 @@
From 49ac07e786df58b914ee85e2db773c0ba8d4e171 Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Tue, 7 Jun 2022 17:36:56 +1200
Subject: [PATCH 04/15] CVE-2022-32743 s4/dsdb/util: Add
dsdb_msg_get_single_value()
This function simulates an add or modify operation for an ldb message to
determine the final value of a particular single-valued attribute. This
is useful when validating attributes that should stay in sync with other
attributes, such as servicePrincipalName and dNSHostName.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=14833
Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
source4/dsdb/samdb/ldb_modules/util.c | 107 ++++++++++++++++++++++++++++++++++
1 file changed, 107 insertions(+)
diff --git a/source4/dsdb/samdb/ldb_modules/util.c b/source4/dsdb/samdb/ldb_modules/util.c
index e7fe8f8..42aa9a2 100644
--- a/source4/dsdb/samdb/ldb_modules/util.c
+++ b/source4/dsdb/samdb/ldb_modules/util.c
@@ -1568,6 +1568,113 @@ int dsdb_get_expected_new_values(TALLOC_CTX *mem_ctx,
return LDB_SUCCESS;
}
+
+/*
+ * Get the value of a single-valued attribute from an ADDed message. 'val' will only live as
+ * long as 'msg' and 'original_val' do, and must not be freed.
+ */
+int dsdb_msg_add_get_single_value(const struct ldb_message *msg,
+ const char *attr_name,
+ const struct ldb_val **val)
+{
+ const struct ldb_message_element *el = NULL;
+
+ /*
+ * The ldb_msg_normalize() call in ldb_request() ensures that
+ * there is at most one message element for each
+ * attribute. Thus, we don't need a loop to deal with an
+ * LDB_ADD.
+ */
+ el = ldb_msg_find_element(msg, attr_name);
+ if (el == NULL) {
+ *val = NULL;
+ return LDB_SUCCESS;
+ }
+ if (el->num_values != 1) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ *val = &el->values[0];
+ return LDB_SUCCESS;
+}
+
+/*
+ * Get the value of a single-valued attribute after processing a
+ * message. 'operation' is either LDB_ADD or LDB_MODIFY. 'val' will only live as
+ * long as 'msg' and 'original_val' do, and must not be freed.
+ */
+int dsdb_msg_get_single_value(const struct ldb_message *msg,
+ const char *attr_name,
+ const struct ldb_val *original_val,
+ const struct ldb_val **val,
+ enum ldb_request_type operation)
+{
+ unsigned idx;
+
+ *val = NULL;
+
+ if (operation == LDB_ADD) {
+ if (original_val != NULL) {
+ /* This is an error on the caller's part. */
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ return dsdb_msg_add_get_single_value(msg, attr_name, val);
+ }
+
+ SMB_ASSERT(operation == LDB_MODIFY);
+
+ *val = original_val;
+
+ for (idx = 0; idx < msg->num_elements; ++idx) {
+ const struct ldb_message_element *el = &msg->elements[idx];
+
+ if (ldb_attr_cmp(el->name, attr_name) != 0) {
+ continue;
+ }
+
+ switch (el->flags & LDB_FLAG_MOD_MASK) {
+ case LDB_FLAG_MOD_ADD:
+ if (el->num_values != 1) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ if (*val != NULL) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ *val = &el->values[0];
+
+ break;
+
+ case LDB_FLAG_MOD_REPLACE:
+ if (el->num_values > 1) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ *val = el->num_values ? &el->values[0] : NULL;
+
+ break;
+
+ case LDB_FLAG_MOD_DELETE:
+ if (el->num_values > 1) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ /*
+ * If a value was specified for the delete, we don't
+ * bother checking it matches the value we currently
+ * have. Any mismatch will be caught later (e.g. in
+ * ldb_kv_modify_internal).
+ */
+
+ *val = NULL;
+
+ break;
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
/*
* This function determines the (last) structural or 88 object class of a passed
* "objectClass" attribute - per MS-ADTS 3.1.1.1.4 this is the last value.
--
1.8.3.1

View File

@ -0,0 +1,69 @@
From 0d888f0c902ebd98cfb82d50ab8b8b3928341ee2 Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Tue, 14 Jun 2022 14:16:10 +1200
Subject: [PATCH 05/15] CVE-2022-32743 s4/dsdb/util: Add function to check for
a subclass relationship
We need to be able to determine whether an object is a subclass of a
specific objectclass such as 'computer'.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=14833
Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
source4/dsdb/samdb/ldb_modules/util.c | 38 +++++++++++++++++++++++++++++++++++
1 file changed, 38 insertions(+)
diff --git a/source4/dsdb/samdb/ldb_modules/util.c b/source4/dsdb/samdb/ldb_modules/util.c
index 42aa9a2..9e00aed 100644
--- a/source4/dsdb/samdb/ldb_modules/util.c
+++ b/source4/dsdb/samdb/ldb_modules/util.c
@@ -1718,6 +1718,44 @@ const struct dsdb_class *dsdb_get_structural_oc_from_msg(const struct dsdb_schem
return dsdb_get_last_structural_class(schema, oc_el);
}
+/*
+ Get the parent class of an objectclass, or NULL if none exists.
+ */
+const struct dsdb_class *dsdb_get_parent_class(const struct dsdb_schema *schema,
+ const struct dsdb_class *objectclass)
+{
+ if (ldb_attr_cmp(objectclass->lDAPDisplayName, "top") == 0) {
+ return NULL;
+ }
+
+ if (objectclass->subClassOf == NULL) {
+ return NULL;
+ }
+
+ return dsdb_class_by_lDAPDisplayName(schema, objectclass->subClassOf);
+}
+
+/*
+ Return true if 'struct_objectclass' is a subclass of 'other_objectclass'. The
+ two objectclasses must originate from the same schema, to allow for
+ pointer-based identity comparison.
+ */
+bool dsdb_is_subclass_of(const struct dsdb_schema *schema,
+ const struct dsdb_class *struct_objectclass,
+ const struct dsdb_class *other_objectclass)
+{
+ while (struct_objectclass != NULL) {
+ /* Pointer comparison can be used due to the same schema str. */
+ if (struct_objectclass == other_objectclass) {
+ return true;
+ }
+
+ struct_objectclass = dsdb_get_parent_class(schema, struct_objectclass);
+ }
+
+ return false;
+}
+
/* Fix the DN so that the relative attribute names are in upper case so that the DN:
cn=Adminstrator,cn=users,dc=samba,dc=example,dc=com becomes
CN=Adminstrator,CN=users,DC=samba,DC=example,DC=com
--
1.8.3.1

View File

@ -0,0 +1,339 @@
From b95431ab2303eb258e37e88d8841f2fb79fc4af5 Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Wed, 1 Jun 2022 16:08:42 +1200
Subject: [PATCH 06/15] CVE-2022-32743 dsdb: Implement validated dNSHostName
write
BUG: https://bugzilla.samba.org/show_bug.cgi?id=14833
Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
selftest/knownfail.d/validated-dns-host-name | 12 --
source4/dsdb/samdb/ldb_modules/acl.c | 283 +++++++++++++++++++++++++++
2 files changed, 283 insertions(+), 12 deletions(-)
diff --git a/selftest/knownfail.d/validated-dns-host-name b/selftest/knownfail.d/validated-dns-host-name
index ee51f44..4b61658 100644
--- a/selftest/knownfail.d/validated-dns-host-name
+++ b/selftest/knownfail.d/validated-dns-host-name
@@ -1,15 +1,3 @@
-^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name\(
-^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_account_no_dollar\(
-^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_allowed_suffixes\(
-^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_case\(
-^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_dollar\(
-^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_empty_string\(
-^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_invalid\(
-^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_no_suffix\(
-^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_no_value\(
^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_spn\(
^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_spn_matching_account_name_new\(
-^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_spn_matching_account_name_original\(
-^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_wrong_prefix\(
-^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_wrong_suffix\(
^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_spn_matching_dns_host_name_invalid\(
diff --git a/source4/dsdb/samdb/ldb_modules/acl.c b/source4/dsdb/samdb/ldb_modules/acl.c
index 1fc6dbf..50802ae 100644
--- a/source4/dsdb/samdb/ldb_modules/acl.c
+++ b/source4/dsdb/samdb/ldb_modules/acl.c
@@ -802,6 +802,277 @@ static int acl_check_spn(TALLOC_CTX *mem_ctx,
return LDB_SUCCESS;
}
+static int acl_check_dns_host_name(TALLOC_CTX *mem_ctx,
+ struct ldb_module *module,
+ struct ldb_request *req,
+ const struct ldb_message_element *el,
+ struct security_descriptor *sd,
+ struct dom_sid *sid,
+ const struct dsdb_attribute *attr,
+ const struct dsdb_class *objectclass)
+{
+ int ret;
+ unsigned i;
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ const struct dsdb_schema *schema = NULL;
+ const struct ldb_message_element *allowed_suffixes = NULL;
+ struct ldb_result *nc_res = NULL;
+ struct ldb_dn *nc_root = NULL;
+ const char *nc_dns_name = NULL;
+ const char *dnsHostName_str = NULL;
+ size_t dns_host_name_len;
+ size_t account_name_len;
+ const struct ldb_message *msg = NULL;
+ const struct ldb_message *search_res = NULL;
+ const struct ldb_val *samAccountName = NULL;
+ const struct ldb_val *dnsHostName = NULL;
+ const struct dsdb_class *computer_objectclass = NULL;
+ bool is_subclass;
+
+ static const char *nc_attrs[] = {
+ "msDS-AllowedDNSSuffixes",
+ NULL
+ };
+
+ if (el->num_values == 0) {
+ return LDB_SUCCESS;
+ }
+ dnsHostName = &el->values[0];
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ /* if we have wp, we can do whatever we like */
+ ret = acl_check_access_on_attribute(module,
+ tmp_ctx,
+ sd,
+ sid,
+ SEC_ADS_WRITE_PROP,
+ attr, objectclass);
+ if (ret == LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+
+ ret = acl_check_extended_right(tmp_ctx,
+ module,
+ req,
+ objectclass,
+ sd,
+ acl_user_token(module),
+ GUID_DRS_DNS_HOST_NAME,
+ SEC_ADS_SELF_WRITE,
+ sid);
+
+ if (ret != LDB_SUCCESS) {
+ dsdb_acl_debug(sd, acl_user_token(module),
+ req->op.mod.message->dn,
+ true,
+ 10);
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /*
+ * If we have "validated write dnshostname", allow delete of
+ * any existing value (this keeps constrained delete to the
+ * same rules as unconstrained)
+ */
+ if (req->operation == LDB_MODIFY) {
+ struct ldb_result *acl_res = NULL;
+
+ static const char *acl_attrs[] = {
+ "sAMAccountName",
+ NULL
+ };
+
+ msg = req->op.mod.message;
+
+ /*
+ * If not add or replace (eg delete),
+ * return success
+ */
+ if ((el->flags
+ & (LDB_FLAG_MOD_ADD|LDB_FLAG_MOD_REPLACE)) == 0)
+ {
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+
+ ret = dsdb_module_search_dn(module, tmp_ctx,
+ &acl_res, msg->dn,
+ acl_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SHOW_RECYCLED,
+ req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ search_res = acl_res->msgs[0];
+ } else if (req->operation == LDB_ADD) {
+ msg = req->op.add.message;
+ search_res = msg;
+ } else {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Check if the account has objectclass 'computer' or 'server'. */
+
+ schema = dsdb_get_schema(ldb, req);
+ if (schema == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ computer_objectclass = dsdb_class_by_lDAPDisplayName(schema, "computer");
+ if (computer_objectclass == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ is_subclass = dsdb_is_subclass_of(schema, objectclass, computer_objectclass);
+ if (!is_subclass) {
+ /* The account is not a computer -- check if it's a server. */
+
+ const struct dsdb_class *server_objectclass = NULL;
+
+ server_objectclass = dsdb_class_by_lDAPDisplayName(schema, "server");
+ if (server_objectclass == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ is_subclass = dsdb_is_subclass_of(schema, objectclass, server_objectclass);
+ if (!is_subclass) {
+ /* Not a computer or server, so no need to validate. */
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+ }
+
+ samAccountName = ldb_msg_find_ldb_val(search_res, "sAMAccountName");
+
+ ret = dsdb_msg_get_single_value(msg,
+ "sAMAccountName",
+ samAccountName,
+ &samAccountName,
+ req->operation);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ account_name_len = samAccountName->length;
+ if (account_name_len && samAccountName->data[account_name_len - 1] == '$') {
+ /* Account for the '$' character. */
+ --account_name_len;
+ }
+
+ dnsHostName_str = (const char *)dnsHostName->data;
+ dns_host_name_len = dnsHostName->length;
+
+ /* Check that sAMAccountName matches the new dNSHostName. */
+
+ if (dns_host_name_len < account_name_len) {
+ goto fail;
+ }
+ if (strncasecmp(dnsHostName_str,
+ (const char *)samAccountName->data,
+ account_name_len) != 0)
+ {
+ goto fail;
+ }
+
+ dnsHostName_str += account_name_len;
+ dns_host_name_len -= account_name_len;
+
+ /* Check the '.' character */
+
+ if (dns_host_name_len == 0 || *dnsHostName_str != '.') {
+ goto fail;
+ }
+
+ ++dnsHostName_str;
+ --dns_host_name_len;
+
+ /* Now we check the suffix. */
+
+ ret = dsdb_find_nc_root(ldb,
+ tmp_ctx,
+ search_res->dn,
+ &nc_root);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ nc_dns_name = samdb_dn_to_dns_domain(tmp_ctx, nc_root);
+ if (nc_dns_name == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ if (strlen(nc_dns_name) == dns_host_name_len &&
+ strncasecmp(dnsHostName_str,
+ nc_dns_name,
+ dns_host_name_len) == 0)
+ {
+ /* It matches -- success. */
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+
+ /* We didn't get a match, so now try msDS-AllowedDNSSuffixes. */
+
+ ret = dsdb_module_search_dn(module, tmp_ctx,
+ &nc_res, nc_root,
+ nc_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SHOW_RECYCLED,
+ req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ allowed_suffixes = ldb_msg_find_element(nc_res->msgs[0],
+ "msDS-AllowedDNSSuffixes");
+ if (allowed_suffixes == NULL) {
+ goto fail;
+ }
+
+ for (i = 0; i < allowed_suffixes->num_values; ++i) {
+ const struct ldb_val *suffix = &allowed_suffixes->values[i];
+
+ if (suffix->length == dns_host_name_len &&
+ strncasecmp(dnsHostName_str,
+ (const char *)suffix->data,
+ dns_host_name_len) == 0)
+ {
+ /* It matches -- success. */
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+ }
+
+fail:
+ ldb_debug_set(ldb, LDB_DEBUG_WARNING,
+ "acl: hostname validation failed for "
+ "hostname[%.*s] account[%.*s]\n",
+ (int)dnsHostName->length, dnsHostName->data,
+ (int)samAccountName->length, samAccountName->data);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+}
+
static int acl_add(struct ldb_module *module, struct ldb_request *req)
{
int ret;
@@ -1536,6 +1807,18 @@ static int acl_modify(struct ldb_module *module, struct ldb_request *req)
if (ret != LDB_SUCCESS) {
goto fail;
}
+ } else if (ldb_attr_cmp("dnsHostName", el->name) == 0) {
+ ret = acl_check_dns_host_name(tmp_ctx,
+ module,
+ req,
+ el,
+ sd,
+ sid,
+ attr,
+ objectclass);
+ if (ret != LDB_SUCCESS) {
+ goto fail;
+ }
} else if (is_undelete != NULL && (ldb_attr_cmp("isDeleted", el->name) == 0)) {
/*
* in case of undelete op permissions on
--
1.8.3.1

View File

@ -0,0 +1,67 @@
From c2ab1f4696fa3f52918a126d0b37993a07f68bcb Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Tue, 7 Jun 2022 17:36:43 +1200
Subject: [PATCH 07/15] CVE-2022-32743 dsdb/common: Add
FORCE_ALLOW_VALIDATED_DNS_HOSTNAME_SPN_WRITE control
Passing this control will grant the right to set validated values for
dNSHostName and servicePrincipalName, and non-validated values for other
attributes.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=14833
Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
source4/dsdb/common/util.c | 7 +++++++
source4/dsdb/samdb/ldb_modules/util.h | 1 +
source4/dsdb/samdb/samdb.h | 6 ++++++
3 files changed, 14 insertions(+)
diff --git a/source4/dsdb/common/util.c b/source4/dsdb/common/util.c
index 1129175..88b0555 100644
--- a/source4/dsdb/common/util.c
+++ b/source4/dsdb/common/util.c
@@ -4546,6 +4546,13 @@ int dsdb_request_add_controls(struct ldb_request *req, uint32_t dsdb_flags)
}
}
+ if (dsdb_flags & DSDB_FLAG_FORCE_ALLOW_VALIDATED_DNS_HOSTNAME_SPN_WRITE) {
+ ret = ldb_request_add_control(req, DSDB_CONTROL_FORCE_ALLOW_VALIDATED_DNS_HOSTNAME_SPN_WRITE_OID, true, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
return LDB_SUCCESS;
}
diff --git a/source4/dsdb/samdb/ldb_modules/util.h b/source4/dsdb/samdb/ldb_modules/util.h
index 5ecf0ee..937767a 100644
--- a/source4/dsdb/samdb/ldb_modules/util.h
+++ b/source4/dsdb/samdb/ldb_modules/util.h
@@ -39,3 +39,4 @@ struct netlogon_samlogon_response;
#define DSDB_FLAG_TOP_MODULE 0x00800000
#define DSDB_FLAG_TRUSTED 0x01000000
#define DSDB_FLAG_REPLICATED_UPDATE 0x02000000
+#define DSDB_FLAG_FORCE_ALLOW_VALIDATED_DNS_HOSTNAME_SPN_WRITE 0x04000000
diff --git a/source4/dsdb/samdb/samdb.h b/source4/dsdb/samdb/samdb.h
index 286c97f..3db7704 100644
--- a/source4/dsdb/samdb/samdb.h
+++ b/source4/dsdb/samdb/samdb.h
@@ -226,6 +226,12 @@ struct dsdb_control_transaction_identifier {
struct GUID transaction_guid;
};
+/*
+ * passed when we want to allow validated writes to dNSHostName and
+ * servicePrincipalName.
+ */
+#define DSDB_CONTROL_FORCE_ALLOW_VALIDATED_DNS_HOSTNAME_SPN_WRITE_OID "1.3.6.1.4.1.7165.4.3.35"
+
#define DSDB_EXTENDED_REPLICATED_OBJECTS_OID "1.3.6.1.4.1.7165.4.4.1"
struct dsdb_extended_replicated_object {
struct ldb_message *msg;
--
1.8.3.1

View File

@ -0,0 +1,240 @@
From f9831259b9f6a49b9e1a7be75198d60374cdef2f Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Tue, 7 Jun 2022 17:39:07 +1200
Subject: [PATCH 08/15] CVE-2022-32743 dsdb/modules/acl: Handle
FORCE_ALLOW_VALIDATED_DNS_HOSTNAME_SPN_WRITE control
When this control is specified, we'll assume we have Validated Write on
dNSHostName and servicePrincipalName, and Write Property on other
attributes.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=14833
Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
source4/dsdb/samdb/ldb_modules/acl.c | 148 +++++++++++++++++++++--------------
1 file changed, 91 insertions(+), 57 deletions(-)
diff --git a/source4/dsdb/samdb/ldb_modules/acl.c b/source4/dsdb/samdb/ldb_modules/acl.c
index 50802ae..a26d0ba 100644
--- a/source4/dsdb/samdb/ldb_modules/acl.c
+++ b/source4/dsdb/samdb/ldb_modules/acl.c
@@ -667,7 +667,8 @@ static int acl_check_spn(TALLOC_CTX *mem_ctx,
struct security_descriptor *sd,
struct dom_sid *sid,
const struct dsdb_attribute *attr,
- const struct dsdb_class *objectclass)
+ const struct dsdb_class *objectclass,
+ const struct ldb_control *implicit_validated_write_control)
{
int ret;
unsigned int i;
@@ -694,34 +695,44 @@ static int acl_check_spn(TALLOC_CTX *mem_ctx,
NULL
};
- /* if we have wp, we can do whatever we like */
- if (acl_check_access_on_attribute(module,
- tmp_ctx,
- sd,
- sid,
- SEC_ADS_WRITE_PROP,
- attr, objectclass) == LDB_SUCCESS) {
- talloc_free(tmp_ctx);
- return LDB_SUCCESS;
- }
+ if (implicit_validated_write_control != NULL) {
+ /*
+ * The validated write control dispenses with ACL
+ * checks. We act as if we have an implicit Self Write
+ * privilege, but, assuming we don't have Write
+ * Property, still proceed with further validation
+ * checks.
+ */
+ } else {
+ /* if we have wp, we can do whatever we like */
+ if (acl_check_access_on_attribute(module,
+ tmp_ctx,
+ sd,
+ sid,
+ SEC_ADS_WRITE_PROP,
+ attr, objectclass) == LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
- ret = acl_check_extended_right(tmp_ctx,
- module,
- req,
- objectclass,
- sd,
- acl_user_token(module),
- GUID_DRS_VALIDATE_SPN,
- SEC_ADS_SELF_WRITE,
- sid);
+ ret = acl_check_extended_right(tmp_ctx,
+ module,
+ req,
+ objectclass,
+ sd,
+ acl_user_token(module),
+ GUID_DRS_VALIDATE_SPN,
+ SEC_ADS_SELF_WRITE,
+ sid);
- if (ret != LDB_SUCCESS) {
- dsdb_acl_debug(sd, acl_user_token(module),
- req->op.mod.message->dn,
- true,
- 10);
- talloc_free(tmp_ctx);
- return ret;
+ if (ret != LDB_SUCCESS) {
+ dsdb_acl_debug(sd, acl_user_token(module),
+ req->op.mod.message->dn,
+ true,
+ 10);
+ talloc_free(tmp_ctx);
+ return ret;
+ }
}
/*
@@ -809,7 +820,8 @@ static int acl_check_dns_host_name(TALLOC_CTX *mem_ctx,
struct security_descriptor *sd,
struct dom_sid *sid,
const struct dsdb_attribute *attr,
- const struct dsdb_class *objectclass)
+ const struct dsdb_class *objectclass,
+ const struct ldb_control *implicit_validated_write_control)
{
int ret;
unsigned i;
@@ -845,35 +857,45 @@ static int acl_check_dns_host_name(TALLOC_CTX *mem_ctx,
return ldb_oom(ldb);
}
- /* if we have wp, we can do whatever we like */
- ret = acl_check_access_on_attribute(module,
- tmp_ctx,
- sd,
- sid,
- SEC_ADS_WRITE_PROP,
- attr, objectclass);
- if (ret == LDB_SUCCESS) {
- talloc_free(tmp_ctx);
- return LDB_SUCCESS;
- }
+ if (implicit_validated_write_control != NULL) {
+ /*
+ * The validated write control dispenses with ACL
+ * checks. We act as if we have an implicit Self Write
+ * privilege, but, assuming we don't have Write
+ * Property, still proceed with further validation
+ * checks.
+ */
+ } else {
+ /* if we have wp, we can do whatever we like */
+ ret = acl_check_access_on_attribute(module,
+ tmp_ctx,
+ sd,
+ sid,
+ SEC_ADS_WRITE_PROP,
+ attr, objectclass);
+ if (ret == LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
- ret = acl_check_extended_right(tmp_ctx,
- module,
- req,
- objectclass,
- sd,
- acl_user_token(module),
- GUID_DRS_DNS_HOST_NAME,
- SEC_ADS_SELF_WRITE,
- sid);
+ ret = acl_check_extended_right(tmp_ctx,
+ module,
+ req,
+ objectclass,
+ sd,
+ acl_user_token(module),
+ GUID_DRS_DNS_HOST_NAME,
+ SEC_ADS_SELF_WRITE,
+ sid);
- if (ret != LDB_SUCCESS) {
- dsdb_acl_debug(sd, acl_user_token(module),
- req->op.mod.message->dn,
- true,
- 10);
- talloc_free(tmp_ctx);
- return ret;
+ if (ret != LDB_SUCCESS) {
+ dsdb_acl_debug(sd, acl_user_token(module),
+ req->op.mod.message->dn,
+ true,
+ 10);
+ talloc_free(tmp_ctx);
+ return ret;
+ }
}
/*
@@ -1621,6 +1643,7 @@ static int acl_modify(struct ldb_module *module, struct ldb_request *req)
struct dom_sid *sid = NULL;
struct ldb_control *as_system;
struct ldb_control *is_undelete;
+ struct ldb_control *implicit_validated_write_control = NULL;
bool userPassword;
bool password_rights_checked = false;
TALLOC_CTX *tmp_ctx;
@@ -1647,6 +1670,12 @@ static int acl_modify(struct ldb_module *module, struct ldb_request *req)
is_undelete = ldb_request_get_control(req, DSDB_CONTROL_RESTORE_TOMBSTONE_OID);
+ implicit_validated_write_control = ldb_request_get_control(
+ req, DSDB_CONTROL_FORCE_ALLOW_VALIDATED_DNS_HOSTNAME_SPN_WRITE_OID);
+ if (implicit_validated_write_control != NULL) {
+ implicit_validated_write_control->critical = 0;
+ }
+
/* Don't print this debug statement if elements[0].name is going to be NULL */
if (msg->num_elements > 0) {
DEBUG(10, ("ldb:acl_modify: %s\n", msg->elements[0].name));
@@ -1803,7 +1832,8 @@ static int acl_modify(struct ldb_module *module, struct ldb_request *req)
sd,
sid,
attr,
- objectclass);
+ objectclass,
+ implicit_validated_write_control);
if (ret != LDB_SUCCESS) {
goto fail;
}
@@ -1815,7 +1845,8 @@ static int acl_modify(struct ldb_module *module, struct ldb_request *req)
sd,
sid,
attr,
- objectclass);
+ objectclass,
+ implicit_validated_write_control);
if (ret != LDB_SUCCESS) {
goto fail;
}
@@ -1827,6 +1858,9 @@ static int acl_modify(struct ldb_module *module, struct ldb_request *req)
* tombstone_reanimate module
*/
continue;
+ } else if (implicit_validated_write_control != NULL) {
+ /* Allow the update. */
+ continue;
} else {
ret = acl_check_access_on_attribute(module,
tmp_ctx,
--
1.8.3.1

View File

@ -0,0 +1,82 @@
From d07641fc5a7d2fa323e6d6fe3223da3a6d682405 Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Thu, 2 Jun 2022 17:11:08 +1200
Subject: [PATCH 09/15] CVE-2022-32743 s4:rpc_server/netlogon: Remove
dNSHostName prefix check
This check is not exhaustive (it does not check the suffix of the
dNSHostName), and should be covered by a validated write check in
acl_modify().
BUG: https://bugzilla.samba.org/show_bug.cgi?id=14833
Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
selftest/knownfail.d/netlogon-dns-host-name | 5 +++++
source4/rpc_server/netlogon/dcerpc_netlogon.c | 21 ++-------------------
2 files changed, 7 insertions(+), 19 deletions(-)
diff --git a/selftest/knownfail.d/netlogon-dns-host-name b/selftest/knownfail.d/netlogon-dns-host-name
index 0164a7c..d6a8aa2 100644
--- a/selftest/knownfail.d/netlogon-dns-host-name
+++ b/selftest/knownfail.d/netlogon-dns-host-name
@@ -1,4 +1,6 @@
^samba.tests.py_credentials.samba.tests.py_credentials.PyCredentialsTests.test_set_dns_hostname_invalid_suffix\(
+^samba.tests.py_credentials.samba.tests.py_credentials.PyCredentialsTests.test_set_dns_hostname_invalid_validated_write\(
+^samba.tests.py_credentials.samba.tests.py_credentials.PyCredentialsTests.test_set_dns_hostname_invalid_write_property\(
^samba.tests.py_credentials.samba.tests.py_credentials.PyCredentialsTests.test_set_dns_hostname_with_flag\(
^samba4.rpc.netlogon on ncacn_ip_tcp with bigendian.netlogon.GetDomainInfo\(
^samba4.rpc.netlogon on ncacn_ip_tcp with seal,padcheck.netlogon.GetDomainInfo\(
@@ -6,6 +8,9 @@
^samba4.rpc.netlogon on ncacn_np with bigendian.netlogon.GetDomainInfo\(
^samba4.rpc.netlogon on ncacn_np with seal,padcheck.netlogon.GetDomainInfo\(
^samba4.rpc.netlogon on ncacn_np with validate.netlogon.GetDomainInfo\(
+^samba4.rpc.netlogon on ncalrpc with bigendian.netlogon.GetDomainInfo\(
+^samba4.rpc.netlogon on ncalrpc with seal,padcheck.netlogon.GetDomainInfo\(
+^samba4.rpc.netlogon on ncalrpc with validate.netlogon.GetDomainInfo\(
^samba4.rpc.netlogon with bigendian.netlogon.GetDomainInfo\(
^samba4.rpc.netlogon with seal,padcheck.netlogon.GetDomainInfo\(
^samba4.rpc.netlogon with validate.netlogon.GetDomainInfo\(
diff --git a/source4/rpc_server/netlogon/dcerpc_netlogon.c b/source4/rpc_server/netlogon/dcerpc_netlogon.c
index eab57da..2d5fc8b 100644
--- a/source4/rpc_server/netlogon/dcerpc_netlogon.c
+++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c
@@ -2413,7 +2413,7 @@ static NTSTATUS dcesrv_netr_LogonGetDomainInfo(struct dcesrv_call_state *dce_cal
};
const char * const attrs2[] = { "sAMAccountName", "dNSHostName",
"msDS-SupportedEncryptionTypes", NULL };
- const char *sam_account_name, *old_dns_hostname, *prefix1, *prefix2;
+ const char *sam_account_name, *old_dns_hostname;
struct ldb_context *sam_ctx;
const struct GUID *our_domain_guid = NULL;
struct lsa_TrustDomainInfoInfoEx *our_tdo = NULL;
@@ -2483,24 +2483,7 @@ static NTSTATUS dcesrv_netr_LogonGetDomainInfo(struct dcesrv_call_state *dce_cal
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
- /*
- * Checks that the sam account name without a possible "$"
- * matches as prefix with the DNS hostname in the workstation
- * info structure.
- */
- prefix1 = talloc_strndup(mem_ctx, sam_account_name,
- strcspn(sam_account_name, "$"));
- NT_STATUS_HAVE_NO_MEMORY(prefix1);
- if (r->in.query->workstation_info->dns_hostname != NULL) {
- prefix2 = talloc_strndup(mem_ctx,
- r->in.query->workstation_info->dns_hostname,
- strcspn(r->in.query->workstation_info->dns_hostname, "."));
- NT_STATUS_HAVE_NO_MEMORY(prefix2);
-
- if (strcasecmp(prefix1, prefix2) != 0) {
- update_dns_hostname = false;
- }
- } else {
+ if (r->in.query->workstation_info->dns_hostname == NULL) {
update_dns_hostname = false;
}
--
1.8.3.1

View File

@ -0,0 +1,52 @@
From 02c2a8c7b01d6412393423813b710c88b20fb97f Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Tue, 7 Jun 2022 17:25:28 +1200
Subject: [PATCH 10/15] CVE-2022-32743 s4:rpc_server/netlogon: Always observe
NETR_WS_FLAG_HANDLES_SPN_UPDATE flag
Even when there is no old DNS hostname present.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=14833
Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
selftest/knownfail.d/netlogon-dns-host-name | 1 -
source4/rpc_server/netlogon/dcerpc_netlogon.c | 7 ++-----
2 files changed, 2 insertions(+), 6 deletions(-)
diff --git a/selftest/knownfail.d/netlogon-dns-host-name b/selftest/knownfail.d/netlogon-dns-host-name
index d6a8aa2..30c157f 100644
--- a/selftest/knownfail.d/netlogon-dns-host-name
+++ b/selftest/knownfail.d/netlogon-dns-host-name
@@ -1,7 +1,6 @@
^samba.tests.py_credentials.samba.tests.py_credentials.PyCredentialsTests.test_set_dns_hostname_invalid_suffix\(
^samba.tests.py_credentials.samba.tests.py_credentials.PyCredentialsTests.test_set_dns_hostname_invalid_validated_write\(
^samba.tests.py_credentials.samba.tests.py_credentials.PyCredentialsTests.test_set_dns_hostname_invalid_write_property\(
-^samba.tests.py_credentials.samba.tests.py_credentials.PyCredentialsTests.test_set_dns_hostname_with_flag\(
^samba4.rpc.netlogon on ncacn_ip_tcp with bigendian.netlogon.GetDomainInfo\(
^samba4.rpc.netlogon on ncacn_ip_tcp with seal,padcheck.netlogon.GetDomainInfo\(
^samba4.rpc.netlogon on ncacn_ip_tcp with validate.netlogon.GetDomainInfo\(
diff --git a/source4/rpc_server/netlogon/dcerpc_netlogon.c b/source4/rpc_server/netlogon/dcerpc_netlogon.c
index 2d5fc8b..efba013 100644
--- a/source4/rpc_server/netlogon/dcerpc_netlogon.c
+++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c
@@ -2495,13 +2495,10 @@ static NTSTATUS dcesrv_netr_LogonGetDomainInfo(struct dcesrv_call_state *dce_cal
/*
* Updates the DNS hostname when the client wishes that the
* server should handle this for him
- * ("NETR_WS_FLAG_HANDLES_SPN_UPDATE" not set). And this is
- * obviously only checked when we do already have a
- * "dNSHostName".
+ * ("NETR_WS_FLAG_HANDLES_SPN_UPDATE" not set).
* See MS-NRPC section 3.5.4.3.9
*/
- if ((old_dns_hostname != NULL) &&
- (r->in.query->workstation_info->workstation_flags
+ if ((r->in.query->workstation_info->workstation_flags
& NETR_WS_FLAG_HANDLES_SPN_UPDATE) != 0) {
update_dns_hostname = false;
}
--
1.8.3.1

View File

@ -0,0 +1,71 @@
From f545142380151a626848dbae9ee746167f3299fa Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Tue, 7 Jun 2022 17:29:02 +1200
Subject: [PATCH 11/15] CVE-2022-32743 s4:rpc_server/netlogon: Connect to samdb
as a user, rather than as system
This allows us to perform validation on a client-specified dNSHostName
value, to ensure that it matches the sAMAccountName.
We might not have any rights to modify the account, so pass the control
FORCE_ALLOW_VALIDATED_DNS_HOSTNAME_SPN_WRITE which allows us to perform
a validated write to dNSHostName and servicePrincipalName (and
unvalidated writes to other attributes, such as operatingSystem).
BUG: https://bugzilla.samba.org/show_bug.cgi?id=14833
Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
selftest/knownfail.d/netlogon-dns-host-name | 17 ++---------------
source4/rpc_server/netlogon/dcerpc_netlogon.c | 5 +++--
2 files changed, 5 insertions(+), 17 deletions(-)
diff --git a/selftest/knownfail.d/netlogon-dns-host-name b/selftest/knownfail.d/netlogon-dns-host-name
index 30c157f..3eca0cd 100644
--- a/selftest/knownfail.d/netlogon-dns-host-name
+++ b/selftest/knownfail.d/netlogon-dns-host-name
@@ -1,15 +1,2 @@
-^samba.tests.py_credentials.samba.tests.py_credentials.PyCredentialsTests.test_set_dns_hostname_invalid_suffix\(
-^samba.tests.py_credentials.samba.tests.py_credentials.PyCredentialsTests.test_set_dns_hostname_invalid_validated_write\(
-^samba.tests.py_credentials.samba.tests.py_credentials.PyCredentialsTests.test_set_dns_hostname_invalid_write_property\(
-^samba4.rpc.netlogon on ncacn_ip_tcp with bigendian.netlogon.GetDomainInfo\(
-^samba4.rpc.netlogon on ncacn_ip_tcp with seal,padcheck.netlogon.GetDomainInfo\(
-^samba4.rpc.netlogon on ncacn_ip_tcp with validate.netlogon.GetDomainInfo\(
-^samba4.rpc.netlogon on ncacn_np with bigendian.netlogon.GetDomainInfo\(
-^samba4.rpc.netlogon on ncacn_np with seal,padcheck.netlogon.GetDomainInfo\(
-^samba4.rpc.netlogon on ncacn_np with validate.netlogon.GetDomainInfo\(
-^samba4.rpc.netlogon on ncalrpc with bigendian.netlogon.GetDomainInfo\(
-^samba4.rpc.netlogon on ncalrpc with seal,padcheck.netlogon.GetDomainInfo\(
-^samba4.rpc.netlogon on ncalrpc with validate.netlogon.GetDomainInfo\(
-^samba4.rpc.netlogon with bigendian.netlogon.GetDomainInfo\(
-^samba4.rpc.netlogon with seal,padcheck.netlogon.GetDomainInfo\(
-^samba4.rpc.netlogon with validate.netlogon.GetDomainInfo\(
+^samba.tests.py_credentials.samba.tests.py_credentials.PyCredentialsTests.test_set_dns_hostname_valid\(
+^samba.tests.py_credentials.samba.tests.py_credentials.PyCredentialsTests.test_set_dns_hostname_valid_denied\(
diff --git a/source4/rpc_server/netlogon/dcerpc_netlogon.c b/source4/rpc_server/netlogon/dcerpc_netlogon.c
index efba013..15cd27b 100644
--- a/source4/rpc_server/netlogon/dcerpc_netlogon.c
+++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c
@@ -2450,7 +2450,8 @@ static NTSTATUS dcesrv_netr_LogonGetDomainInfo(struct dcesrv_call_state *dce_cal
}
NT_STATUS_NOT_OK_RETURN(status);
- sam_ctx = dcesrv_samdb_connect_as_system(mem_ctx, dce_call);
+ /* We want to avoid connecting as system. */
+ sam_ctx = dcesrv_samdb_connect_as_user(mem_ctx, dce_call);
if (sam_ctx == NULL) {
return NT_STATUS_INVALID_SYSTEM_SERVICE;
}
@@ -2607,7 +2608,7 @@ static NTSTATUS dcesrv_netr_LogonGetDomainInfo(struct dcesrv_call_state *dce_cal
}
}
- if (dsdb_replace(sam_ctx, new_msg, 0) != LDB_SUCCESS) {
+ if (dsdb_replace(sam_ctx, new_msg, DSDB_FLAG_FORCE_ALLOW_VALIDATED_DNS_HOSTNAME_SPN_WRITE) != LDB_SUCCESS) {
DEBUG(3,("Impossible to update samdb: %s\n",
ldb_errstring(sam_ctx)));
}
--
1.8.3.1

View File

@ -0,0 +1,56 @@
From 7638abd38a13f9d2b5c769eb12c70eacf49b3806 Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Tue, 7 Jun 2022 17:37:34 +1200
Subject: [PATCH 12/15] CVE-2022-32743 dsdb/modules/acl: Account for
sAMAccountName without $
If we have an account without a trailing $, we should ensure the
servicePrincipalName matches the entire sAMAccountName. We should not
allow a match against the sAMAccountName prefix of length
strlen(samAccountName) - 1, as that could conflict with a different
account.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=14833
Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
source4/dsdb/samdb/ldb_modules/acl.c | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/source4/dsdb/samdb/ldb_modules/acl.c b/source4/dsdb/samdb/ldb_modules/acl.c
index a26d0ba..82f6ec3 100644
--- a/source4/dsdb/samdb/ldb_modules/acl.c
+++ b/source4/dsdb/samdb/ldb_modules/acl.c
@@ -543,6 +543,7 @@ static int acl_validate_spn_value(TALLOC_CTX *mem_ctx,
char *instanceName;
char *serviceType;
char *serviceName;
+ size_t account_name_len;
const char *forest_name = samdb_forest_name(ldb, mem_ctx);
const char *base_domain = samdb_default_domain_name(ldb, mem_ctx);
struct loadparm_context *lp_ctx = talloc_get_type(ldb_get_opaque(ldb, "loadparm"),
@@ -616,11 +617,18 @@ static int acl_validate_spn_value(TALLOC_CTX *mem_ctx,
}
}
}
+
+ account_name_len = strlen(samAccountName);
+ if (account_name_len && samAccountName[account_name_len - 1] == '$') {
+ /* Account for the '$' character. */
+ --account_name_len;
+ }
+
/* instanceName can be samAccountName without $ or dnsHostName
* or "ntds_guid._msdcs.forest_domain for DC objects */
- if (strlen(instanceName) == (strlen(samAccountName) - 1)
+ if (strlen(instanceName) == account_name_len
&& strncasecmp(instanceName, samAccountName,
- strlen(samAccountName) - 1) == 0) {
+ account_name_len) == 0) {
goto success;
}
if ((dnsHostName != NULL) &&
--
1.8.3.1

View File

@ -0,0 +1,217 @@
From e1c52ac05a9ff505d2e5eac2f1ece4e95844ee71 Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Tue, 7 Jun 2022 17:38:55 +1200
Subject: [PATCH 13/15] CVE-2022-32743 dsdb/modules/acl: Allow simultaneous
sAMAccountName, dNSHostName, and servicePrincipalName change
If the message changes the sAMAccountName, we'll check dNSHostName and
servicePrincipalName values against the new value of sAMAccountName,
rather than the account's current value. Similarly, if the message
changes the dNSHostName, we'll check servicePrincipalName values against
the new dNSHostName. This allows setting more than one of these
attributes simultaneously with validated write rights.
We now pass 'struct ldb_val' to acl_validate_spn_value() instead of
simple strings. Previously, we were relying on the data inside 'struct
ldb_val' having a terminating zero byte, even though this is not
guaranteed.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=14833
Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
selftest/knownfail.d/netlogon-dns-host-name | 2 -
selftest/knownfail.d/validated-dns-host-name | 3 -
source4/dsdb/samdb/ldb_modules/acl.c | 85 +++++++++++++++++++++-------
3 files changed, 65 insertions(+), 25 deletions(-)
delete mode 100644 selftest/knownfail.d/netlogon-dns-host-name
delete mode 100644 selftest/knownfail.d/validated-dns-host-name
diff --git a/selftest/knownfail.d/netlogon-dns-host-name b/selftest/knownfail.d/netlogon-dns-host-name
deleted file mode 100644
index 3eca0cd..0000000
--- a/selftest/knownfail.d/netlogon-dns-host-name
+++ /dev/null
@@ -1,2 +0,0 @@
-^samba.tests.py_credentials.samba.tests.py_credentials.PyCredentialsTests.test_set_dns_hostname_valid\(
-^samba.tests.py_credentials.samba.tests.py_credentials.PyCredentialsTests.test_set_dns_hostname_valid_denied\(
diff --git a/selftest/knownfail.d/validated-dns-host-name b/selftest/knownfail.d/validated-dns-host-name
deleted file mode 100644
index 4b61658..0000000
--- a/selftest/knownfail.d/validated-dns-host-name
+++ /dev/null
@@ -1,3 +0,0 @@
-^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_spn\(
-^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_spn_matching_account_name_new\(
-^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_spn_matching_dns_host_name_invalid\(
diff --git a/source4/dsdb/samdb/ldb_modules/acl.c b/source4/dsdb/samdb/ldb_modules/acl.c
index 82f6ec3..4098ae2 100644
--- a/source4/dsdb/samdb/ldb_modules/acl.c
+++ b/source4/dsdb/samdb/ldb_modules/acl.c
@@ -529,10 +529,10 @@ static int acl_sDRightsEffective(struct ldb_module *module,
static int acl_validate_spn_value(TALLOC_CTX *mem_ctx,
struct ldb_context *ldb,
- const char *spn_value,
+ const struct ldb_val *spn_value,
uint32_t userAccountControl,
- const char *samAccountName,
- const char *dnsHostName,
+ const struct ldb_val *samAccountName,
+ const struct ldb_val *dnsHostName,
const char *netbios_name,
const char *ntds_guid)
{
@@ -543,6 +543,7 @@ static int acl_validate_spn_value(TALLOC_CTX *mem_ctx,
char *instanceName;
char *serviceType;
char *serviceName;
+ const char *spn_value_str = NULL;
size_t account_name_len;
const char *forest_name = samdb_forest_name(ldb, mem_ctx);
const char *base_domain = samdb_default_domain_name(ldb, mem_ctx);
@@ -551,7 +552,18 @@ static int acl_validate_spn_value(TALLOC_CTX *mem_ctx,
bool is_dc = (userAccountControl & UF_SERVER_TRUST_ACCOUNT) ||
(userAccountControl & UF_PARTIAL_SECRETS_ACCOUNT);
- if (strcasecmp_m(spn_value, samAccountName) == 0) {
+ spn_value_str = talloc_strndup(mem_ctx,
+ (const char *)spn_value->data,
+ spn_value->length);
+ if (spn_value_str == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ if (spn_value->length == samAccountName->length &&
+ strncasecmp((const char *)spn_value->data,
+ (const char *)samAccountName->data,
+ spn_value->length) == 0)
+ {
/* MacOS X sets this value, and setting an SPN of your
* own samAccountName is both pointless and safe */
return LDB_SUCCESS;
@@ -565,7 +577,7 @@ static int acl_validate_spn_value(TALLOC_CTX *mem_ctx,
"Could not initialize kerberos context.");
}
- ret = krb5_parse_name(krb_ctx, spn_value, &principal);
+ ret = krb5_parse_name(krb_ctx, spn_value_str, &principal);
if (ret) {
krb5_free_context(krb_ctx);
return LDB_ERR_CONSTRAINT_VIOLATION;
@@ -618,8 +630,10 @@ static int acl_validate_spn_value(TALLOC_CTX *mem_ctx,
}
}
- account_name_len = strlen(samAccountName);
- if (account_name_len && samAccountName[account_name_len - 1] == '$') {
+ account_name_len = samAccountName->length;
+ if (account_name_len &&
+ samAccountName->data[account_name_len - 1] == '$')
+ {
/* Account for the '$' character. */
--account_name_len;
}
@@ -627,12 +641,18 @@ static int acl_validate_spn_value(TALLOC_CTX *mem_ctx,
/* instanceName can be samAccountName without $ or dnsHostName
* or "ntds_guid._msdcs.forest_domain for DC objects */
if (strlen(instanceName) == account_name_len
- && strncasecmp(instanceName, samAccountName,
- account_name_len) == 0) {
+ && strncasecmp(instanceName,
+ (const char *)samAccountName->data,
+ account_name_len) == 0)
+ {
goto success;
}
if ((dnsHostName != NULL) &&
- (strcasecmp(instanceName, dnsHostName) == 0)) {
+ strlen(instanceName) == dnsHostName->length &&
+ (strncasecmp(instanceName,
+ (const char *)dnsHostName->data,
+ dnsHostName->length) == 0))
+ {
goto success;
}
if (is_dc) {
@@ -650,10 +670,13 @@ fail:
krb5_free_context(krb_ctx);
ldb_debug_set(ldb, LDB_DEBUG_WARNING,
"acl: spn validation failed for "
- "spn[%s] uac[0x%x] account[%s] hostname[%s] "
+ "spn[%.*s] uac[0x%x] account[%.*s] hostname[%.*s] "
"nbname[%s] ntds[%s] forest[%s] domain[%s]\n",
- spn_value, (unsigned)userAccountControl,
- samAccountName, dnsHostName,
+ (int)spn_value->length, spn_value->data,
+ (unsigned)userAccountControl,
+ (int)samAccountName->length, samAccountName->data,
+ dnsHostName != NULL ? (int)dnsHostName->length : 0,
+ dnsHostName != NULL ? (const char *)dnsHostName->data : "",
netbios_name, ntds_guid,
forest_name, base_domain);
return LDB_ERR_CONSTRAINT_VIOLATION;
@@ -686,9 +709,9 @@ static int acl_check_spn(TALLOC_CTX *mem_ctx,
struct ldb_result *netbios_res;
struct ldb_dn *partitions_dn = samdb_partitions_dn(ldb, tmp_ctx);
uint32_t userAccountControl;
- const char *samAccountName;
- const char *dnsHostName;
const char *netbios_name;
+ const struct ldb_val *dns_host_name_val = NULL;
+ const struct ldb_val *sam_account_name_val = NULL;
struct GUID ntds;
char *ntds_guid = NULL;
@@ -773,9 +796,31 @@ static int acl_check_spn(TALLOC_CTX *mem_ctx,
return ret;
}
+ dns_host_name_val = ldb_msg_find_ldb_val(acl_res->msgs[0], "dNSHostName");
+
+ ret = dsdb_msg_get_single_value(req->op.mod.message,
+ "dNSHostName",
+ dns_host_name_val,
+ &dns_host_name_val,
+ req->operation);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
userAccountControl = ldb_msg_find_attr_as_uint(acl_res->msgs[0], "userAccountControl", 0);
- dnsHostName = ldb_msg_find_attr_as_string(acl_res->msgs[0], "dnsHostName", NULL);
- samAccountName = ldb_msg_find_attr_as_string(acl_res->msgs[0], "samAccountName", NULL);
+
+ sam_account_name_val = ldb_msg_find_ldb_val(acl_res->msgs[0], "sAMAccountName");
+
+ ret = dsdb_msg_get_single_value(req->op.mod.message,
+ "sAMAccountName",
+ sam_account_name_val,
+ &sam_account_name_val,
+ req->operation);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
ret = dsdb_module_search(module, tmp_ctx,
&netbios_res, partitions_dn,
@@ -806,10 +851,10 @@ static int acl_check_spn(TALLOC_CTX *mem_ctx,
for (i=0; i < el->num_values; i++) {
ret = acl_validate_spn_value(tmp_ctx,
ldb,
- (char *)el->values[i].data,
+ &el->values[i],
userAccountControl,
- samAccountName,
- dnsHostName,
+ sam_account_name_val,
+ dns_host_name_val,
netbios_name,
ntds_guid);
if (ret != LDB_SUCCESS) {
--
1.8.3.1

View File

@ -0,0 +1,159 @@
From 6b76bc7339addb14884c2d6ddb20c559c7fbe07d Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Thu, 9 Jun 2022 19:32:30 +1200
Subject: [PATCH 14/15] CVE-2022-32743 s4:rpc_server/common: Add
dcesrv_samdb_connect_session_info()
This function allows us to connect to samdb as a particular user by
passing in that user's session info.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=14833
Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
source4/rpc_server/common/common.h | 1 +
source4/rpc_server/common/server_info.c | 65 ++++++++++++++++++++-------------
2 files changed, 40 insertions(+), 26 deletions(-)
diff --git a/source4/rpc_server/common/common.h b/source4/rpc_server/common/common.h
index 7d2f8c5..b57ddf2 100644
--- a/source4/rpc_server/common/common.h
+++ b/source4/rpc_server/common/common.h
@@ -30,6 +30,7 @@ struct dcesrv_context;
struct dcesrv_call_state;
struct ndr_interface_table;
struct ncacn_packet;
+struct auth_session_info;
struct dcerpc_server_info {
const char *domain_name;
diff --git a/source4/rpc_server/common/server_info.c b/source4/rpc_server/common/server_info.c
index a2af376..34228c3 100644
--- a/source4/rpc_server/common/server_info.c
+++ b/source4/rpc_server/common/server_info.c
@@ -190,48 +190,44 @@ bool dcesrv_common_validate_share_name(TALLOC_CTX *mem_ctx, const char *share_na
return true;
}
-static struct ldb_context *dcesrv_samdb_connect_common(
+/*
+ * call_session_info is session info for samdb. call_audit_session_info is for
+ * auditing and may be NULL.
+ */
+struct ldb_context *dcesrv_samdb_connect_session_info(
TALLOC_CTX *mem_ctx,
struct dcesrv_call_state *dce_call,
- bool as_system)
+ const struct auth_session_info *call_session_info,
+ const struct auth_session_info *call_audit_session_info)
{
struct ldb_context *samdb = NULL;
- struct auth_session_info *system_session_info = NULL;
- const struct auth_session_info *call_session_info =
- dcesrv_call_session_info(dce_call);
struct auth_session_info *user_session_info = NULL;
- struct auth_session_info *ldb_session_info = NULL;
struct auth_session_info *audit_session_info = NULL;
struct tsocket_address *remote_address = NULL;
- if (as_system) {
- system_session_info = system_session(dce_call->conn->dce_ctx->lp_ctx);
- if (system_session_info == NULL) {
- return NULL;
- }
- }
-
user_session_info = copy_session_info(mem_ctx, call_session_info);
if (user_session_info == NULL) {
return NULL;
}
+ if (call_audit_session_info != NULL) {
+ audit_session_info = copy_session_info(mem_ctx, call_audit_session_info);
+ if (audit_session_info == NULL) {
+ talloc_free(user_session_info);
+ return NULL;
+ }
+ }
+
if (dce_call->conn->remote_address != NULL) {
remote_address = tsocket_address_copy(dce_call->conn->remote_address,
user_session_info);
if (remote_address == NULL) {
+ TALLOC_FREE(audit_session_info);
+ talloc_free(user_session_info);
return NULL;
}
}
- if (system_session_info != NULL) {
- ldb_session_info = system_session_info;
- audit_session_info = user_session_info;
- } else {
- ldb_session_info = user_session_info;
- audit_session_info = NULL;
- }
-
/*
* We need to make sure every argument
* stays arround for the lifetime of 'samdb',
@@ -253,10 +249,11 @@ static struct ldb_context *dcesrv_samdb_connect_common(
mem_ctx,
dce_call->event_ctx,
dce_call->conn->dce_ctx->lp_ctx,
- ldb_session_info,
+ user_session_info,
remote_address,
0);
if (samdb == NULL) {
+ TALLOC_FREE(audit_session_info);
talloc_free(user_session_info);
return NULL;
}
@@ -265,6 +262,8 @@ static struct ldb_context *dcesrv_samdb_connect_common(
if (audit_session_info != NULL) {
int ret;
+ talloc_steal(samdb, audit_session_info);
+
ret = ldb_set_opaque(samdb,
DSDB_NETWORK_SESSION_INFO,
audit_session_info);
@@ -288,8 +287,18 @@ struct ldb_context *dcesrv_samdb_connect_as_system(
TALLOC_CTX *mem_ctx,
struct dcesrv_call_state *dce_call)
{
- return dcesrv_samdb_connect_common(mem_ctx, dce_call,
- true /* as_system */);
+ const struct auth_session_info *system_session_info = NULL;
+ const struct auth_session_info *call_session_info = NULL;
+
+ system_session_info = system_session(dce_call->conn->dce_ctx->lp_ctx);
+ if (system_session_info == NULL) {
+ return NULL;
+ }
+
+ call_session_info = dcesrv_call_session_info(dce_call);
+
+ return dcesrv_samdb_connect_session_info(mem_ctx, dce_call,
+ system_session_info, call_session_info);
}
/*
@@ -301,6 +310,10 @@ struct ldb_context *dcesrv_samdb_connect_as_user(
TALLOC_CTX *mem_ctx,
struct dcesrv_call_state *dce_call)
{
- return dcesrv_samdb_connect_common(mem_ctx, dce_call,
- false /* not as_system */);
+ const struct auth_session_info *call_session_info = NULL;
+
+ call_session_info = dcesrv_call_session_info(dce_call);
+
+ return dcesrv_samdb_connect_session_info(mem_ctx, dce_call,
+ call_session_info, NULL);
}
--
1.8.3.1

View File

@ -0,0 +1,70 @@
From 15c86028a861139cee4560fe093c965ffc30eb13 Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Thu, 9 Jun 2022 19:46:07 +1200
Subject: [PATCH 15/15] CVE-2022-32743 s4:rpc_server/netlogon: Reconnect to
samdb as workstation account
This ensures that the database update can be attributed to the
workstation account, rather than to the anonymous SID, in the audit
logs.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=14833
Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Autobuild-User(master): Douglas Bagnall <dbagnall@samba.org>
Autobuild-Date(master): Thu Jul 28 23:41:27 UTC 2022 on sn-devel-184
---
source4/rpc_server/netlogon/dcerpc_netlogon.c | 28 +++++++++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/source4/rpc_server/netlogon/dcerpc_netlogon.c b/source4/rpc_server/netlogon/dcerpc_netlogon.c
index 15cd27b..12ad780 100644
--- a/source4/rpc_server/netlogon/dcerpc_netlogon.c
+++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c
@@ -2422,6 +2422,7 @@ static NTSTATUS dcesrv_netr_LogonGetDomainInfo(struct dcesrv_call_state *dce_cal
struct ldb_dn *workstation_dn;
struct netr_DomainInformation *domain_info;
struct netr_LsaPolicyInformation *lsa_policy_info;
+ struct auth_session_info *workstation_session_info = NULL;
uint32_t default_supported_enc_types = 0xFFFFFFFF;
bool update_dns_hostname = true;
int ret, i;
@@ -2468,6 +2469,33 @@ static NTSTATUS dcesrv_netr_LogonGetDomainInfo(struct dcesrv_call_state *dce_cal
dom_sid_string(mem_ctx, creds->sid));
NT_STATUS_HAVE_NO_MEMORY(workstation_dn);
+ /* Get the workstation's session info from the database. */
+ status = authsam_get_session_info_principal(mem_ctx,
+ dce_call->conn->dce_ctx->lp_ctx,
+ sam_ctx,
+ NULL, /* principal */
+ workstation_dn,
+ 0, /* session_info_flags */
+ &workstation_session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * Reconnect to samdb as the workstation, now that we have its
+ * session info. We do this so the database update can be
+ * attributed to the workstation account in the audit logs --
+ * otherwise it might be incorrectly attributed to
+ * SID_NT_ANONYMOUS.
+ */
+ sam_ctx = dcesrv_samdb_connect_session_info(mem_ctx,
+ dce_call,
+ workstation_session_info,
+ workstation_session_info);
+ if (sam_ctx == NULL) {
+ return NT_STATUS_INVALID_SYSTEM_SERVICE;
+ }
+
/* Lookup for attributes in workstation object */
ret = gendb_search_dn(sam_ctx, mem_ctx, workstation_dn, &res1,
attrs2);
--
1.8.3.1

View File

@ -49,7 +49,7 @@
Name: samba
Version: 4.15.3
Release: 8
Release: 9
Summary: A suite for Linux to interoperate with Windows
License: GPLv3+ and LGPLv3+
@ -78,6 +78,21 @@ Patch8: backport-CVE-2022-32746.patch
Patch9: backport-CVE-2022-32745.patch
Patch10: backport-CVE-2022-2031-CVE-2022-32744.patch
Patch11: backport-CVE-2022-32742.patch
Patch12: 0001-CVE-2022-32743-s4-acl-Add-tests-for-validated-dNSHos.patch
Patch13: 0002-CVE-2022-32743-tests-py_credentials-Add-tests-for-se.patch
Patch14: 0003-CVE-2022-32743-s4-torture-rpc-Fix-tests-to-match-Win.patch
Patch15: 0004-CVE-2022-32743-s4-dsdb-util-Add-dsdb_msg_get_single_.patch
Patch16: 0005-CVE-2022-32743-s4-dsdb-util-Add-function-to-check-fo.patch
Patch17: 0006-CVE-2022-32743-dsdb-Implement-validated-dNSHostName-.patch
Patch18: 0007-CVE-2022-32743-dsdb-common-Add-FORCE_ALLOW_VALIDATED.patch
Patch19: 0008-CVE-2022-32743-dsdb-modules-acl-Handle-FORCE_ALLOW_V.patch
Patch20: 0009-CVE-2022-32743-s4-rpc_server-netlogon-Remove-dNSHost.patch
Patch21: 0010-CVE-2022-32743-s4-rpc_server-netlogon-Always-observe.patch
Patch22: 0011-CVE-2022-32743-s4-rpc_server-netlogon-Connect-to-sam.patch
Patch23: 0012-CVE-2022-32743-dsdb-modules-acl-Account-for-sAMAccou.patch
Patch24: 0013-CVE-2022-32743-dsdb-modules-acl-Allow-simultaneous-s.patch
Patch25: 0014-CVE-2022-32743-s4-rpc_server-common-Add-dcesrv_samdb.patch
Patch26: 0015-CVE-2022-32743-s4-rpc_server-netlogon-Reconnect-to-s.patch
BuildRequires: avahi-devel bison dbus-devel docbook-style-xsl e2fsprogs-devel flex gawk gnupg2 gnutls-devel >= 3.4.7 gpgme-devel
BuildRequires: jansson-devel krb5-devel >= %{required_mit_krb5} libacl-devel libaio-devel libarchive-devel libattr-devel
@ -3401,6 +3416,12 @@ fi
%endif
%changelog
* Fri Aug 26 2022 zhouyihang <zhouyihang3@h-partners.com> - 4.15.3-9
- Type:cves
- ID:CVE-2022-32743
- SUG:NA
- DESC:fix CVE-2022-32743
* Fri Aug 12 2022 xinghe <xinghe2@h-partners.com> - 4.15.3-8
- Type:bugfix
- ID:NA