PHP LDAP Multiple Binds - php

Quite new to PHP and LDAP here, looking for some assistance with a personal project (trying to teach myself!).
I would like to password protect certain pages on our website using a simple login box.
My login.php page contains a simple login form with username and password inputs. I have managed to get the login process working using the code below. I am able to use my Active Directory username/pass to login via this form and proceed to the desired page, no issues.
However I'm not sure if I am doing it the 'correct' way.
My code;
ldap.php
session_start();
function authenticate($user, $password) {
if(empty($user) || empty($password)) return false;
$ldaphost = "ad.example.com";
$ldap_dn = "DC=ad,DC=example,DC=com";
$ldap_user_group = "Staff";
$ldap_usr_dom = '#ad.example.com';
$ldap = ldap_connect($ldaphost);
if($bind = ldap_bind($ldap, $user.$ldap_usr_dom, $password) or die ("Error: ".ldap_error($ldap))) {
$filter = "(sAMAccountName=".$user.")";
$attr = array("");
$result = ldap_search($ldap, $ldap_dn, $filter) or exit("Unable to search LDAP server") or die ("Error searching: ".ldap_error($ldap));
$entries = ldap_get_entries($ldap, $result);
ldap_unbind($ldap);
}
foreach($entries[0]['memberof'] as $grps) {
if(empty($grps) || empty($ldap_user_group)) return false;
if(strpos($grps, $ldap_user_group)) {
$access = 1;
} else {
}
}
if($access != 0) {
$_SESSION['user'] = $user;
$_SESSION['access'] = $access;
return true;
} else {
return false;
}
}
I've been told (by someone else) that this particular LDAP authentication process should work in two steps, as follows;
A search is made for the entered user name. I would recommend you use a search user DN and password for this – a user that has search permissions. It binds with these credentials before making the search. If the search succeeds it retrieves the DN of the found user and the search attribute which will later be used to look up the member record.
A second bind is then made with the retrieved user DN and the entered password. If this bind succeeds then the user is authenticated.
My questions are;
Is the above statement correct?
Are two 'binds' necessary?
Can't I just bind the LDAP connection with the credentials the user entered?
Any advice is appreciated, I'm struggling to get my head round the authentication process really :s

Short Answers:
yes
yes
no
Long answer:
Currently you can only bind with the users username and email-address. And that only works with AD as backend. So when you want to do an AD-Authenticator that's OK. But you specificslly asked for an LDAP-Authenticator. And an LDAP bind only works witha DN as the "username". As most of your users will not know that it's easier for them to remember an email-address or a username for a login. So you will need to find the DN to the users login-data. So you will need to do a search in the LDAP and for that you have to bind. So to bind as the user you need to bind... To get around that circular dependency you need to bind first as someone that has read access to the Directory and use that session to find the DN of the user. When found you use that DN and the user-provided password to do a second bind to verify the users credentials.
I did a talk about that just two days ago at zendcon. You can find the slides with some examples at https://heiglandreas.github.io/slidedeck/Directory_Authentication_with_LDAP/20161019%20-%20zendcon/index_online.html

Related

how to filter ldap active directory users information based on IIS windows authentication and display that information in php website?

