[wp-trac] [WordPress Trac] #48356: wp_create_nonce(...) and check_ajax_referer(...) fails on the 2nd AJAX call if that is two-action AJAX with AJAX-LOGIN as the first action
WordPress Trac
noreply at wordpress.org
Thu Oct 17 13:32:07 UTC 2019
#48356: wp_create_nonce(...) and check_ajax_referer(...) fails on the 2nd AJAX call
if that is two-action AJAX with AJAX-LOGIN as the first action
--------------------------+-----------------------------
Reporter: KestutisIT | Owner: (none)
Type: defect (bug) | Status: new
Priority: normal | Milestone: Awaiting Review
Component: General | Version: 5.2.3
Severity: major | Keywords: needs-patch
Focuses: javascript |
--------------------------+-----------------------------
So if I generate a new `SpecialPluginGlobals['AJAX_SECURITY']` variable
with `wp_create_nonce(..)` and register it as a global JavaScript variable
in AssetController.php:
{{{#!php
<?php
wp_localize_script('special-plugin-main', 'SpecialPluginGlobals',
array(
'AJAX_SECURITY' => wp_create_nonce('special-plugin-front-ajax-
nonce'),
));
}}}
And then I Have "Already have an account? Please login" section in the
page, where customers does login with it via class method call from
`SpecialPluginMain.js`:
{{{#!javascript
// Dynamic variables
if(typeof SpecialPluginGlobals === "undefined")
{
// The values here will come from WordPress script localizations,
// but in case if they wouldn't, we have a backup initializer below
var SpecialPluginGlobals = {};
}
// NOTE: For object-oriented language experience, this variable name
should always match current file name
var FleetManagementMain = {
globals: FleetManagementGlobals,
lang: FleetManagementLang,
vars: FleetManagementVars,
// <...> Some other JS class methods
doLogin: function ()
{
jQuery.ajax({
type: 'POST',
dataType: 'json',
url: this.globals['LOGIN_AJAX_URL'],
data:
{
'action': 'ajaxlogin', //calls wp_ajax_nopriv_ajaxlogin
'login_account_id_or_email': jQuery('.special-plugin-
search-summary-table form.login-form input.login-account-id-or-
email').val(),
'login_password': jQuery('.special-plugin-search-summary-
table form.login-form input.login-password').val(),
'ajax_security': this.globals['AJAX_SECURITY']
},
success: function(data)
{
jQuery('.special-plugin-search-summary-table .login-
result').text(data.message);
if (data.loggedIn === true)
{
// NOTE: We set customers dropdown immediately
//console.log('Logged in successfully!');
// Clear the guest customer lookup section
jQuery('.special-plugin-search-summary-table .guest-
customer-lookup-section').empty();
jQuery('.special-plugin-search-summary-table .login-
form').empty();
SpecialPluginMain.setCustomersDropdownHTML();
}
}
});
},
// <...> Other JS class methods like 'setCustomersDropdownHTML()'
};
}}}
Then the system will crash on second Ajax call which tries to pull the
list (make an HTML dropdown) of all customers with that Account ID
(WordPress user id).
I can sure probably try to send back the new $ajaxNounce after login via
JSON, but that is breaking the idea of why why having this AJAX_NOUNCE -
to check the source autenthity, and if the script returns it via JS Ajax
call then it is a security issue.
And problem here is that, the WordPress changes the NOUNCE after user
get's logged in.
This should not be happening first of all, as the purpose of NONCE, first
of all is to check the SOURCE AUTHENTICITY that the request really came
from the same domain, and is not for password checking - as then it is
super easy to break the WordPress security if that is the password
mechanism.
So problem is in this method in WordPress core, that it creates different
nonces for GUEST and LOGGED_IN user, while there should be a way to create
a nonce that would work for both states. As well as the function is self-
describing to check the nonce only, and should not do anything else, what
would be an unexpected behavior:
{{{#!php
<?php
function wp_create_nonce( $action = -1 ) {
$user = wp_get_current_user();
$uid = (int) $user->ID;
if ( ! $uid ) {
/** This filter is documented in wp-
includes/pluggable.php */
$uid = apply_filters( 'nonce_user_logged_out',
$uid, $action );
}
$token = wp_get_session_token();
$i = wp_nonce_tick();
return substr( wp_hash( $i . '|' . $action . '|' . $uid .
'|' . $token, 'nonce' ), -12, 10 );
}
}}}
The very bad hack for this issue is demonstrated below (creating a new
nonce and sending it via AJAX RESPONSE:
{{{#!php
<?php
final class MainController
{
/**
* Starts the plug-in main functionality
*/
public function runOnInit()
{
// <..> Other code
// User hook
if (!is_user_logged_in())
{
// Enable the user with no privileges to run ajax_login() in
AJAX
add_action('wp_ajax_nopriv_ajaxlogin', array($this,
'ajaxLogin'));
}
}
/**
*
*/
public function ajaxLogin()
{
if($this->canProcess)
{
try
{
// Assign routing to conf, only if it is not yet assigned
$conf = $this->conf();
// Load the language file, only if it is not yet loaded
$lang = $this->i18n();
// First check the nonce, if it fails the function will
break
check_ajax_referer('special-plugin-front-ajax-nonce',
'ajax_security');
// Nonce is checked, get the POST data and sign user on
$params = array();
$paramWP_UserIdOrEmail =
isset($_POST['login_account_id_or_email']) ?
$_POST['login_account_id_or_email'] : 0;
$userField = is_email($paramWP_UserIdOrEmail) ? 'email' :
'id';
$params['user_login'] = get_user_by($userField,
$paramWP_UserIdOrEmail)->user_login;
$params['user_password'] = isset($_POST['login_password'])
? $_POST['login_password'] : '';
$params['remember'] = TRUE;
$objWPSignOn = wp_signon($params, FALSE);
if (is_wp_error($objWPSignOn))
{
$jsonParams = array(
'loggedIn' => FALSE,
'message'=>
$lang->escJS('LANG_USER_ACCOUNT_ID_OR_PASSWORD_ERROR_TEXT'),
);
} else
{
$jsonParams = array(
'loggedIn' => TRUE,
'message'=>
$lang->escJS('LANG_USER_SUCCESSFULLY_LOGGED_IN_TEXT'),
'new_ajax_nonce' => wp_create_nonce('special-
plugin-front-ajax-nonce'),
);
}
echo json_encode($jsonParams);
die();
} catch (\Exception $e)
{
$this->processError(__FUNCTION__, $e->getMessage());
}
}
}
// <..> Other methods
}
}}}
For testing, if needed, the code of all above (a bit older version, and
without the AJAX LOGIN part) is available via:
https://wordpress.org/plugins/expandable-faq/
Or
https://github.com/SolidMVC/ExpandableFAQ/tree/master/Controllers
--
Ticket URL: <https://core.trac.wordpress.org/ticket/48356>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform
More information about the wp-trac
mailing list