1

I am trying to get a very simple PHP script to change a user password in my Active Directory domain.

Here is the script I found some where online:

<?php
$uid = 'Mohammed Noureldin';
$newPassword = '5omeGoodP@ssword';
$bindDn = 'CN=Administrator,OU=UsersOU,DC=example,DC=local';
$bindPassword = 'An0therGoodP@ssword';
$baseDn = 'OU=UsersOU,DC=example,DC=local';
$protocolVersion = 3;

$ldap = ldap_connect('localhost');
if (!ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, $protocolVersion))
{
    exit('Failed to set protocol version to '.$protocolVersion);
}
// bind anonymously so that we can verify if the server really is running
ldap_bind($ldap);
if (ldap_errno($ldap) !== 0)
{
    exit('Could not connect to LDAP server');
}

// now bind with the correct username and password
ldap_bind($ldap, $bindDn, $bindPassword);
if (ldap_errno($ldap) !== 0)
{
    exit('ERROR: '.ldap_error($ldap));
}

$searchResults = ldap_search($ldap, $baseDn, 'cn='.$uid);
// no matching records
if ($searchResults === false)
{
    exit('No user found');
}

if (!is_resource($searchResults))
{
    exit('Error in search results.');
}
// create the unicode password
$len = strlen($newPassword);
$newPass = '"';
for ($i = 0; $i < $len; $i++)
{
    $newPass .= "{$newPassword{$i}}\000";
}
$newPass .= '"';

$entry = ldap_first_entry($ldap, $searchResults);
if (!is_resource($entry))
{
    exit('Couldn\'t get entry');
}
$userDn = ldap_get_dn($ldap, $entry);
if (!$userDn)
{
exit('Errrrrrrrrr1');

}
if (!ldap_modify($ldap, $userDn, array('unicodePwd' => $newPass)))
{
exit(ldap_errno($ldap)." ". ldap_error($ldap));

}
?>

The output of this PHP page was this error message:

53 Server is unwilling to perform

And the script simply didn't work (the password of the user was NOT changed).

I know the main principle that AD stores the passwords in unicodePwd field (if that is still the case till now), and I knew that I have to use secure connection and I am using it (hopfully it is correctly setup).

I googled about that error message but I couldn't find any functional solution.

I also tried some other scripts but this one was the best till now because the others gave me some errors in some previous steps (for example binding).

I really appreciate any help to solve that problem, or even another functional script may be a good idea! Thanks in advance.

4
  • This code is not connecting to the LDAP server securely. See the documentation Commented Mar 12, 2016 at 4:34
  • If I am using port 636 and ldaps over a third party application (Apache Directory Studio), and I am able to connect, doesn't that mean I am using the secure connection correctly? Commented Mar 12, 2016 at 4:36
  • That only means your third party application is using the secure connection correctly. It doesn't mean that the code you've written is doing so! Commented Mar 12, 2016 at 4:36
  • Ok so could you help me to make my code to use the secure connection? what I am missing? Commented Mar 12, 2016 at 4:37

6 Answers 6

6

You may not change a password using this method unless you connect over SSL/TLS. If you Google or Bing for the word unicodePwd, which you already knew because you included it in your post, one of the first if not the first result will be the MSDN documentation for unicodePwd, which states within the first three sentences:

This attribute is written by an LDAP Modify under the following restricted conditions. Windows 2000 operating system servers require that the client have a 128-bit (or better) SSL/TLS-encrypted connection to the DC in order to modify this attribute. On Windows Server 2003 operating system, Windows Server 2008 operating system, Windows Server 2008 R2 operating system, Windows Server 2012 operating system, Windows Server 2012 R2 operating system, and Windows Server 2016 Technical Preview operating system, the DC also permits modification of the unicodePwd attribute on a connection protected by 128-bit (or better) Simple Authentication and Security Layer (SASL)-layer encryption instead of SSL/TLS. In Windows Server 2008, Windows Server 2008 R2, Windows Server 2012, Windows Server 2012 R2, and Windows Server 2016 Technical Preview, if the fAllowPasswordOperationsOverNonSecureConnection heuristic of the dSHeuristics attribute (section 6.1.1.2.4.1.2) is true and Active Directory is operating as AD LDS, then the DC permits modification of the unicodePwd attribute over a connection that is neither SSL/TLS-encrypted nor SASL-encrypted. The unicodePwd attribute is never returned by an LDAP search.

If you just perform a simple search for unicodePwd, again one of the very first results you'll get is STEP BY STEP CODE on how to do this:

https://support.microsoft.com/en-us/kb/269190

Sign up to request clarification or add additional context in comments.

2 Comments

I am already using SSL (hopfully it is correctly setup), I edited my question. Anyway, is there another method to change the password without using SSL?
Based on the code that you posted, I do not believe you that you are using SSL. There's no ldaps://, no :636, no LDAPStatus = ldap_get_option(LDAPConnection, LDAP_OPT_SSL, (void *)&SSLOption);
2

Realise I'm a year late to this party; but having discovered this post whilst troubleshooting a similar problem...

Suspect ldaps is/was providing a certificate that wasn't trusted by the server hosting this php script (linux?); OP mentions having changed the code to use ldaps and getting to exit('Could not connect to LDAP server'); but connecting OK via Apache Directory Studio which may be on a machine that DOES trust the certificate (ie a domain joined workstation that trusts the DC).