I'm creating an intranet PHP website without any login requirements. I have an IIS application (based on PHP), authentication is done with Windows Authentication (Anonymous Authentication is disabled). I've successfully managed to set up IIS and windows authentication with some GPO tweaks. My simple PHP page contains $_SERVER['REMOTE_USER']; so active directory user without any login prompts can see DOMAIN\User.Name
In my understanding IIS Windows authentication is very limited and can only display the user's name and domain name. So I enabled LDAP to display more information about the user such as display name or phone number. But I'm stuck here because as far as I know, LDAP uses username and password bind to retrieve information. When I use active directory admin credentials it gives me a table of all user's information but how to filter that table to display only current user information (based on windows authentication).
Code:
<?php
$current_user = get_current_user();
$ldap_password = 'AdminPassword';
$ldap_username = 'Administrator#domain.name';
$ldap_connection = ldap_connect("domain.name");
if (FALSE === $ldap_connection){
echo 'ERROR';
}
ldap_set_option($ldap_connection, LDAP_OPT_PROTOCOL_VERSION, 3) or die('Unable to set LDAP protocol version');
ldap_set_option($ldap_connection, LDAP_OPT_REFERRALS, 0);
if (TRUE === ldap_bind($ldap_connection, $ldap_username, $ldap_password)){
$ldap_base_dn = 'OU=Users,DC=domain,DC=name';
$search_filter = '(|(objectCategory=person)(objectCategory=contact))';
$result = ldap_search($ldap_connection, $ldap_base_dn, $search_filter);
if (FALSE !== $result){
$entries = ldap_get_entries($ldap_connection, $result);
echo '<h2>Result</h2></br>';
echo '<table border = "1"><tr><td>Username</td><td>Last Name</td><td>First Name</td></tr>';
for ($x=0; $x<$entries['count']; $x++){
$LDAP_samaccountname = "";
if (!empty($entries[$x]['samaccountname'][0])) {
$LDAP_samaccountname = $entries[$x]['samaccountname'][0];
if ($LDAP_samaccountname == "NULL"){
$LDAP_samaccountname= "";
}
} else {
$LDAP_uSNCreated = $entries[$x]['usncreated'][0];
$LDAP_samaccountname= "CONTACT_" . $LDAP_uSNCreated;
}
//Last Name
$LDAP_LastName = "";
if (!empty($entries[$x]['sn'][0])) {
$LDAP_LastName = $entries[$x]['sn'][0];
if ($LDAP_LastName == "NULL"){
$LDAP_LastName = "";
}
}
//First Name
$LDAP_FirstName = "";
if (!empty($entries[$x]['givenname'][0])) {
$LDAP_FirstName = $entries[$x]['givenname'][0];
if ($LDAP_FirstName == "NULL"){
$LDAP_FirstName = "";
}
}
echo "<tr><td><strong>" . $LDAP_samaccountname ."</strong></td><td>" .$LDAP_LastName."</td><td>".$LDAP_FirstName."</td></tr>";
}
}
ldap_unbind($ldap_connection);
echo("</table>");
}
?>
EDIT: Managed to filter current user by editing LDAP filter:
$search_filter = "(|(objectCategory=persons)(sAMAccountName=*$current_user*))";
Your query is almost right, but it's working in a roundabout way :)
There is no objectCategory called persons. It's just person (no "s"). So objectCategory=persons is always false for every object on your domain. But it's working because you're using an OR (|).
So the only criteria it's really using is sAMAccountName=*$current_user*. But that's asking for any object where sAMAccountName contains $current_user. That has two unintended consequences:
If you have a user called neil, and another called oneil, then whenever neil logs in, you will find both accounts in that search.
Because your search criteria starts with a wildcard (*), it cannot use the index to find the account. That means that it has to look through every object on your domain to find a match. That might not matter if you have a small domain, but the more objects you have on your domain, the longer it will take.
IIS is giving you the exact username, so there is no need to make a "contains" comparison. So your query can be simplified to:
(sAMAccountName=$current_user)
Since sAMAccountName is an indexed attribute, that will be a super fast query.
You will often see the added criteria of limiting the search to user accounts, like this (notice the &):
(&(objectClass=user)(objectCategory=person)(sAMAccountName=$current_user))
But really, only users can authenticate to IIS, and the sAMAccountName is unique across all object types, so it doesn't really matter.

PHP LDAP secondary query after obtaining dn

