Changing Active Directory user password with PHP script - active-directory

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.

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

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)

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');

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.

$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);
}

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);

Related

phpmyadmin: Connection failed: Unknown database.

I'm a beginner and a teacher trying to make a quiz and connect to the database. I'm using MAMP 7.0.6, also installed XAMPP on my window. I had a database called "students" on phpmyadmin and a script like this:
<?php
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "students";
// Create connection
$conn = new mysqli($servername, $username, $password, $dbname);
// Check connection
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
echo "Connected successfully";
?>
It works fine when I tried to connect to the database without the $dbname, however, when I included that it says "Connection failed: Unknown database 'students'". I'm sure the name and username, password and stuff is correct, even though I don't know why the password has to be "" instead of the one shown on my phpmyadmin, but it works so I'll just leave it for later unless some of you would like to spend the time to explain it to me.
I just want to make this work asap coz it has been bugging me for a few days and I still can't find the solution online. I need to get the quiz done by mid August so thank you so much in advance!!!!

How to upload database using Dropbox?

I've just created a Barcode Scanner app and have a database of barcode. I want to upload/sync this database to the company's server then another programmer can get and build website UI. Unfortunately, our server is not public (but it can connect internet through proxy), so I want to use Dropbox to do that. Could you please give me a useful sample code or tell me the best way to upload/sync database in this case? I am extremely grateful for your help!
Alright, assuming your database is a MySQL DB with a host environment that lets you run cron jobs, access your FTP, etc... here's a possible code snippet for you, I just had to do this myself with the Dropbox API, you can actually read the full post here for a walk-thru (Dropbox API and MySQL DB Dump/Upload
<?php
# Include the Dropbox SDK libraries
require_once __DIR__."/dropbox-sdk/lib/Dropbox/autoload.php";
use \Dropbox as dbx;
//your access token from the Dropbox App Panel
$accessToken = 'NOT-A-REAL-TOKEN-REPLACE-THIS-QM8jS0z1w1t-REPLACE-THIS-TOKEN';
//run the MySQL dump and zip;
// location of your temp directory
$tmpDir = "your_temp_dir";
// username for MySQL
$user = "DB_user";
// password for MySQL
$password = "DB_password";
// database name to backup
$dbName = "DB_name";
// hostname or IP where database resides
$dbHost = "your_hostname";
// the zip file will have this prefix
$prefix = "sql_db_";
// Create the database backup file
$sqlFile = $tmpDir.$prefix.date('Y_m_d_h:i:s').".sql";
$backupFilename = $prefix.date('Y_m_d_h:i:s').".tgz";
$backupFile = $tmpDir.$backupFilename;
$createBackup = "mysqldump -h ".$dbHost." -u ".$user." --password='".$password."' ".$dbName." --> ".$sqlFile;
//echo $createBackup;
$createZip = "tar cvzf $backupFile $sqlFile";
//echo $createZip;
exec($createBackup);
exec($createZip);
//now run the DBox app info and set the client; we are naming the app folder SQL_Backup but CHANGE THAT TO YOUR ACTUAL APP FOLDER NAME;
$appInfo = dbx\AppInfo::loadFromJsonFile(__DIR__."/config.json");
$dbxClient = new dbx\Client($accessToken, "SQL_Backup");
//now the main handling of the zipped file upload;
//this message will send in a system e-mail from your cron job (assuming you set up cron to email you);
echo("Uploading $backupFilename to Dropbox\n");
//this is the actual Dropbox upload method;
$f = fopen($backupFile, "rb");
$result = $dbxClient->uploadFile('/SQL_Backup/'.$backupFilename, dbx\WriteMode::force(), $f);
fclose($f);
// Delete the temporary files
unlink($sqlFile);
unlink($backupFile);
?>
You also need to make a config.json file like so:
{
"key": "YOUR_KEY_FROM_DROPBOX_APP_PANEL",
"secret": "YOUR_SECRET_FROM_DROPBOX_APP_PANEL"
}
You will need to create a new Dropbox app under your Dropbox account to get your key and secret, and to generate the auth code for your username, do that here when logged in: https://www.dropbox.com/developers/apps
You also need to download the Dropbox PHP SDK library to put on your server in the same folder as this PHP code above, find that here: https://www.dropbox.com/developers/core/sdks/php
Hope this helps; if you need more step-by-step or your developer does, go that link at the top for a full walk through.

How to allow local scripts to run, with Auth enabled?

I am using SWFUpload and some other scripts that print and download PDF's.
The thing is that the machine tries to access a page in order to generate the data for the scripts, but it hits the Authorization requirements.
I am using a custom made ip check for this scripts and put them in $this->Auth->allow().
I would like to allow all requests from 127.0.0.1.
What is the correct workflow, for custom scripts?
Should I allow() all methods and checkIP() or should I make a user for the system and authorize him. I find the last one a little bit to much just for a local connection!
Ok, so I figured out the solution, as it follows:
I wrote an ip check method in core class Security
public static function checkIPBased($userId = false, $ipPool = array()) {
$permission = FALSE;
//CHECK BY IP IN IP POOL
$ipAllowed = array_merge($ipPool, array(
'localhost',
'127.0.0.1',
'::1',
));
foreach ($ipAllowed as $ip):
if (strpos($_SERVER['REMOTE_ADDR'], $ip) === 0)
$permission = true;
endforeach;
//CHECK IF THE USER IS ACCESING HIS ORDER
if ($userId && (($userId == AuthComponent::user('id')) || ((int) AuthComponent::user('group_id') === 1)))
$permission = true;
return $permission;
}
After that I wrote in AppController in beforeFilter() the following code:
if (Security::checkIPBased())
$this->Auth->allow();
This way I am allowing the machine to access all methods while still using Auth Component!