To fix this proper, investigate your Private Key Infrastructure (a big topic, start here : https://social.technet.microsoft.com/wiki/contents/articles/2980.ldap-over-ssl-ldaps-certificate.aspx)

Alternatively, just tell the php server to trust the LDAP self-signed cert as see: Authenticating a self-signed certificate for LDAPS connection

To verify this is the case before embarking (ie not recommended in production) consider configuring LDAP on the php host (assumed linux server) to ignore errors caused by the certificate authority/trust by doing putting

TLS_REQCERT never

at the end of /etc/ldap/ldap.conf (restart apache / webserver after change)

Comments

2

You're creating the unicode password wrong. Here is code that works at least for me on Server 2012.

// create the unicode password
$newpassword = "\"" . $newpassword . "\"";
$len = strlen($newpassword);
for ($i = 0; $i < $len; $i++) $newpass .= "{$newpassword{$i}}\000";
$entry["unicodePwd"] = $newpass;

// Modify the password
if (ldap_mod_replace($ldap, $userDn, $entry))
{
  exit("Successfully updated password!");
}

Notice how I encode the " with the new password, that was the trick that got it working for me.

3 Comments

This isn't necessary because he is putting his quotes in single ticks. '"' is virtually the same as "\""
@JustinE In fact, yes, it is necessary, because in the code posted in the question the quotation marks are not included in the character encoding loop, unlike the code in this answer which is encoding the quotation marks, producing the correct value for the new password.
Nowadays, we can get rid of the loop to encode the new password and use one of the following 2 functions: iconv("UTF-8", "UTF-16LE", '"' . $newPassword . '"'); or mb_convert_encoding('"' . $newPassword . '"', 'UTF-16LE');
1

As Ryan Ries has noted, you must make a secure connection in order to change a password, but the code you've posted does not do so.

The problematic code is:

$ldap = ldap_connect('localhost');

As you can see, this makes a non-secure connection.

To make a secure connection, you need to specify an LDAPS URI:

$ldap = ldap_connect('ldaps://localhost');

2 Comments

When I tried your suggestion I got this error: Could not connect to LDAP server
Try to use the correct address of the domain controller. It probably is not running on localhost.
0
$server = "ldaps://172.1.200.1:636";
$dn = "dc=srv,dc=world";
$message = array();

function changePassword($server,$dn,$user,$oldPassword,$newPassword,$newPasswordCnf){
  global $message;

  error_reporting(0);
  putenv('LDAPTLS_REQCERT=allow');
  $con=ldap_connect($server);
  ldap_set_option($con, LDAP_OPT_PROTOCOL_VERSION, 3);
  ldap_set_option($con, LDAP_OPT_REFERRALS, 0);

  $findWhat = array ("cn","mail");
  $findWhat = array();
  $findWhere = $dn;
  $findFilter = "(sAMAccountName=$user)";

  $ldaprdn = 'mydomain' . "\\" . $user;
  $ldapbind = ldap_bind($con, $ldaprdn, $oldPassword);
  if ($ldapbind) {
      $message[] = "ldapbind  ($ldapbind) with sAMAccountName=$user";
  } else {
    $error = ldap_error($con);
    $errno = ldap_errno($con);
    $message[] = "ldapbind error $errno - $error";
    ldap_close($con);
    return false;
  }
  $sr = ldap_search($con,$dn,$findFilter,$findWhat);
  $records = ldap_get_entries($con, $sr);

 if ($records["count"] != "1") {
    $message[] = "Error E100 - Wrong user or password.";
    return false;
  }else {
    $message[] = "Found user <b>".$records[0]["cn"][0]." DN=".$records[0]["dn"]." </b>". print_r($records[0],true);
  }


  $entry = array();
  #seems a more correct way that handles complicated characters
  $entry["unicodePwd"] = iconv("UTF-8", "UTF-16LE", '"' . $newPassword . '"');
  # base64_encode is only needed in ldif text files !
  #$entry["unicodePwd"] = base64_encode($newPassw);

  $result = ldap_modify($con,$records[0]["dn"],$entry);
  if ($result === false){
    $message[] = $newpass.",".$entry["unicodePwd"]." Your password was not changed . with:".print_r($result, true);
    $error = ldap_error($con);
    $errno = ldap_errno($con);
    $message[] = "$errno - $error";
  }
  else {
    $message[] = " Your password has been changed. ";
    //mail($records[0]["mail"][0],"Password change notice : ".$user,"Your password has just been changed.");
    }
ldap_close($con);
}

2 Comments

the script is not fully tested , but since you ask how , I use ldaps://
use iconv("UTF-8", "UTF-16LE", '"' . $newPassword . '"') seems I have better results
0

I'm running SAMBA 4.11 as AD/DC and had to use ldap_modify_batch to get this working.

    $modifs = [
            [
                    "attrib"  => "unicodePwd",
                    "modtype" => LDAP_MODIFY_BATCH_REMOVE,
                    "values"  => [iconv("UTF-8", "UTF-16LE", '"' . $curPassword . '"')],
            ],
            [
                    "attrib"  => "unicodePwd",
                    "modtype" => LDAP_MODIFY_BATCH_ADD,
                    "values"  => [iconv("UTF-8", "UTF-16LE", '"' . $newPassword . '"')],
            ],
    ];
    $result = ldap_modify_batch($ldapconn, $dn, $modifs);
    error_log("Batch modify result:".print_r($result, true));
    $errorMessage = ldap_errno($ldapconn)." ". ldap_error($ldapconn);
    error_log("$errorMessage:".$errorMessage);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.