Im working on adding authentication to one of my dashboards.
My setup is a little unique I believe. We use a service account to obtain the DN of a user, this query works as expected. We then bind a second time using that new dn instead of the service account. This also works, so technically at this point, the user is properly authenticated.
I'm trying to perform a second ldap_search after succesful bind as the dn I pull from the first query. This is unfortunately giving me the results of the previous ldap_search. This is what I'm not understanding.
if($bind = #ldap_bind($ldap, $ldap_dn, $adminpass)) {
// valid
echo "bound to ldap<BR>\n";
$filter = "(&(objectclass=user)(samaccountname=$user))";
$attr = array("dn, password, samaccountname");
$dn = "DC=CORP,DC=COMPANY,DC=com";
$result = ldap_search($ldap, $dn, $filter, $attr) or exit("Unable to search LDAP server");
$entries = ldap_get_entries($ldap, $result);
// Now build second query to bind and authenticate as user.
$ldap_dn_bind = $entries["0"]["dn"];
echo $ldap_dn_bind;
if($ubind = #ldap_bind($ldap, $ldap_dn_bind, $password)) {
echo "bound as $user - $ldap_dn_bind<BR>\n\n"; // Works
$u_attr = array("description, physicaldeliveryofficename, postaladdress, st, postalcode, title, telephonenumber, mobile, samaccountname, givenname, sn, company, displayname, employeetype, mail, manager, employeeID, KMADescription, terminationdate");
$u_result = ldap_search($ldap, $dn, $filter, $u_attr) or exit("Unable to search LDAP server");
echo "ldap search<BR>\n";
$u_entries = ldap_get_entries($ldap, $u_result);
echo "print u_entries";
print_r($u_entries);
echo "done";
} else {
die("failed to authenticate user");
}
This line:
$u_result = ldap_search($ldap, $dn, $filter, $u_attr) or exit("Unable to search LDAP server");
seems to work as desired and no error about performing the ldap search.
$u_entries however contains the same information as $entries and this is where I'm having a problem. I'm trying to obtain details about the user and insert them into a local db if they're not already present.
I had the same problem before, check if your LDAP server lets external connections in.
Check your $dn variable if you're using emails only enter the stuff after the # sign
Also be sure that your admin credentials
also this link helped me understand it a bit more:
https://github.com/Adldap2/Adldap2-Laravel/issues/224
Note i worked with Laravel
My attributes array was incorrect and by definition ldap_search will ALWAYS return the DN. Problem resolved.

Add password user using LDAP PHP

I searched every days for my problem, I tried many solutions and I didn't find... :(
I want to create an user using ldap_add with PHP. Working fine without enable account and without password. You find the code below.
Can you help me, please?
Config :
PHP 5.6
Windows Server 2012 R2 with AD
I can enable an account when I use $info["useraccountcontrol"]=544; but the account isn't with a password... User must loggon without password and type his new password at the first connection. *
I tried to add a password with $info['userPassword'] and chand useraccountontrol at 512 and I get this error :
ldap_add(): Add: Server is unwilling to perform
Here is my code :
<?php
$name = htmlspecialchars($_POST["name_build"]);
$lastname = htmlspecialchars($_POST["lastname_build"]);
$department = utf8_encode(htmlspecialchars($_POST["department_build"]));
$title = utf8_encode(htmlspecialchars($_POST["title_build"]));
$dn="CN=$name OU=Users, o=Domocom, c=net";
$ds = ldap_connect("192.168.1.1",389);
if ($ds) {
ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3); // IMPORTANT
ldap_bind($ds, "administrateur#domocom.net", "password");
// Prépareles données
$cn = $info["cn"] = "$lastname $name";
$info["sn"]="$name";
$info["givenname"]="$lastname";
$info["displayname"]="$lastname $name";
$info["name"]="$lastname $name";
$info["userprincipalname"]= "$lastname.$name#domocom.net";
$info["samaccountname"]= "$lastname.$name";
$info["title"]="$title";
$info["department"]="$department";
$info["mail"]="$lastname.$name#domocom.fr";
$info["postalcode"]="69009";
$info["objectClass"][0]="user";
//$info['userPassword'] = "password";
//$info["useraccountcontrol"]=544;
$r = ldap_add($ds,"CN=$cn,OU=Users,OU=Direction,OU=Domocom-SP,DC=domocom,DC=net", $info);
ldap_close($ds);
} else {
echo "unable to connect to ldap server";
}
?>
Thanks a lot.
PS : it's fake society for my school. :p
If it's an AD you might need to use a secure LDAP-Connection.
For that you'll need to call ldap_connect('ldaps://192.168.1.1:<port of the AD>');. Calling ldap_connect with two parameters is deprecated and should be avoided. Use it with an LDAP-URI!
You can also omit the if…else around the ldap_connect as it will return true in almost all cases. And a true return-value does not mean that a connection to the server actually as established. A connection is first established on the first ldap_-command that needs a connection which is typically ldap_bind.
And then you might want to have a look at Change AD password using PHP, Issue updating AD password using PHP and Change AD Password using PHP/COM/ADSI/LDAP

SQL query error?

