-
Notifications
You must be signed in to change notification settings - Fork 159
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Hash the login nonce for storage to avoid leaking it via DB access #453
Changes from all commits
5ff442a
f6a5019
0c01f88
58ff60f
702adc6
13f8dfa
9f5a9a1
1722119
5c293a4
c16e184
1ab863c
6db97cd
6844ad6
b146d33
b1b106b
0cafae8
1dfe473
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -550,7 +550,7 @@ public static function backup_2fa() { | |
} | ||
|
||
if ( true !== self::verify_login_nonce( $user->ID, $nonce ) ) { | ||
wp_safe_redirect( get_bloginfo( 'url' ) ); | ||
wp_safe_redirect( home_url() ); | ||
exit; | ||
} | ||
|
||
|
@@ -728,6 +728,17 @@ public static function login_url( $params = array(), $scheme = 'login' ) { | |
return add_query_arg( $params, site_url( 'wp-login.php', $scheme ) ); | ||
} | ||
|
||
/** | ||
* Get the hash of a nonce for storage and comparison. | ||
* | ||
* @param string $nonce Nonce value to be hashed. | ||
* | ||
* @return string | ||
*/ | ||
protected static function hash_login_nonce( $nonce ) { | ||
return wp_hash( $nonce, 'nonce' ); | ||
} | ||
|
||
/** | ||
* Create the login nonce. | ||
* | ||
|
@@ -737,15 +748,21 @@ public static function login_url( $params = array(), $scheme = 'login' ) { | |
* @return array | ||
*/ | ||
public static function create_login_nonce( $user_id ) { | ||
$login_nonce = array(); | ||
$login_nonce = array( | ||
'expiration' => time() + HOUR_IN_SECONDS, | ||
); | ||
|
||
try { | ||
$login_nonce['key'] = bin2hex( random_bytes( 32 ) ); | ||
} catch ( Exception $ex ) { | ||
$login_nonce['key'] = wp_hash( $user_id . wp_rand() . microtime(), 'nonce' ); | ||
} | ||
$login_nonce['expiration'] = time() + HOUR_IN_SECONDS; | ||
|
||
if ( ! update_user_meta( $user_id, self::USER_META_NONCE_KEY, $login_nonce ) ) { | ||
// Store the nonce hashed to avoid leaking it via database access. | ||
$login_nonce_stored = $login_nonce; | ||
$login_nonce_stored['key'] = self::hash_login_nonce( $login_nonce['key'] ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here we hash the nonce for storage and pass the original to the form. |
||
|
||
if ( ! update_user_meta( $user_id, self::USER_META_NONCE_KEY, $login_nonce_stored ) ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Store the nonce with the key hashed. |
||
return false; | ||
} | ||
|
||
|
@@ -775,16 +792,19 @@ public static function delete_login_nonce( $user_id ) { | |
*/ | ||
public static function verify_login_nonce( $user_id, $nonce ) { | ||
$login_nonce = get_user_meta( $user_id, self::USER_META_NONCE_KEY, true ); | ||
if ( ! $login_nonce ) { | ||
|
||
if ( ! $login_nonce || empty( $login_nonce['key'] ) || empty( $login_nonce['expiration'] ) ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Account for missing nonce params in the DB. This should never happen in normal circumstance. |
||
return false; | ||
} | ||
|
||
if ( $nonce !== $login_nonce['key'] || time() > $login_nonce['expiration'] ) { | ||
self::delete_login_nonce( $user_id ); | ||
return false; | ||
if ( hash_equals( $login_nonce['key'], self::hash_login_nonce( $nonce ) ) && time() < $login_nonce['expiration'] ) { | ||
return true; | ||
} | ||
|
||
return true; | ||
// Require a fresh nonce if verification fails. | ||
self::delete_login_nonce( $user_id ); | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
|
@@ -806,7 +826,7 @@ public static function login_form_validate_2fa() { | |
} | ||
|
||
if ( true !== self::verify_login_nonce( $user->ID, $nonce ) ) { | ||
wp_safe_redirect( get_bloginfo( 'url' ) ); | ||
wp_safe_redirect( home_url() ); | ||
exit; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,27 +10,32 @@ | |
"issues": "https://github.com/WordPress/two-factor/issues" | ||
}, | ||
"config": { | ||
"sort-packages": true, | ||
"platform": { | ||
"php": "5.6.20" | ||
}, | ||
"allow-plugins": { | ||
"roots/wordpress-core-installer": true, | ||
"dealerdirect/phpcodesniffer-composer-installer": true | ||
"dealerdirect/phpcodesniffer-composer-installer": true, | ||
"roots/wordpress-core-installer": true | ||
} | ||
}, | ||
"extra": { | ||
"wordpress-install-dir": "wordpress" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was needed after updating to |
||
}, | ||
"require": { | ||
"php": ">=5.6" | ||
}, | ||
"require-dev": { | ||
"php-coveralls/php-coveralls": "^2.4", | ||
"wp-coding-standards/wpcs": "^2.3", | ||
"roots/wordpress": "^5.9", | ||
"wp-phpunit/wp-phpunit": "^5.9", | ||
"phpunit/phpunit": "^5", | ||
"yoast/phpunit-polyfills": "^1.0", | ||
"phpcompatibility/phpcompatibility-wp": "^2.1", | ||
"automattic/vipwpcs": "^2.3", | ||
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.2", | ||
"automattic/vipwpcs": "^2.3" | ||
"php-coveralls/php-coveralls": "^2.5", | ||
"phpcompatibility/phpcompatibility-wp": "^2.1", | ||
"phpunit/phpunit": "^5", | ||
"roots/wordpress-core-installer": "^1.100", | ||
"roots/wordpress-full": "^6.0", | ||
"wp-coding-standards/wpcs": "^2.3", | ||
"wp-phpunit/wp-phpunit": "^6.0", | ||
"yoast/phpunit-polyfills": "^1.0" | ||
}, | ||
"scripts": { | ||
"lint": "phpcs", | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need a nonce that is valid for an hour? How about we reduce it to 10 minutes or something like that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in #473 👍🏻