mirror of
https://github.com/samba-team/samba.git
synced 2024-12-22 13:34:15 +03:00
dsdb: Add authentication audit logging for LDAP password change
This ensures this particular vector is not forgotten Signed-off-by: Andrew Bartlett <abartlet@samba.org>
This commit is contained in:
parent
008843463f
commit
638b10adb0
@ -31,6 +31,7 @@ from samba.net import Net
|
||||
from samba import ntstatus
|
||||
import samba
|
||||
from subprocess import call
|
||||
from ldb import LdbError
|
||||
|
||||
USER_NAME = "authlogtestuser"
|
||||
USER_PASS = samba.generate_random_password(32,32)
|
||||
@ -226,10 +227,9 @@ class AuthLogPassChangeTests(samba.tests.auth_log_base.AuthLogTestBase):
|
||||
msg["Authentication"]["authDescription"]
|
||||
== "OemChangePasswordUser2")
|
||||
|
||||
newPassword = samba.generate_random_password(32,32)
|
||||
username = os.environ["USERNAME"]
|
||||
password = os.environ["PASSWORD"]
|
||||
server = os.environ["SERVER"]
|
||||
password = os.environ["PASSWORD"]
|
||||
server_param = "--server=%s" % server
|
||||
creds = "-U%s%%%s" % (username,password)
|
||||
call(["bin/net", "rap", server_param,
|
||||
@ -240,3 +240,90 @@ class AuthLogPassChangeTests(samba.tests.auth_log_base.AuthLogTestBase):
|
||||
self.assertEquals(7,
|
||||
len(messages),
|
||||
"Did not receive the expected number of messages")
|
||||
|
||||
def test_ldap_change_password(self):
|
||||
def isLastExpectedMessage( msg):
|
||||
return (msg["type"] == "Authentication" and
|
||||
msg["Authentication"]["status"]
|
||||
== "NT_STATUS_OK" and
|
||||
msg["Authentication"]["serviceDescription"]
|
||||
== "LDAP Password Change" and
|
||||
msg["Authentication"]["authDescription"]
|
||||
== "LDAP Modify")
|
||||
|
||||
new_password = samba.generate_random_password(32,32)
|
||||
self.ldb.modify_ldif(
|
||||
"dn: cn=" + USER_NAME + ",cn=users," + self.base_dn + "\n" +
|
||||
"changetype: modify\n" +
|
||||
"delete: userPassword\n" +
|
||||
"userPassword: " + USER_PASS + "\n" +
|
||||
"add: userPassword\n" +
|
||||
"userPassword: " + new_password + "\n"
|
||||
)
|
||||
|
||||
messages = self.waitForMessages( isLastExpectedMessage)
|
||||
print "Received %d messages" % len(messages)
|
||||
self.assertEquals(4,
|
||||
len(messages),
|
||||
"Did not receive the expected number of messages")
|
||||
|
||||
#
|
||||
# Currently this does not get logged, so we expect to only see the log
|
||||
# entries for the underlying ldap bind.
|
||||
#
|
||||
def test_ldap_change_password_bad_user(self):
|
||||
def isLastExpectedMessage( msg):
|
||||
return (msg["type"] == "Authorization" and
|
||||
msg["Authorization"]["serviceDescription"]
|
||||
== "LDAP" and
|
||||
msg["Authorization"]["authType"] == "krb5")
|
||||
|
||||
new_password = samba.generate_random_password(32,32)
|
||||
try:
|
||||
self.ldb.modify_ldif(
|
||||
"dn: cn=" + "badUser" + ",cn=users," + self.base_dn + "\n" +
|
||||
"changetype: modify\n" +
|
||||
"delete: userPassword\n" +
|
||||
"userPassword: " + USER_PASS + "\n" +
|
||||
"add: userPassword\n" +
|
||||
"userPassword: " + new_password + "\n"
|
||||
)
|
||||
self.fail()
|
||||
except LdbError, (num, msg):
|
||||
pass
|
||||
|
||||
messages = self.waitForMessages( isLastExpectedMessage)
|
||||
print "Received %d messages" % len(messages)
|
||||
self.assertEquals(3,
|
||||
len(messages),
|
||||
"Did not receive the expected number of messages")
|
||||
|
||||
def test_ldap_change_password_bad_original_password(self):
|
||||
def isLastExpectedMessage( msg):
|
||||
return (msg["type"] == "Authentication" and
|
||||
msg["Authentication"]["status"]
|
||||
== "NT_STATUS_WRONG_PASSWORD" and
|
||||
msg["Authentication"]["serviceDescription"]
|
||||
== "LDAP Password Change" and
|
||||
msg["Authentication"]["authDescription"]
|
||||
== "LDAP Modify")
|
||||
|
||||
new_password = samba.generate_random_password(32,32)
|
||||
try:
|
||||
self.ldb.modify_ldif(
|
||||
"dn: cn=" + USER_NAME + ",cn=users," + self.base_dn + "\n" +
|
||||
"changetype: modify\n" +
|
||||
"delete: userPassword\n" +
|
||||
"userPassword: " + "badPassword" + "\n" +
|
||||
"add: userPassword\n" +
|
||||
"userPassword: " + new_password + "\n"
|
||||
)
|
||||
self.fail()
|
||||
except LdbError, (num, msg):
|
||||
pass
|
||||
|
||||
messages = self.waitForMessages( isLastExpectedMessage)
|
||||
print "Received %d messages" % len(messages)
|
||||
self.assertEquals(4,
|
||||
len(messages),
|
||||
"Did not receive the expected number of messages")
|
||||
|
@ -45,6 +45,8 @@
|
||||
#include "../lib/crypto/crypto.h"
|
||||
#include "param/param.h"
|
||||
#include "lib/krb5_wrap/krb5_samba.h"
|
||||
#include "auth/common_auth.h"
|
||||
#include "lib/messaging/messaging.h"
|
||||
|
||||
#ifdef ENABLE_GPGME
|
||||
#undef class
|
||||
@ -119,7 +121,7 @@ struct setup_password_fields_io {
|
||||
|
||||
struct smb_krb5_context *smb_krb5_context;
|
||||
|
||||
/* infos about the user account */
|
||||
/* info about the user account */
|
||||
struct {
|
||||
uint32_t userAccountControl;
|
||||
NTTIME pwdLastSet;
|
||||
@ -128,6 +130,7 @@ struct setup_password_fields_io {
|
||||
bool is_computer;
|
||||
bool is_krbtgt;
|
||||
uint32_t restrictions;
|
||||
struct dom_sid *account_sid;
|
||||
} u;
|
||||
|
||||
/* new credentials and old given credentials */
|
||||
@ -2292,7 +2295,7 @@ static int setup_smartcard_reset(struct setup_password_fields_io *io)
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
static int make_error_and_update_badPwdCount(struct setup_password_fields_io *io)
|
||||
static int make_error_and_update_badPwdCount(struct setup_password_fields_io *io, WERROR *werror)
|
||||
{
|
||||
struct ldb_context *ldb = ldb_module_get_ctx(io->ac->module);
|
||||
struct ldb_message *mod_msg = NULL;
|
||||
@ -2390,15 +2393,16 @@ static int make_error_and_update_badPwdCount(struct setup_password_fields_io *io
|
||||
|
||||
done:
|
||||
ret = LDB_ERR_CONSTRAINT_VIOLATION;
|
||||
*werror = WERR_INVALID_PASSWORD;
|
||||
ldb_asprintf_errstring(ldb,
|
||||
"%08X: %s - check_password_restrictions: "
|
||||
"The old password specified doesn't match!",
|
||||
W_ERROR_V(WERR_INVALID_PASSWORD),
|
||||
W_ERROR_V(*werror),
|
||||
ldb_strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int check_password_restrictions(struct setup_password_fields_io *io)
|
||||
static int check_password_restrictions(struct setup_password_fields_io *io, WERROR *werror)
|
||||
{
|
||||
struct ldb_context *ldb = ldb_module_get_ctx(io->ac->module);
|
||||
int ret;
|
||||
@ -2406,6 +2410,8 @@ static int check_password_restrictions(struct setup_password_fields_io *io)
|
||||
lp_ctx = talloc_get_type(ldb_get_opaque(ldb, "loadparm"),
|
||||
struct loadparm_context);
|
||||
|
||||
*werror = WERR_INVALID_PARAMETER;
|
||||
|
||||
if (!io->ac->update_password) {
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
@ -2427,7 +2433,7 @@ static int check_password_restrictions(struct setup_password_fields_io *io)
|
||||
has no problems at all */
|
||||
if (io->og.nt_hash) {
|
||||
if (!io->o.nt_hash || memcmp(io->og.nt_hash->hash, io->o.nt_hash->hash, 16) != 0) {
|
||||
return make_error_and_update_badPwdCount(io);
|
||||
return make_error_and_update_badPwdCount(io, werror);
|
||||
}
|
||||
|
||||
nt_hash_checked = true;
|
||||
@ -2440,7 +2446,7 @@ static int check_password_restrictions(struct setup_password_fields_io *io)
|
||||
if (io->og.lm_hash) {
|
||||
if ((!io->o.lm_hash && !nt_hash_checked)
|
||||
|| (io->o.lm_hash && memcmp(io->og.lm_hash->hash, io->o.lm_hash->hash, 16) != 0)) {
|
||||
return make_error_and_update_badPwdCount(io);
|
||||
return make_error_and_update_badPwdCount(io, werror);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2455,10 +2461,11 @@ static int check_password_restrictions(struct setup_password_fields_io *io)
|
||||
!io->ac->pwd_reset)
|
||||
{
|
||||
ret = LDB_ERR_CONSTRAINT_VIOLATION;
|
||||
*werror = WERR_PASSWORD_RESTRICTION;
|
||||
ldb_asprintf_errstring(ldb,
|
||||
"%08X: %s - check_password_restrictions: "
|
||||
"password is too young to change!",
|
||||
W_ERROR_V(WERR_PASSWORD_RESTRICTION),
|
||||
W_ERROR_V(*werror),
|
||||
ldb_strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
@ -2481,10 +2488,11 @@ static int check_password_restrictions(struct setup_password_fields_io *io)
|
||||
|
||||
case SAMR_VALIDATION_STATUS_PWD_TOO_SHORT:
|
||||
ret = LDB_ERR_CONSTRAINT_VIOLATION;
|
||||
*werror = WERR_PASSWORD_RESTRICTION;
|
||||
ldb_asprintf_errstring(ldb,
|
||||
"%08X: %s - check_password_restrictions: "
|
||||
"the password is too short. It should be equal or longer than %u characters!",
|
||||
W_ERROR_V(WERR_PASSWORD_RESTRICTION),
|
||||
W_ERROR_V(*werror),
|
||||
ldb_strerror(ret),
|
||||
io->ac->status->domain_data.minPwdLength);
|
||||
io->ac->status->reject_reason = SAM_PWD_CHANGE_PASSWORD_TOO_SHORT;
|
||||
@ -2492,26 +2500,29 @@ static int check_password_restrictions(struct setup_password_fields_io *io)
|
||||
|
||||
case SAMR_VALIDATION_STATUS_NOT_COMPLEX_ENOUGH:
|
||||
ret = LDB_ERR_CONSTRAINT_VIOLATION;
|
||||
*werror = WERR_PASSWORD_RESTRICTION;
|
||||
ldb_asprintf_errstring(ldb,
|
||||
"%08X: %s - check_password_restrictions: "
|
||||
"the password does not meet the complexity criteria!",
|
||||
W_ERROR_V(WERR_PASSWORD_RESTRICTION),
|
||||
W_ERROR_V(*werror),
|
||||
ldb_strerror(ret));
|
||||
io->ac->status->reject_reason = SAM_PWD_CHANGE_NOT_COMPLEX;
|
||||
return ret;
|
||||
|
||||
default:
|
||||
ret = LDB_ERR_CONSTRAINT_VIOLATION;
|
||||
*werror = WERR_PASSWORD_RESTRICTION;
|
||||
ldb_asprintf_errstring(ldb,
|
||||
"%08X: %s - check_password_restrictions: "
|
||||
"the password doesn't fit due to a miscellaneous restriction!",
|
||||
W_ERROR_V(WERR_PASSWORD_RESTRICTION),
|
||||
W_ERROR_V(*werror),
|
||||
ldb_strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (io->ac->pwd_reset) {
|
||||
*werror = WERR_OK;
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
@ -2523,10 +2534,11 @@ static int check_password_restrictions(struct setup_password_fields_io *io)
|
||||
ret = memcmp(io->n.nt_hash, io->o.nt_history[i].hash, 16);
|
||||
if (ret == 0) {
|
||||
ret = LDB_ERR_CONSTRAINT_VIOLATION;
|
||||
*werror = WERR_PASSWORD_RESTRICTION;
|
||||
ldb_asprintf_errstring(ldb,
|
||||
"%08X: %s - check_password_restrictions: "
|
||||
"the password was already used (in history)!",
|
||||
W_ERROR_V(WERR_PASSWORD_RESTRICTION),
|
||||
W_ERROR_V(*werror),
|
||||
ldb_strerror(ret));
|
||||
io->ac->status->reject_reason = SAM_PWD_CHANGE_PWD_IN_HISTORY;
|
||||
return ret;
|
||||
@ -2542,10 +2554,11 @@ static int check_password_restrictions(struct setup_password_fields_io *io)
|
||||
ret = memcmp(io->n.lm_hash, io->o.lm_history[i].hash, 16);
|
||||
if (ret == 0) {
|
||||
ret = LDB_ERR_CONSTRAINT_VIOLATION;
|
||||
*werror = WERR_PASSWORD_RESTRICTION;
|
||||
ldb_asprintf_errstring(ldb,
|
||||
"%08X: %s - check_password_restrictions: "
|
||||
"the password was already used (in history)!",
|
||||
W_ERROR_V(WERR_PASSWORD_RESTRICTION),
|
||||
W_ERROR_V(*werror),
|
||||
ldb_strerror(ret));
|
||||
io->ac->status->reject_reason = SAM_PWD_CHANGE_PWD_IN_HISTORY;
|
||||
return ret;
|
||||
@ -2556,10 +2569,11 @@ static int check_password_restrictions(struct setup_password_fields_io *io)
|
||||
/* are all password changes disallowed? */
|
||||
if (io->ac->status->domain_data.pwdProperties & DOMAIN_REFUSE_PASSWORD_CHANGE) {
|
||||
ret = LDB_ERR_CONSTRAINT_VIOLATION;
|
||||
*werror = WERR_PASSWORD_RESTRICTION;
|
||||
ldb_asprintf_errstring(ldb,
|
||||
"%08X: %s - check_password_restrictions: "
|
||||
"password changes disabled!",
|
||||
W_ERROR_V(WERR_PASSWORD_RESTRICTION),
|
||||
W_ERROR_V(*werror),
|
||||
ldb_strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
@ -2567,10 +2581,11 @@ static int check_password_restrictions(struct setup_password_fields_io *io)
|
||||
/* can this user change the password? */
|
||||
if (io->u.userAccountControl & UF_PASSWD_CANT_CHANGE) {
|
||||
ret = LDB_ERR_CONSTRAINT_VIOLATION;
|
||||
*werror = WERR_PASSWORD_RESTRICTION;
|
||||
ldb_asprintf_errstring(ldb,
|
||||
"%08X: %s - check_password_restrictions: "
|
||||
"password can't be changed on this account!",
|
||||
W_ERROR_V(WERR_PASSWORD_RESTRICTION),
|
||||
W_ERROR_V(*werror),
|
||||
ldb_strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
@ -2578,6 +2593,87 @@ static int check_password_restrictions(struct setup_password_fields_io *io)
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
static int check_password_restrictions_and_log(struct setup_password_fields_io *io)
|
||||
{
|
||||
WERROR werror;
|
||||
int ret = check_password_restrictions(io, &werror);
|
||||
struct ph_context *ac = io->ac;
|
||||
/*
|
||||
* Password resets are not authentication events, and if the
|
||||
* upper layer checked the password and supplied the hash
|
||||
* values as proof, then this is also not an authentication
|
||||
* even at this layer (already logged). This is to log LDAP
|
||||
* password changes.
|
||||
*/
|
||||
|
||||
/* Do not record a failure in the auth log below in the success case */
|
||||
if (ret == LDB_SUCCESS) {
|
||||
werror = WERR_OK;
|
||||
}
|
||||
|
||||
if (ac->pwd_reset == false && ac->change == NULL) {
|
||||
struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
|
||||
struct imessaging_context *msg_ctx;
|
||||
struct loadparm_context *lp_ctx
|
||||
= talloc_get_type_abort(ldb_get_opaque(ldb, "loadparm"),
|
||||
struct loadparm_context);
|
||||
NTSTATUS status = werror_to_ntstatus(werror);
|
||||
const char *domain_name = lpcfg_sam_name(lp_ctx);
|
||||
void *opaque_remote_address = NULL;
|
||||
/*
|
||||
* Forcing this via the NTLM auth structure is not ideal, but
|
||||
* it is the most practical option right now, and ensures the
|
||||
* logs are consistent, even if some elements are always NULL.
|
||||
*/
|
||||
struct auth_usersupplied_info ui = {
|
||||
.mapped_state = true,
|
||||
.was_mapped = true,
|
||||
.client = {
|
||||
.account_name = io->u.sAMAccountName,
|
||||
.domain_name = domain_name,
|
||||
},
|
||||
.mapped = {
|
||||
.account_name = io->u.sAMAccountName,
|
||||
.domain_name = domain_name,
|
||||
},
|
||||
.service_description = "LDAP Password Change",
|
||||
.auth_description = "LDAP Modify",
|
||||
.password_type = "plaintext"
|
||||
};
|
||||
|
||||
opaque_remote_address = ldb_get_opaque(ldb,
|
||||
"remoteAddress");
|
||||
if (opaque_remote_address == NULL) {
|
||||
ldb_asprintf_errstring(ldb,
|
||||
"Failed to obtain remote address for "
|
||||
"the LDAP client while changing the "
|
||||
"password");
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
ui.remote_host = talloc_get_type(opaque_remote_address,
|
||||
struct tsocket_address);
|
||||
|
||||
msg_ctx = imessaging_client_init(ac, lp_ctx,
|
||||
ldb_get_event_context(ldb));
|
||||
if (!msg_ctx) {
|
||||
ldb_asprintf_errstring(ldb,
|
||||
"Failed to generate client messaging context in %s",
|
||||
lpcfg_imessaging_path(ac, lp_ctx));
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
log_authentication_event(msg_ctx,
|
||||
lp_ctx,
|
||||
&ui,
|
||||
status,
|
||||
domain_name,
|
||||
io->u.sAMAccountName,
|
||||
NULL,
|
||||
io->u.account_sid);
|
||||
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int update_final_msg(struct setup_password_fields_io *io)
|
||||
{
|
||||
struct ldb_context *ldb = ldb_module_get_ctx(io->ac->module);
|
||||
@ -2842,12 +2938,12 @@ static int setup_io(struct ph_context *ac,
|
||||
io->u.is_computer = ldb_msg_check_string_attribute(info_msg, "objectClass", "computer");
|
||||
|
||||
/* Ensure it has an objectSID too */
|
||||
account_sid = samdb_result_dom_sid(ac, info_msg, "objectSid");
|
||||
if (account_sid != NULL) {
|
||||
io->u.account_sid = samdb_result_dom_sid(ac, info_msg, "objectSid");
|
||||
if (io->u.account_sid != NULL) {
|
||||
NTSTATUS status;
|
||||
uint32_t rid = 0;
|
||||
|
||||
status = dom_sid_split_rid(account_sid, account_sid, NULL, &rid);
|
||||
status = dom_sid_split_rid(account_sid, io->u.account_sid, NULL, &rid);
|
||||
if (NT_STATUS_IS_OK(status)) {
|
||||
if (rid == DOMAIN_RID_KRBTGT) {
|
||||
io->u.is_krbtgt = true;
|
||||
@ -3842,7 +3938,7 @@ static int password_hash_add_do_add(struct ph_context *ac)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = check_password_restrictions(&io);
|
||||
ret = check_password_restrictions_and_log(&io);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
@ -4180,7 +4276,7 @@ static int password_hash_mod_do_mod(struct ph_context *ac)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = check_password_restrictions(&io);
|
||||
ret = check_password_restrictions_and_log(&io);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user