1
0
mirror of https://github.com/samba-team/samba.git synced 2025-01-22 22:04:08 +03:00

CVE-2020-25722 s4/dsdb/samldb: check for clashes in UPNs/samaccountnames

We already know duplicate sAMAccountNames and UserPrincipalNames are bad,
but we also have to check against the values these imply in each other.

For example, imagine users with SAM account names "Alice" and "Bob" in
the realm "example.com". If they do not have explicit UPNs, by the logic
of MS-ADTS 5.1.1.1.1 they use the implict UPNs "alice@example.com" and
"bob@example.com", respectively. If Bob's UPN gets set to
"alice@example.com", it will clash with Alice's implicit one.

Therefore we refuse to allow a UPN that implies an existing SAM account
name and vice versa.

BUG: https://bugzilla.samba.org/show_bug.cgi?id=14564

Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
This commit is contained in:
Douglas Bagnall 2021-10-22 13:17:34 +13:00 committed by Jule Anger
parent 083813b635
commit a87278b69c
2 changed files with 203 additions and 18 deletions

View File

@ -1,16 +1 @@
samba.tests.ldap_upn_sam_account.+LdapUpnSamSambaOnlyTest.test_upn_sam_SAM_contains_delete
samba.tests.ldap_upn_sam_account.+LdapUpnSamSambaOnlyTest.test_upn_sam_UPN_same_but_for_internal_spaces
samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_SAM_ends_with_at
samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_SAM_has_at_signs_clashing_upn_first
samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_SAM_has_at_signs_clashing_upn_second
samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_SAM_starts_with_at
samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_UPN_clash_on_other_realm
samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_UPN_ends_with_at
samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_UPN_has_no_at
samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_UPN_starts_with_at
samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_add_the_same_upn_to_different_objects
samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_different_objects_SAM_after_UPN
samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_different_objects_SAM_before_UPN
samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_different_objects_same_UPN_different_case
samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_spaces_around_at
samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_two_way_clash

View File

