From 272081ca255b523b753cf3542cbb981198eb2a72 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Fri, 5 Sep 2014 23:11:23 -0700 Subject: [PATCH] CRM-15247 - CRM_Contact_Page_AJAX::checkUserName - Require a token before checking username The use-case for this function: when a new constituent signs up for a user account, we give advice on whether the username is available. Unfortunately, attackers can use that functionality to scan the list of usernames. There's no protection from a motivated attacker (except to disable new signups). This patch aims to mitigate the problem in two ways: - For sites which don't allow user signups, the scanning won't work (b/c attackers can't obtain a token). - For sites which do allow signups, scanning requires more work (to obtain & refresh tokens). --- CRM/Contact/Page/AJAX.php | 18 ++++++ .../Smarty/plugins/function.crmSigner.php | 60 +++++++++++++++++++ .../CRM/common/checkUsernameAvailable.tpl | 14 ++++- 3 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 CRM/Core/Smarty/plugins/function.crmSigner.php diff --git a/CRM/Contact/Page/AJAX.php b/CRM/Contact/Page/AJAX.php index 4dbb69d595..85c1cbf39f 100644 --- a/CRM/Contact/Page/AJAX.php +++ b/CRM/Contact/Page/AJAX.php @@ -36,6 +36,13 @@ * This class contains all contact related functions that are called using AJAX (jQuery) */ class CRM_Contact_Page_AJAX { + /** + * When a user chooses a username, CHECK_USERNAME_TTL + * is the time window in which they can check usernames + * (without reloading the overall form). + */ + const CHECK_USERNAME_TTL = 10800; // 3hr; 3*60*60 + static function getContactList() { // if context is 'customfield' if (CRM_Utils_Array::value('context', $_GET) == 'customfield') { @@ -615,6 +622,17 @@ WHERE sort_name LIKE '%$name%'"; * */ static public function checkUserName() { + $signer = new CRM_Utils_Signer(CRM_Core_Key::privateKey(), array('for', 'ts')); + if ( + CRM_Utils_Time::getTimeRaw() > $_REQUEST['ts'] + self::CHECK_USERNAME_TTL + || $_REQUEST['for'] != 'civicrm/ajax/cmsuser' + || !$signer->validate($_REQUEST['sig'], $_REQUEST) + ) { + $user = array('name' => 'error'); + echo json_encode($user); + CRM_Utils_System::civiExit(); + } + $config = CRM_Core_Config::singleton(); $username = trim($_REQUEST['cms_name']); diff --git a/CRM/Core/Smarty/plugins/function.crmSigner.php b/CRM/Core/Smarty/plugins/function.crmSigner.php new file mode 100644 index 0000000000..f023811701 --- /dev/null +++ b/CRM/Core/Smarty/plugins/function.crmSigner.php @@ -0,0 +1,60 @@ +sign($params); + $smarty->assign($var, $params); +} diff --git a/templates/CRM/common/checkUsernameAvailable.tpl b/templates/CRM/common/checkUsernameAvailable.tpl index 0647851f05..fec2099a1e 100644 --- a/templates/CRM/common/checkUsernameAvailable.tpl +++ b/templates/CRM/common/checkUsernameAvailable.tpl @@ -24,6 +24,7 @@ +--------------------------------------------------------------------+ *} {* This included tpl checks if a given username is taken or available. *} +{crmSigner var=checkUserSig for=civicrm/ajax/cmsuser} {literal} var lastName = null; cj("#checkavailability").click(function() { @@ -56,6 +57,7 @@ cj("#checkavailability").click(function() { var check = "{/literal}{ts escape='js'}Checking...{/ts}{literal}"; var available = "{/literal}{ts escape='js'}This username is currently available.{/ts}{literal}"; var notavailable = "{/literal}{ts escape='js'}This username is taken.{/ts}{literal}"; + var errorMsg = "{/literal}{ts escape='js'}Error checking username. Please reload the form and try again.{/ts}{literal}"; //remove all the class add the messagebox classes and start fading cj("#msgbox").removeClass().addClass('cmsmessagebox').css({"color":"#000","backgroundColor":"#FFC","border":"1px solid #c93"}).text(check).fadeIn("slow"); @@ -63,11 +65,21 @@ cj("#checkavailability").click(function() { //check the username exists or not from ajax var contactUrl = {/literal}"{crmURL p='civicrm/ajax/cmsuser' h=0 }"{literal}; - cj.post(contactUrl,{ cms_name:cj("#cms_name").val() } ,function(data) { + var checkUserParams = { + cms_name: cj("#cms_name").val(), + ts: {/literal}"{$checkUserSig.ts}"{literal}, + sig: {/literal}"{$checkUserSig.signature}"{literal}, + for: 'civicrm/ajax/cmsuser' + }; + cj.post(contactUrl, checkUserParams ,function(data) { if ( data.name == "no") {/*if username not avaiable*/ cj("#msgbox").fadeTo(200,0.1,function() { cj(this).html(notavailable).addClass('cmsmessagebox').css({"color":"#CC0000","backgroundColor":"#F7CBCA","border":"1px solid #CC0000"}).fadeTo(900,1); }); + } else if ( data.name == "error") {/*if username not avaiable*/ + cj("#msgbox").fadeTo(200,0.1,function() { + cj(this).html(errorMsg).addClass('cmsmessagebox').css({"color":"#CC0000","backgroundColor":"#F7CBCA","border":"1px solid #CC0000"}).fadeTo(900,1); + }); } else { cj("#msgbox").fadeTo(200,0.1,function() { cj(this).html(available).addClass('cmsmessagebox').css({"color":"#008000","backgroundColor":"#C9FFCA", "border": "1px solid #349534"}).fadeTo(900,1); -- 2.25.1