2

I am using Python ldap3[1] to build an API that allows users to change their Microsoft Active Directory passwords using their current credentials. This is what my API is doing:

1- Create LDAP connect and bind to LDAP server:

tls_config = Tls(validate=ssl.CERT_NONE) server = Server(ldaps_endpoint, port = 636, use_ssl = True, tls = tls_config) connection = Connection(server, user=username, password=password, authentication='NTLM') connection.bind()

2- Change password using extend.microsoft.modifyPassword.ad_modify_password() ldap3 function:

user_modified = extend.microsoft.modifyPassword.ad_modify_password(connection, user_dn, new_password, current_password)

This works fine when the user flag change password on next logon is not set. When it is set, it does not work because the connection fails to bind(). I tried using an ANONYMOUS connection instead of NTLM which binds successfully. However, the ad_modify_password() function fails with:

In order to perform this operation a successful bind must be completed on the connection

How is ad_modify_password() supposed to work with change password on next logon flag?

[1] https://ldap3.readthedocs.io/

RonaDona
  • 912
  • 6
  • 13
  • 30

1 Answers1

0

It's not -- and that's not something particular to Python. Microsoft shipped an ASP-based user password change web site you could run on a domain controller, and MS's site had the same limitation. If the user's password had already expired, or the user needed to change their password on next logon, you're stuck.

Two approaches:

(1) Build a self-service password reset functionality that authenticates the user against something other than their AD account password -- hashed attributes stored on their user object, challenge/response questions that get stored in a database table, etc. Provided the user passes the secondary authentication, admin credentials are used to reset the password.

(2) Specifically for users that must change password on next logon, the pwdLastSet attribute will be set to '0' when the user must change their password. Using a system credential with 'write' access to the value, modify that value to -1. Then bind with the account and password the user has supplied. If the bind fails, set pwdLastSet back to 0. If the bind passes, change the password.

#1 is more time/effort, but sorts people with expired passwords, people who need to change password on next login, people who are locked out, and people who have forgotten their password. The "people who have forgotten their password / gotten locked out" tend to be the big win -- reducing help desk calls can offset the time/money put into self-service password reset development.

#2 is far simpler but only handles the single scenario you present. If there's a max password age defined for the domain (or a fine grained password policy that establishes a max password age for some user accounts), users with expired creds may still be stuck.

LisaJ
  • 1,666
  • 1
  • 12
  • 18
  • Thanks LisaJ. Actually you can do this with Microsoft's password reset tool that comes with RDWeb role. Tried on Windows Server 2016: ``` Install-WindowsFeature RDS-Web-Access -IncludeAllSubFeature $configFile = "C:\Windows\Web\RDWeb\Pages\Web.config" (Get-Content $configFile) -replace 'key="PasswordChangeEnabled" value="false"', 'key="PasswordChangeEnabled" value="true"' | Out-File $configFile -Force ``` I think the reason it works is because the tool uses Kerberos while I used NTLMv2 over LDAPS. I won't be investigating the issue further, and will stick to this tool instead. – RonaDona Oct 03 '18 at 23:35