I'm getting this error and I don't quite understand why. I've been going over this for hours now, tried looking into it via research, no luck.
In my PHP login system, I check if the row is selected:
//Start session
session_start();
//Include database connection details
require_once('config.php');
//Array to store validation errors
$errmsg_arr = array();
//Validation error flag
$errflag = false;
//Connect to mysql server
$link = mysql_connect(DB_HOST, DB_USER, DB_PASSWORD);
if(!$link) {
die('Failed to connect to server: ' . mysql_error());
}
//Select database
$db = mysql_select_db(DB_DATABASE);
if(!$db) {
die("Unable to select database");
}
//Prevent SQL injection.
function clean($str) {
$str = #trim($str);
if(get_magic_quotes_gpc()) {
$str = stripslashes($str);
}
return mysql_real_escape_string($str);
}
//Sanitize the POST values
$login = clean($_POST['login']);
$password = clean($_POST['password']);
//Input Validations
if($login == '') {
$errmsg_arr[] = 'Login ID missing';
$errflag = true;
}
if($password == '') {
$errmsg_arr[] = 'Password missing';
$errflag = true;
}
//If there are input validations, redirect back to the login form
if($errflag) {
$_SESSION['ERRMSG_ARR'] = $errmsg_arr;
session_write_close();
echo "input validation";
exit();
}
//Create query
$qry="SELECT * FROM details WHERE USERNAME='$login' AND PASSWORD='".md5($_POST['password'])."'";
$result=mysql_query($qry);
//Check whether the query was successful or not
if($result) {
if(mysql_num_rows($result) == 1) {
//Login Successful
session_regenerate_id();
$member = mysql_fetch_assoc($result);
$_SESSION['MEMBER_ID'] = $member['USERNAME'];
session_write_close();
header("location: client-index.php");
exit();
}else {
//Login failed
echo "login failed?";
exit();
}
}else {
die("Query failed");
}
?>
It echos "failed", for whatever reason.
If you told us what the error is, it might help us answer your question better.
But at any rate, this is emphatically the WRONG way to go about this. There are many serious problems with this system.
First of all, you're not sanitizing your inputs. Since you're mixing your data and commands together, all a user would have to do is enter a username of "x' or 1 = 1' --" to get into the system. Here's why: the only command the SQL server would get is "SELECT * FROM details WHERE USERNAME = 'x' or 1 = 1". In other words, if the username is x or 1 = 1 (which it is), then the SQL server would respond with a positive result. (The two dashes at the end of the "username" denote a comment in SQL, so everything after that in the query would be ignored).
A truly malicious attacker could even wreak havoc on your system by entering a username "x'--; DROP TABLES;', and your entire database would be gone. (See this comic for where I got this.)
In fact, you shouldn't even really be using mysql_query at all. According to the PHP documentation:
Use of this extension is discouraged. Instead, the MySQLi or PDO_MySQL extension should be used.
I would do a bit more reading on the subject if I were you. Even if this is just for practice, it's still best to get things right the first time. Look into PDO: it's not too hard to learn, yet quite useful. Its main advantage is that it does not mix data and commands, so you won't have the same problem of unsanitized inputs messing up your database.
Also, while it's good to see that you're hashing your passwords--and you'd be amazed at how many companies that should know better do not--MD5 is no longer considered cryptographically secure. It's relatively easy to get what's called a "hash collision," where two different plaintexts produce the same hash. Now, SHA-256 should be the minimum you use.
Also, on the subject of hashing, you should be adding something called salt. A salt is some kind of random text that you add to your plaintext in order to further obfuscate it. The reason for this is that there are what's called rainbow tables out there. A rainbow table is a list of pre-calculated hashes of all common passwords. If someone were to get a hold of your database, they could then compare all the passwords to rainbow tables to find their plaintexts.
Finally, in order to slow down brute force attacks--where an attacker tries all alphanumeric combinations until they get the password--you should also be using a loop where the hash algorithm gets re-calculated x number of times, usually between 1000 and 10000 times. PHP's crypt does this very nicely.
And BTW: don't feel bad. I've done all these things before, too. That's why I know that you shouldn't do them. Don't worry--you'll get there soon enough. Keep at it!
I am new to site so can not add comments maybe this wont help much but ill give it a go anyway
in your sql query it looks like you passing variable $login as text not variable value
$qry="SELECT * FROM details WHERE USERNAME='$login' AND PASSWORD='".md5($_POST['password'])."'";
and it should be
$qry="SELECT * FROM details WHERE USERNAME=".$login." AND PASSWORD='".md5($_POST['password'])."'";
is it a query failed or login failed?
anyway if query failed :
try to change your query in to this :
surrounds your fields with backtick (not single quote)
$qry="SELECT * FROM details WHERE `USERNAME`='$login' AND `PASSWORD`='".md5($_POST['password'])."'";
if login failed :
if(mysql_num_rows($result) == 1) {
//Login successful
}else {
//Login failed
}
are you sure that the query will only have 1 result? because with this condition, if results is greater than 1 it will also failed to login.

