Skip to content

Commit

Permalink
SITES-380: Block Views Contextual Filters PHP with Paranoia (#32)
Browse files Browse the repository at this point in the history
* SITES-380: Block Views Contextual Filters PHP with Paranoia

* fixup! Merge in latest changes from paranoia-7.x-1.x
  • Loading branch information
jbickar authored and kbrownell committed Mar 21, 2018
1 parent eda8693 commit 736dc91
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 11 deletions.
26 changes: 26 additions & 0 deletions docroot/sites/all/modules/contrib/paranoia/README.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,32 @@ What it does:
- Remove the PHP and paranoia modules from the module admin page.
- Provides a hook to let you remove other modules from the module admin page.


Using the feature to scramble the password for stale accounts
=============================================================
Paranoia includes a feature to scramble the password of an account that has not
logged in for a while. This feature uses a queue so that it can scalably handle
scrambling the password of thousands of accounts. The "scramble" does not set a
new password. It sets the password to an invalid string which will
always fail when compared to any user input. To use this feature:

1. Navigate to /admin/config/system/paranoia to configure how many days an
account must be inactive before it's password will be scrambled. Also
choose whether or not to email users letting them know their password was
reset.

2. Use the Drush command to queue up accounts to be marked as stale:

drush -v paranoia-reset-stale-accounts

3. Run the queue to process the stale expirations:

drush -v queue-run paranoia_stale_expirations

Using the -v option on drush will show extra information about the operations.

You can also let cron handle processing the queue, though that may take a long time.

NOTE on disabling:
=====
The only way to disable paranoia module is by changing its status in the
Expand Down
31 changes: 31 additions & 0 deletions docroot/sites/all/modules/contrib/paranoia/paranoia.admin.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

/**
* @file
* Defines administration form for Paranoia module.
*/

/**
* Administration form.
*/
function paranoia_admin_form() {
$form = array();

$form['paranoia_access_threshold'] = array(
'#type' => 'textfield',
'#title' => t('User access threshold (in days)'),
'#default_value' => variable_get('paranoia_access_threshold', 730),
'#description' => t('Accounts that go unaccessed for more than this number
of days can have their passwords randomized using the "drush
paranoia-reset-stale-accounts" command.'),
);
$form['paranoia_email_notification'] = array(
'#type' => 'checkbox',
'#title' => t('Email affected users'),
'#default_value' => variable_get('paranoia_email_notification', FALSE),
'#description' => t('Check this box to notify users by email when their
account passwords have been randomized.'),
);

return system_settings_form($form);
}
51 changes: 50 additions & 1 deletion docroot/sites/all/modules/contrib/paranoia/paranoia.drush.inc
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

/**
* Implements hook_drush_command().
* Implements hook_drush_sql_sync_sanitize().
*/
function paranoia_drush_sql_sync_sanitize($site) {
// Don't use DBTNG here so this mostly workis across old versions of Drupal.
Expand Down Expand Up @@ -75,3 +75,52 @@ function paranoia_drush_sql_sync_sanitize($site) {
dt('Truncate core cache tables.'),
$trucate_caches_query);
}

/**
* Implements hook_drush_help().
*/
function paranoia_drush_help($command) {
switch ($command) {
case 'drush:paranoia-reset-stale-accounts':
return dt('Queue accounts to have their passwords reset if they have not logged in recently.');
}
}

/**
* Implements hook_drush_command().
*/
function paranoia_drush_command() {
return array(
'paranoia-reset-stale-accounts' => array(
'description' => dt('Queue accounts to have their passwords reset if they have not logged in recently.'),
),
);
}

/**
* Drush callback to queue stale accounts to have their passwords reset.
*/
function drush_paranoia_reset_stale_accounts() {
$queue = DrupalQueue::get('paranoia_stale_expirations');

// Don't add to the queue if there are remaining items to be processed, to
// avoid duplicate queue items if the cron queue iterator from a previous
// cron run has not gotten through all of its queued items.
if ($queue->numberOfItems() > 0) {
watchdog('paranoia', 'Skipping adding items to the queue as stale expiration items already exist.');
return;
}

// Check for accounts whose last access time is older than the threshold,
// which defaults to 2 years (365 * 2 = 730 days).
$offset = REQUEST_TIME - (variable_get('paranoia_access_threshold', 730) * 60 * 60 * 24);
$result = db_query("SELECT uid FROM {users} WHERE uid > 0 AND access > 0 AND access < :offset AND pass NOT LIKE 'ZZZ%'", array(':offset' => $offset));

$count = 0;
foreach ($result as $record) {
$count++;
$queue->createItem($record->uid);
}
drush_log(dt('Queued @count users to have their password reset', array('@count' => $count)), 'ok');
watchdog('paranoia', 'Queued @count users to have their password reset', array('@count' => $count));
}
147 changes: 137 additions & 10 deletions docroot/sites/all/modules/contrib/paranoia/paranoia.module
Original file line number Diff line number Diff line change
@@ -1,13 +1,40 @@
<?php

/**
* @file
* - Disables PHP block visibility permission and gives status error if a role
* has this permission.
* - Disables the PHP module.
* - Hides the PHP and paranoia modules from the modules page.
* - Prevents user/1 editing which could give access to abitrary contrib module
* php execution.
* Paranoia module file. Provides various extra security features.
*/

/**
* Implements hook_menu().
*/
function paranoia_menu() {
$items['admin/config/system/paranoia'] = array(
'title' => 'Paranoia',
'description' => 'Configure settings for protecting old accounts that have
not been accessed recently.',
'page callback' => 'drupal_get_form',
'page arguments' => array('paranoia_admin_form'),
'access arguments' => array('administer paranoia'),
'file' => 'paranoia.admin.inc',
'type' => MENU_NORMAL_ITEM,
);

return $items;
}

/**
* Implements hook_permission().
*/
function paranoia_permission() {
return array(
'administer paranoia' => array(
'title' => t('Administer paranoia'),
'description' => t('Perform administration tasks for the Paranoia module.'),
'restrict access' => TRUE,
),
);
}

/**
* Implements hook_menu_alter().
Expand Down Expand Up @@ -326,7 +353,7 @@ function custom_breadcrumbs_paranoia_hide_permissions() {
/**
* Implements hook_paranoia_hide_paths().
*
* On behalf of devel.module
* On behalf of devel.module.
*/
function devel_paranoia_hide_paths() {
return array('devel/php');
Expand All @@ -335,7 +362,7 @@ function devel_paranoia_hide_paths() {
/**
* Implements hook_paranoia_hide_paths().
*
* On behalf of views.module
* On behalf of views.module.
*/
function views_paranoia_hide_paths() {
return array('admin/structure/views/import');
Expand All @@ -345,7 +372,8 @@ function views_paranoia_hide_paths() {
* Implements hook_form_alter().
*
* Hides forms that allow php arrays for importing to avoid RCE.
* http://heine.familiedeelstra.com/security/unserialize
*
* @see http://heine.familiedeelstra.com/security/unserialize
*/
function paranoia_form_alter(&$form, &$form_state, $form_id) {
$forms_to_disable = module_invoke_all('paranoia_risky_forms');
Expand Down Expand Up @@ -375,6 +403,15 @@ function paranoia_form_alter(&$form, &$form_state, $form_id) {
if ($form_id == "custom_breadcrumbs_form") {
unset($form['visibility_php']);
}

// Disable the ability to use PHP in Views Contextual Filters.
// See https://www.drupal.org/docs/7/modules/views/views-howtos/php-contextual-filters for example use cases.
if ($form_id == "views_ui_config_item_form") {
unset($form['options']['default_argument_type']['#options']['php']);
unset($form['options']['validate']['type']['#options']['php']);
unset($form['options']['validate']['options']['php']);
}

}

/**
Expand Down Expand Up @@ -475,5 +512,95 @@ function rules_admin_paranoia_risky_forms() {
* On behalf of views module.
*/
function views_paranoia_risky_forms() {
return array('views_ui_import_page');
return ['views_ui_import_page'];
}

/**
* Implements hook_cron_queue_info().
*/
function paranoia_cron_queue_info() {
$queues['paranoia_stale_expirations'] = array(
'worker callback' => 'paranoia_reset_stale',
'time' => variable_get('paranoia_queue_time', 15),
);
return $queues;
}

/**
* Worker callback for the paranoia_stale_expirations queue.
*/
function paranoia_reset_stale($item) {
paranoia_reset_password_for_uid($item);
}

/**
* Resets a password on a uid and sends a mail as appropriate.
*
* @param int $uid
* A user ID that should have their password reset.
*/
function paranoia_reset_password_for_uid($uid) {
if ($account = user_load($uid)) {
// The ZZZ prefix ensures the password comparison will fail until a reset.
// See user_check_password().
$result = db_query("UPDATE {users} SET pass = CONCAT('ZZZ', SHA(CONCAT(pass, MD5(RAND())))) WHERE uid = :uid", array(':uid' => $account->uid));
if ($result) {
watchdog('paranoia', 'Password randomized for @user.', array('@user' => $account->name), WATCHDOG_INFO);
if (variable_get('paranoia_email_notification', FALSE)) {
paranoia_expired_mail_send($account->uid);
}
}
else {
watchdog('paranoia', 'Failed to randomize password for uid @uid.', array('@uid' => $uid), WATCHDOG_ERROR);
}
}
}

/**
* Implements hook_mail().
*/
function paranoia_mail($key, &$message, $params) {
switch ($key) {
case 'paranoia_expired':
$account = user_load($params['uid']);
$options = array(
'langcode' => $message['language']->language,
);

$message['subject'] = t('@site-name account password randomized', array('@site-name' => variable_get('site_name', 'Drupal')), $options);
$message['body'][] = t("@name,

As a security precaution, since you have not logged in to your account for
@threshold days, the password on the account has been randomized.",
array(
'@name' => $account->name,
'@threshold' => variable_get('paranoia_access_threshold', 730),
), $options);
$message['body'][] = check_plain($params['message']);
break;
}
}

/**
* Sends an e-mail if a password has been reset.
*
* @param int $uid
* The User ID of an account to email, to notify that the account's
* password has been randomized, since the account had not been
* accessed for a long period of time (the default threshold is
* 2 years).
*/
function paranoia_expired_mail_send($uid) {
$account = user_load($uid);

$params = array('uid' => $account->uid);

// Send the mail, and check for success.
$result = drupal_mail('paranoia', 'paranoia_expired', $account->mail, language_default(), $params);
if ($result['result']) {
watchdog('paranoia', 'Notification of randomized password sent to @account.', array('@account' => $account->name), WATCHDOG_INFO);
}
else {
watchdog('paranoia', 'There was a problem sending a notification message to @account.', array('@account' => $account->name), WATCHDOG_ERROR);
}
}

0 comments on commit 736dc91

Please sign in to comment.