From bd1d64a1b98cab1b89aab13aa35f780326978f4b Mon Sep 17 00:00:00 2001 From: Nic Waller Date: Sun, 2 Jun 2013 22:13:34 -0700 Subject: [PATCH] Rewrite config.php with password hashes --- includes/auth.php | 57 +++++++++++++++++++++++++++++++++---- includes/functions-auth.php | 47 ++++++++++++++++++++++++------ includes/functions-html.php | 22 -------------- includes/load-yourls.php | 5 ++-- 4 files changed, 93 insertions(+), 38 deletions(-) diff --git a/includes/auth.php b/includes/auth.php index b760bad..84a9c1c 100644 --- a/includes/auth.php +++ b/includes/auth.php @@ -27,11 +27,56 @@ yourls_do_action( 'auth_successful' ); -// Deal with query string message added upon login -if( isset( $_GET['login_msg'] ) ) { - yourls_display_login_message(); +/* + * The following code is a shim that helps users store passwords securely in config.php + * by storing a password hash and removing the plaintext. + * + * TODO: Remove this once real user management is implemented. + */ +if ( isset( $_GET['pwhash'] ) ) { + switch ( $_GET['pwhash'] ) { + case 'always': + yourls_update_option('pwhash', 'always'); + yourls_add_notice( 'Password hashing turned on.' ); + break; + case 'never': + yourls_update_option( 'pwhash', 'never' ); + yourls_add_notice( 'Password hashing turned off.' ); + break; + case 'once': + yourls_update_option( 'pwhash', 'prompt' ); + $success = yourls_hash_passwords_now(); + if ( $success ) { + yourls_add_notice( 'Plaintext passwords were secured with hashing.' ); + } else { + yourls_add_notice( 'Password hashing failed.' ); + } + break; + } } -if ( isset( $_GET['pwhash'] ) && $_GET['pwhash'] === 'now' ) { - yourls_hash_passwords_now(); -} +if ( yourls_has_cleartext_passwords() ) { + if ( yourls_get_option( 'pwhash' ) === 'always' ) { + $success = yourls_hash_passwords_now(); + if ( $success ) { + yourls_add_notice( 'Plaintext passwords were converted to password hashes.' ); + } else { + yourls_add_notice( 'Password hashing failed.' ); + } + } else if ( yourls_get_option( 'pwhash' ) != 'never' ) { + $url_always = yourls_admin_url( 'index.php?pwhash=always' ); + $url_never = yourls_admin_url( 'index.php?pwhash=never' ); + $url_once = yourls_admin_url( 'index.php?pwhash=once' ); + $message = <<< EOT + Notice: Your password is stored insecurely in config.php. + Your installation of YOURLS can be made more securely by choosing to hash your passwords. + See UsernamePassword for details. + +EOT; + yourls_add_notice( $message, 'notice' ); + } +} \ No newline at end of file diff --git a/includes/functions-auth.php b/includes/functions-auth.php index f934388..ca28419 100644 --- a/includes/functions-auth.php +++ b/includes/functions-auth.php @@ -88,10 +88,6 @@ function yourls_is_valid_user() { // Login form : redirect to requested URL to avoid re-submitting the login form on page reload if( isset( $_REQUEST['username'] ) && isset( $_REQUEST['password'] ) ) { $url = $_SERVER['REQUEST_URI']; - // If password stored unencrypted, append query string. TODO: deprecate this when there's proper user management - if( !yourls_has_hashed_password( $_REQUEST['username'] ) ) { - $url = yourls_add_query_arg( array( 'login_msg' => 'pwdclear' ) ); - } yourls_redirect( $url ); } } @@ -136,6 +132,7 @@ function yourls_check_password_hash( $user, $submitted_password ) { if ( yourls_user_has_phppass( $user ) ) { $hasher = new PasswordHash(8, false); list( , $hash ) = explode( ':', $yourls_user_passwords[ $user ] ); + $hash = str_replace( '!', '$', $hash ); return ( $hasher->CheckPassword( $submitted_password, $hash ) ); } else if( yourls_has_hashed_password( $user ) ) { // Stored password is a salted hash: "md5:<$r = rand(10000,99999)>:" @@ -165,21 +162,55 @@ function yourls_user_has_phppass( $user ) { /** * Overwrite plaintext passwords in config file with hashed versions. + * This has the unfortunate side effect of invalidating the session cookie + * for any user whose password is changed. * @since 1.7 * @return true if overwrite was successful, otherwise false */ function yourls_hash_passwords_now() { global $yourls_user_passwords; $hasher = new PasswordHash(8, false); + $configdata = file_get_contents( YOURLS_CONFIGFILE ); + // TODO: check mode for writability foreach ( $yourls_user_passwords as $user => $pwvalue ) { if ( !yourls_user_has_phppass( $user ) && !yourls_has_hashed_password( $user ) ) { $clearpass = $pwvalue; - $hash = $hasher->HashPassword($clearpass); - // TODO: use sed/awk to rewrite entry in config file, carefully! - // something like this - // sed -i "s/'root' *=> *'1234234134'/'root' => '123412341241234'/" config.php + $hash = $hasher->HashPassword( $clearpass ); + // PHP would interpret $ as a variable, so replace it in storage. + $hash = str_replace( '$', '!', $hash ); + $pattern = "/'$user'[\t ]*=>[\t ]*'$clearpass'/"; + $replace = "'$user' => 'phpass:$hash'"; + $count = 0; + $configdata = preg_replace( $pattern, $replace, $configdata, -1, $count ); + // There should be exactly one replacement. Otherwise, fast fail. + if ( $count != 1 ) { + yourls_add_notice( $count . $pattern ); + return false; + } } } + $success = file_put_contents( YOURLS_CONFIGFILE, $configdata ); + if ( $success === FALSE ) { + yourls_add_notice( 'Failed writing password hashes to config.php' ); + return false; + } + return true; +} + +/** + * Check to see if any passwords are stored as cleartext. + * + * @since 1.7 + * @return bool true if any passwords are cleartext + */ +function yourls_has_cleartext_passwords() { + global $yourls_user_passwords; + foreach ( $yourls_user_passwords as $user => $pwdata ) { + if ( !yourls_has_hashed_password( $user ) && !yourls_user_has_phppass( $user ) ) { + return true; + } + } + return false; } /** diff --git a/includes/functions-html.php b/includes/functions-html.php index a5c6dce..92c710e 100644 --- a/includes/functions-html.php +++ b/includes/functions-html.php @@ -846,25 +846,3 @@ function yourls_l10n_calendar_strings() { yourls__( 'Today' ); yourls__( 'Close' ); } - -/** - * Display custom message based on query string parameter 'login_msg' - * - * @since 1.7 - */ -function yourls_display_login_message() { - if( !isset( $_GET['login_msg'] ) ) - return; - - switch( $_GET['login_msg'] ) { - case 'pwdclear': - $message = ''; - $message .= yourls__( 'Notice: your password is stored as clear text in your config.php' ); - $message .= ' ' . yourls__( 'You can improve the security of YOURLS right now by choosing to hash your passwords.' ); - $message .= ' ' . yourls__( 'See UsernamePassword for details.