How can I change username or email or phone number information in LDAP?

I have this script through which I can change my LDAP password but I also want to change my username or full name or email or phone number. How can I do that? When I echo out the records I only get info like my name and email but what do I need to do to make ldap_modify change my full name or phone number or email or userid?
<?php
$server = "ldap://ldap";
$dn = "ou=People,DC=ssdfg,DC=sadad,DC=com";
$message = array();
function changePassword($server,$dn,$user,$oldPassword,$newPassword,$newPasswordCnf){
global $message;
error_reporting(0);
$con=ldap_connect($server);
ldap_set_option($con, LDAP_OPT_PROTOCOL_VERSION, 3);
$findWhat = array ("cn","mail");
$findWhere = $dn;
$findFilter = "(uid=$user)";
#bind anon and find user by uid
$sr = ldap_search($con,$dn,$findFilter,$findWhat);
$records = ldap_get_entries($con, $sr);
echo "<pre>";print_r($records);
/* error if found more than one user */
if ($records["count"] != "1") {
$message[] = "Error E100 - Wrong user.";
return false;
}else {
$message[] = "Found user <b>".$records[0]["cn"][0]."</b>";
}
/* try to bind as that user */
if (ldap_bind($con, $records[0]["dn"], $oldPassword) === false) {
$message[] = "Error E104 - Current password is wrong.";
return false;
}
else { }
if ($newPassword != $newPasswordCnf ) {
$message[] = "Error E101 - New passwords do not match! ";
return false;
}
if (strlen($newPassword) < 8 ) {
$message[] = "Error E102 - Your new password is too short! ";
return false;
}
if (!preg_match("/[0-9]/",$newPassword)) {
$message[] = "Error E103 - Your password must contain at least one digit. ";
return false;
}
if (!preg_match("/[a-zA-Z]/",$newPassword)) {
$message[] = "Error E103 - Your password must contain at least one letter. ";
return false;
}
/* change the password finally */
$entry = array();
$entry["userPassword"] = "{SHA}" . base64_encode( pack( "H*", sha1( $newPassword ) ) );
if (ldap_modify($con,$records[0]["dn"],$entry) === false){
$message[] = "E200 - Your password cannot be change, please contact the administrator.";
}
else {
$message[] = " Your password has been changed. ";
//mail($records[0]["mail"][0],"Password change notice : ".$user,"Your password has just been changed.");
}
}
?>
Change your $findwhat variable to have a * in it and you will get all of the user attributes that your account you are executing the search with can see. Note that anonymous might not be able to see much and it certainly won't be able to update much. My advice is to create an account in your directory that has all the privileges you will need and do all of your operations under that (except for authentication of course).
Modifying the other attributes should just be a matter of including them in your $entry array. You need to use the proper attribute names but you'll see them when you print them out after changing $findwhat to a *.
Also Apache Directory Studio is a nice free tool for working with directories. One thing cool about it is that you can view the search and modification logs and see the ldif operations it is sending to the directory. Then you can replicate that in your code.
Attributes must be requested unless the ALL_ATTRIBUTES value is used, often this is an asterisk, but not always. The directory server must allow clients to retrieve values, and userPassword is often restricted to users with more privileges.
To modify attribute values, construct a modify request with the distinguished name, attribute, and the new values.
There are couple things of note:
The LDAP client should check for response controls when the server transmits a response to a request. Failure to check for response controls will result in the LDAP client possibly missing important information from the server
Why does this client base 64 encode the password? Directory server should never accept pre-encoded passwords (servers should be configured to reject pre-encoded passwords) because the server cannot execute password quality and history checks on pre-encoded passwords. For these and other reasons, transmitting pre-encoded passwords are a terrible idea, and all clients should reject this practice. LDAP clients must use a secure connection (SSL, TLS, or ipsec) and transmit passwords in the clear or use an external SASL bind request. For even better security use the password modify extended request which requires the existing password, and can generate a password for the user.
This client does not appear to be able to respond to an unsolicited notification from the server. Directory Servers may transmit an extended result that is unsolicited, that is, not in response to a client request. Clients must be able to handle these notifications which are often notifications that the server disconnected the client for whatever reason.
For more information, please see "LDAP: Programming Practices".

Categories