@ -238,8 +238,9 @@ static int samldb_unique_attr_check(struct samldb_ctx *ac, const char *attr,
return ldb_module_oom(ac->module);
}
/* Make sure that attr (eg) "sAMAccountName" is only used once */
/*
* No other object should have the attribute with this value.
*/
if (attr_conflict != NULL) {
ret = dsdb_module_search(ac->module, ac, &res,
base_dn,
@ -273,6 +274,193 @@ static int samldb_unique_attr_check(struct samldb_ctx *ac, const char *attr,
return LDB_SUCCESS;
}
static inline int samldb_sam_account_upn_clash_sub_search(
struct samldb_ctx *ac,
TALLOC_CTX *mem_ctx,
struct ldb_dn *base_dn,
const char *attr,
const char *value,
const char *err_msg
)
{
/*
* A very specific helper function for samldb_sam_account_upn_clash(),
* where we end up doing this same thing several times in a row.
*/
const char * const no_attrs[] = { NULL };
struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
struct ldb_result *res = NULL;
int ret;
char *enc_value = ldb_binary_encode_string(ac, value);
if (enc_value == NULL) {
return ldb_module_oom(ac->module);
}
ret = dsdb_module_search(ac->module, mem_ctx, &res,
base_dn,
LDB_SCOPE_SUBTREE, no_attrs,
DSDB_FLAG_NEXT_MODULE, ac->req,
"(%s=%s)",
attr, enc_value);
talloc_free(enc_value);
if (ret != LDB_SUCCESS) {
return ret;
} else if (res->count > 1) {
return ldb_operr(ldb);
} else if (res->count == 1) {
if (ldb_dn_compare(res->msgs[0]->dn, ac->msg->dn) != 0){
ldb_asprintf_errstring(ldb,
"samldb: %s '%s' "
"is already in use %s",
attr, value, err_msg);
/* different errors for different attrs */
if (strcasecmp("userPrincipalName", attr) == 0) {
return LDB_ERR_CONSTRAINT_VIOLATION;
}
return LDB_ERR_ENTRY_ALREADY_EXISTS;
}
}
return LDB_SUCCESS;
}
static int samldb_sam_account_upn_clash(struct samldb_ctx *ac)
{
struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
int ret;
struct ldb_dn *base_dn = ldb_get_default_basedn(ldb);
TALLOC_CTX *tmp_ctx = NULL;
const char *real_sam = NULL;
const char *real_upn = NULL;
char *implied_sam = NULL;
char *implied_upn = NULL;
const char *realm = NULL;
ret = samldb_get_single_valued_attr(ldb, ac,
"sAMAccountName",
&real_sam);
if (ret != LDB_SUCCESS) {
return ret;
}
ret = samldb_get_single_valued_attr(ldb, ac,
"userPrincipalName",
&real_upn);
if (ret != LDB_SUCCESS) {
return ret;
}
if (real_upn == NULL && real_sam == NULL) {
/* Not changing these things, so we're done */
return LDB_SUCCESS;
}
tmp_ctx = talloc_new(ac);
realm = samdb_dn_to_dns_domain(tmp_ctx, base_dn);
if (realm == NULL) {
talloc_free(tmp_ctx);
return ldb_operr(ldb);
}
if (real_upn != NULL) {
/*
* note we take the last @ in the upn because the first (i.e.
* sAMAccountName equivalent) part can contain @.
*
* It is also OK (per Windows) for a UPN to have zero @s.
*/
char *at = NULL;
char *upn_realm = NULL;
implied_sam = talloc_strdup(tmp_ctx, real_upn);
if (implied_sam == NULL) {
talloc_free(tmp_ctx);
return ldb_module_oom(ac->module);
}
at = strrchr(implied_sam, '@');
if (at == NULL) {
/*
* there is no @ in this UPN, so we treat the whole
* thing as a sAMAccountName for the purposes of a
* clash.
*/
DBG_INFO("samldb: userPrincipalName '%s' contains "
"no '@' character\n", implied_sam);
} else {
/*
* Now, this upn only implies a sAMAccountName if the
* realm is our realm. So we need to compare the tail
* of the upn to the realm.
*/
*at = '\0';
upn_realm = at + 1;
if (strcasecmp(upn_realm, realm) != 0) {
/* implied_sam is not the implied
* sAMAccountName after all, because it is
* from a different realm. */
TALLOC_FREE(implied_sam);
}
}
}
if (real_sam != NULL) {
implied_upn = talloc_asprintf(tmp_ctx, "%s@%s",
real_sam, realm);
if (implied_upn == NULL) {
talloc_free(tmp_ctx);
return ldb_module_oom(ac->module);
}
}
/*
* Now we have all of the actual and implied names, in which to search
* for conflicts.
*/
if (real_sam != NULL) {
ret = samldb_sam_account_upn_clash_sub_search(
ac, tmp_ctx, base_dn, "sAMAccountName",
real_sam, "");
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
}
if (implied_upn != NULL) {
ret = samldb_sam_account_upn_clash_sub_search(
ac, tmp_ctx, base_dn, "userPrincipalName", implied_upn,
"(implied by sAMAccountName)");
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
}
if (real_upn != NULL) {
ret = samldb_sam_account_upn_clash_sub_search(
ac, tmp_ctx, base_dn, "userPrincipalName",
real_upn, "");
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
}
if (implied_sam != NULL) {
ret = samldb_sam_account_upn_clash_sub_search(
ac, tmp_ctx, base_dn, "sAMAccountName", implied_sam,
"(implied by userPrincipalName)");
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
}
talloc_free(tmp_ctx);
return LDB_SUCCESS;
}
/* This is run during an add or modify */
static int samldb_sam_accountname_valid_check(struct samldb_ctx *ac)
{
int ret = 0;
@ -306,7 +494,11 @@ static int samldb_sam_accountname_valid_check(struct samldb_ctx *ac)
} else if (ret == LDB_ERR_OBJECT_CLASS_VIOLATION) {
ret = LDB_ERR_CONSTRAINT_VIOLATION;
}
if (ret != LDB_SUCCESS) {
return ret;
}
ret = samldb_sam_account_upn_clash(ac);
if (ret != LDB_SUCCESS) {
return ret;
}
@ -4180,7 +4372,6 @@ static int samldb_modify(struct ldb_module *module, struct ldb_request *req)
if (ret != LDB_SUCCESS) {
return ret;
}
user_account_control
= ldb_msg_find_attr_as_uint(res->msgs[0],
"userAccountControl",
@ -4204,6 +4395,15 @@ static int samldb_modify(struct ldb_module *module, struct ldb_request *req)
}
}
el = ldb_msg_find_element(ac->msg, "userPrincipalName");
if (el != NULL) {
ret = samldb_sam_account_upn_clash(ac);
if (ret != LDB_SUCCESS) {
talloc_free(ac);
return ret;
}
}
el = ldb_msg_find_element(ac->msg, "ldapDisplayName");
if (el != NULL) {
ret = samldb_schema_ldapdisplayname_valid_check(ac);