' ); - $message .= ' ' . yourls__( 'Yes, hash all passwords now.' ); - yourls_add_notice( $message, 'notice' ); - break; - } -} - diff --git a/includes/load-yourls.php b/includes/load-yourls.php index 81fc535..92667f6 100644 --- a/includes/load-yourls.php +++ b/includes/load-yourls.php @@ -4,14 +4,15 @@ // Include settings if( file_exists( dirname( dirname( __FILE__ ) ) . '/user/config.php' ) ) { // config.php in /user/ - require_once( dirname( dirname( __FILE__ ) ) . '/user/config.php' ); + define( 'YOURLS_CONFIGFILE', dirname( dirname( __FILE__ ) ) . '/user/config.php' ); } elseif ( file_exists( dirname( __FILE__ ) . '/config.php' ) ) { // config.php in /includes/ - require_once( dirname( __FILE__ ) . '/config.php' ); + define( 'YOURLS_CONFIGFILE', dirname( __FILE__ ) . '/config.php' ); } else { // config.php not found :( die( '

Cannot find config.php.

Please read the readme.html to learn how to install YOURLS

' ); } +require_once( YOURLS_CONFIGFILE ); // Check if config.php was properly updated for 1.4 if( !defined( 'YOURLS_DB_PREFIX' ) ) -- 2.45.0