Select doctrine config after login on Zend 1.11

everybody! I'm doing an application with several databases where each one belongs to one specific department.
So, I have on my Bootstrap.php the method _initDoctrine() that initialize the connection with DB (meanwhile, i have only one).
public function _initDoctrine() {
$this->getApplication()->getAutoloader()
->pushAutoloader(array('Doctrine', 'autoload'));
spl_autoload_register(array('Doctrine', 'modelsAutoload'));
$manager = Doctrine_Manager::getInstance();
$manager->setAttribute(Doctrine::ATTR_AUTO_ACCESSOR_OVERRIDE, true);
$manager->setAttribute(
Doctrine::ATTR_MODEL_LOADING, Doctrine::MODEL_LOADING_CONSERVATIVE
);
$manager->setAttribute(Doctrine::ATTR_AUTOLOAD_TABLE_CLASSES, true);
$doctrineConfig = $this->getOption('doctrine');
Doctrine::loadModels($doctrineConfig['models_path']);
$conn = Doctrine_Manager::connection($doctrineConfig['dsn'], 'doctrine');
$conn->setAttribute(Doctrine::ATTR_USE_NATIVE_ENUM, true);
return $conn;
}
In my login page, there's 3 options (databases) which the user will choose to connect.
So, there's anyway to init doctrine config after login??
PS: sorry my english

Need help debugging a custom authentication plugin for Moodle

I'm trying to authenticate against the user db of my website (CMS based) and it uses a slightly different approach at storing hashed passwords. It uses a randomly generated salt for each user. The salt is stored in the user db along with the hashed passwords. Hence, direct field-mapped authentication (as the External DB plugin does) won't work for me.
To start off, I just mirrored the DB plugin and modified the user_login() procedure to read the hashed password and the salt from the database and then hash the entered password again with the salt and match it up with the password in the database. Here's the code for my user_login() function
function user_login($username, $password) {
global $CFG;
$textlib = textlib_get_instance();
$extusername = $textlib->convert(stripslashes($username), 'utf-8', $this->config->extencoding);
$extpassword = $textlib->convert(stripslashes($password), 'utf-8', $this->config->extencoding);
$authdb = $this->db_init();
// normal case: use external db for passwords
// Get user data
$sql = "SELECT
*
FROM {$this->config->table}
WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."' ";
$authdb->SetFetchMode(ADODB_FETCH_ASSOC);
// No DB Connection
if ( !$rs = $authdb->Execute( $sql ) ) {
$authdb->Close();
print_error('auth_dbcantconnect','auth');
return false;
}
// No records returned
if( $rs->EOF ) {
$rs->Close();
$authdb->Close();
return false;
}
// Get password
$db_password = $rs->fields['user_password'];
$salt = $rs->fields['user_salt'];
// Close DB Conn
$rs->Close();
$authdb->Close();
// Return match
return sha1( $extpassword . $salt ) == $db_password;
}
But when I try to login, username / passwords corresponding to the website (CMS) database are failing. However, the password (for the same user) that was stored in Moodle earlier on (before I tried using this custom plugin) is getting me through.
That means, either my authentication routine is failing or moodle's internal db based auth mechanism is taking precedence over it.
I've enabled ADODB debug mode - but that isn't helping either. When I enable the debug output from Server settings, the error messages are being sent prior to the page headers. Thus the login page won't display at all.
I have all other forms of authentication turned off (except for Manual which can't be turned off) and my own.
Any ideas on how to solve this issue?
Can you confirm the order that the authentication pluggins are displayed? This will determine the order in which they are used. See..
http://docs.moodle.org/en/Manage_authentication
Either way, the behaviour you're seeing suggests that your code is returning false and the fall through logic described here...
http://moodle.org/mod/forum/discuss.php?d=102070
... and here...
http://docs.moodle.org/en/Development:Authentication_plugins
... is kicking in.
Have you tried returning "true" always from your plugin to ensure that it's being called. Then, you can start returning "true" based upon other things (hard coded usernames etc). This approach will allow you to get to the point where you are either continuing to fail or seeing more targetted failures. Are you sure, for example, that it's the user_login function and not the subsequent call to update_user_record that is failing?
Finally, are you sure you're generating the salted password in the exact same way that it was created in the first place? This would be, for me, the most likely cause of the problem. Can you take control of the creation of the salted password so that you own both creation of new users and authentication of users - this would ensure that you were in sync with how the salted password and hash were generated.

Resources