From 4c5e337ce6077aeafcd5b25ea7b4d1f0ad0641cf Mon Sep 17 00:00:00 2001 From: Brian Lycett Date: Mon, 3 Aug 2020 17:35:13 +0100 Subject: [PATCH] Better passwords (#35) * Add support for stronger hashes (#34) * Add generate_salt function * Add suport for clear text passwords If someone wants to shoot themselves in the foot, they are free to do it * Add support for blowfish * Add support for extended DES * Add support for md5crypt * Fix salt generation call * Add support for sha256crypt * Add support for sha512crypt * Update previous functions * Add a default cause * Fix some shenanigans and log cleanup * Couple minor fixes * Let password hash checking be done in the password function * Update the README with new passwords * Change the default fallback to SSHA * Put crypt algos in an array ordered by preference so we can fail to the most secure algo available * Remove superfluous count++ * Updated password hashing code Co-authored-by: Angelin01 --- README.md | 73 +++++++++++----- www/account_manager/new_user.php | 2 +- www/includes/config.inc.php | 3 +- www/includes/ldap_functions.inc.php | 127 +++++++++++++++++++++++----- 4 files changed, 162 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 6783f38..083e665 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ LDAP User Manager -- -A PHP web-based interface for LDAP user account management and self-service password change. +This is a PHP LDAP account manager; a web-based GUI interface which allows you to quickly populate a new LDAP directory and easily manage user accounts and groups. It also has a self-service password change module. +It's designed to work with OpenLDAP and to be run as a container. It complements OpenLDAP containers such as [*osixia/openldap*](https://hub.docker.com/r/osixia/openldap/). -Purpose ---- -This presents a simple-to-use interface for setting up a new LDAP directory and managing user accounts and groups, as well as providing a way for users to change their own password. It's designed to complement OpenLDAP servers such as *osixia/openldap* (https://hub.docker.com/r/osixia/openldap/). +Features +--- * Setup wizard: this will create the necessary structure to allow you to add users and groups and will set up an initial admin user that can log into the user manager. * Group creation and management. @@ -35,6 +35,14 @@ Screenshots ![self_service_password_change](https://user-images.githubusercontent.com/17613683/59344258-9ffcab80-8d05-11e9-9dc2-27dfd373fcc8.png) +A note on your LDAP schema +--- + +By default this application will expect the LDAP server to be using the **RFC2307BIS** schema. OpenLDAP (including the **osixia/openldap** image) uses the old NIS schema as its default schema. The user manager will work with either, but RFC2307BIS is recommended as it allows you to use **memberOf** searches. You can enable RFC2307BIS in **osixia/openldap** by setting `LDAP_RFC2307BIS_SCHEMA` to `true` during the initial setup. The application is set to expect BIS by default for backwards-compatibility with older versions. + +If you prefer not to use RFC2307BIS then set `LDAP_USES_NIS_SCHEMA` to `TRUE`. This will create groups solely as the **posixGroup** objectclass, and the default for `LDAP_GROUP_MEMBERSHIP_USES_UID` will `TRUE`. + + Quick start --- @@ -53,9 +61,9 @@ docker run \ -e "LDAP_ADMIN_BIND_PWD=secret"\ -e "LDAP_USES_NIS_SCHEMA=true" \ -e "EMAIL_DOMAIN=example.com"\ - wheelybird/ldap-user-manager:v1.1 + wheelybird/ldap-user-manager:v1.2 ``` -Now go to https://lum.example.com/setup. +Change the variable values to suit your environment. You might need to change `LDAP_USES_NIS_SCHEMA` if you're using the BIS schema. Now go to https://lum.example.com/setup. Configuration @@ -64,17 +72,7 @@ Configuration Configuration is via environmental variables. Please bear the following in mind: * This tool needs to bind to LDAP as a user with permissions to modify everything under the base DN. - * This interface is designed to work with a fresh LDAP server and should be used with populated LDAP directories with caution and at your own risk. - -LDAP_USES_NIS_SCHEMA ----- - -By default this application will expect the LDAP server to be using the **RFC2307BIS** schema. OpenLDAP (including the **osixia/openldap** image) uses the old NIS schema as its default schema. The user manager will work with either, but RFC2307BIS is recommended as it allows you to use **memberOf** searches. You can enable RFC2307BIS in **osixia/openldap** by setting `LDAP_RFC2307BIS_SCHEMA` to `true` during the initial setup. - -If you prefer not to use RFC2307BIS then set `LDAP_USES_NIS_SCHEMA` to `TRUE`. This will create groups solely as the **posixGroup** objectclass, and the default for `LDAP_GROUP_MEMBERSHIP_USES_UID` will `TRUE`. The application is set to expect the BIS schema by default for backwards-compatibility with older releases. - - - + * This interface is designed to work with a fresh LDAP server and should be against populated LDAP directories with caution and at your own risk. Mandatory: ---- @@ -93,7 +91,7 @@ Optional: * `LDAP_USER_OU` (default: *people*): The name of the OU used to store user accounts (without the base DN appended). -* `LDAP_USES_NIS_SCHEMA` (default: *FALSE*): If you use the NIS schema instead of the (preferable) RFC2307BIS schema, set this to `TRUE`. See [LDAP_USES_NIS_SCHEMA](#LDAP_USES_NIS_SCHEMA) for more information. +* `LDAP_USES_NIS_SCHEMA` (default: *FALSE*): If you use the NIS schema instead of the (preferable) RFC2307BIS schema, set this to `TRUE`. See [A note on your LDAP schema](#a-note-on-your-ldap-schema) for more information. * `LDAP_GROUP_OU` (default: *groups*): The name of the OU used to store groups (without the base DN appended). * `LDAP_GROUP_MEMBERSHIP_ATTRIBUTE` (default: *memberUID* or *uniqueMember*): The attribute used when adding a user to a group. If `LDAP_USES_NIS_SCHEMA` is `TRUE` the default is `memberUID`, otherwise it's `uniqueMember`. Explicitly setting this variable will override the default. @@ -101,7 +99,7 @@ Optional: * `LDAP_REQUIRE_STARTTLS` (default: *TRUE*): If *TRUE* then a TLS connection is required for this interface to work. If set to *FALSE* then the interface will work without STARTTLS, but a warning will be displayed on the page. -* `LDAP_TLS_CACERT` (no default): If you need to use a specific CA certificate for TLS connections to the LDAP server (when `LDAP_REQUIRE_STARTTLS` is set) then assign the contents of the CA certificate to this variable. e.g. `-e LDAP_TLS_CERT=$(
- onkeyup="check_username_validity(document.getElementById('username').value); update_email();"> + onkeyup="check_entity_name_validity(document.getElementById('username').value,'username_div'); update_email();">
diff --git a/www/includes/config.inc.php b/www/includes/config.inc.php index 6bff5c7..68bfadb 100644 --- a/www/includes/config.inc.php +++ b/www/includes/config.inc.php @@ -43,8 +43,7 @@ $USERNAME_REGEX = (getenv('USERNAME_REGEX') ? getenv('USERNAME_REGEX') : '^[a-z][a-zA-Z0-9\._-]{3,32}$'); #We'll use the username regex for groups too. - $PASSWORD_HASH = (getenv('PASSWORD_HASH') ? getenv('PASSWORD_HASH') : 'SSHA'); - if ( ! in_array($PASSWORD_HASH, array('MD5','SMD5','SHA','SSHA','CRYPT'))) { $PASSWORD_HASH = 'SSHA'; } + if (getenv('PASSWORD_HASH')) { $PASSWORD_HASH = strtoupper(getenv('PASSWORD_HASH')); } $ACCEPT_WEAK_PASSWORDS = ((strcasecmp(getenv('ACCEPT_WEAK_PASSWORDS'),'TRUE') == 0) ? TRUE : FALSE); diff --git a/www/includes/ldap_functions.inc.php b/www/includes/ldap_functions.inc.php index 2dd112d..b5db533 100644 --- a/www/includes/ldap_functions.inc.php +++ b/www/includes/ldap_functions.inc.php @@ -140,40 +140,131 @@ function ldap_setup_auth($ldap_connection, $password) { } +################################# + +function generate_salt($length) { + + $permitted_chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ./'; + + mt_srand((double)microtime() * 1000000); + + $salt = ''; + while (strlen($salt) < $length) { + $salt .= substr($permitted_chars, (rand() % strlen($permitted_chars)), 1); + } + + return $salt; + +} + ################################## function ldap_hashed_password($password) { - global $PASSWORD_HASH; + global $PASSWORD_HASH, $log_prefix; - $permitted_chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; - $salt = substr(str_shuffle($permitted_chars), 0, 64); + $check_algos = array ( + "SHA512CRYPT" => "CRYPT_SHA512", + "SHA256CRYPT" => "CRYPT_SHA256", +# "BLOWFISH" => "CRYPT_BLOWFISH", +# "EXT_DES" => "CRYPT_EXT_DES", + "MD5CRYPT" => "CRYPT_MD5" + ); - switch (strtoupper($PASSWORD_HASH)) { + $remaining_algos = array ( + "SSHA", + "SHA", + "SMD5", + "MD5", + "CRYPT", + "CLEAR" + ); - case 'MD5': - $hashed_pwd = '{MD5}' . base64_encode(md5($password,TRUE)); - break; + $available_algos = array(); + + foreach ($check_algos as $algo_name => $algo_function) { + if (defined($algo_function) and constant($algo_function) != 0) { + array_push($available_algos, $algo_name); + } + else { + error_log("$log_prefix password hashing - the system doesn't support ${algo_name}"); + } + } + $available_algos = array_merge($available_algos, $remaining_algos); + + if (isset($PASSWORD_HASH)) { + if (!in_array($PASSWORD_HASH, $available_algos)) { + $hash_algo = $available_algos[0]; + error_log("$log_prefix LDAP password: the chosen hash method ($PASSWORD_HASH) wasn't available"); + } + else { + $hash_algo = $PASSWORD_HASH; + } + } + else { + $hash_algo = $available_algos[0]; + } + error_log("$log_prefix LDAP password: using '${hash_algo}' as the hashing method"); + + $hash_algo = 'SSHA'; + + switch ($hash_algo) { + + case 'SHA512CRYPT': + $hashed_pwd = '{CRYPT}' . crypt($password, '$6$' . generate_salt(8)); + break; + + case 'SHA256CRYPT': + $hashed_pwd = '{CRYPT}' . crypt($password, '$5$' . generate_salt(8)); + break; + +# Blowfish & EXT_DES didn't work +# case 'BLOWFISH': +# $hashed_pwd = '{CRYPT}' . crypt($password, '$2a$12$' . generate_salt(13)); +# break; + +# case 'EXT_DES': +# $hashed_pwd = '{CRYPT}' . crypt($password, '_' . generate_salt(8)); +# break; + + case 'MD5CRYPT': + $hashed_pwd = '{CRYPT}' . crypt($password, '$1$' . generate_salt(9)); + break; case 'SMD5': - $hashed_pwd = '{SMD5}' . base64_encode(md5($password.$salt,TRUE) . $salt); - break; + $salt = generate_salt(8); + $hashed_pwd = '{SMD5}' . base64_encode(md5($password . $salt, TRUE) . $salt); + break; + + case 'MD5': + $hashed_pwd = '{MD5}' . base64_encode(md5($password, TRUE)); + break; case 'SHA': - $hashed_pwd = '{SHA}' . base64_encode(sha1($password,TRUE)); - break; + $hashed_pwd = '{SHA}' . base64_encode(sha1($password, TRUE)); + break; case 'SSHA': - $hashed_pwd = '{SSHA}' . base64_encode(sha1($password.$salt,TRUE) . $salt); - break; + $salt = generate_salt(8); + $hashed_pwd = '{SSHA}' . base64_encode(sha1($password . $salt, TRUE) . $salt); + break; case 'CRYPT': - $hashed_pwd = '{crypt}' . crypt($password, $salt); - break; + $salt = generate_salt(2); + $hashed_pwd = '{CRYPT}' . crypt($password, $salt); + break; + + case 'CLEAR': + error_log("$log_prefix password hashing - WARNING - Saving password in cleartext. This is extremely bad practice and should never ever be done in a production environment."); + $hashed_pwd = $password; + break; + } + error_log("$log_prefix password update - algo $hash_algo | pwd $hashed_pwd"); + return $hashed_pwd; } @@ -669,11 +760,7 @@ function ldap_change_password($ldap_connection,$username,$new_password) { return FALSE; } - #Hash password - - $hashed_pass = ldap_hashed_password($new_password); - - $entries["userPassword"] = $new_password; + $entries["userPassword"] = ldap_hashed_password($new_password); $update = @ ldap_mod_replace($ldap_connection, $this_dn, $entries); if ($update) {