View source
<?php
define('SECURITY_REVIEW_VERSION', '6.x-1.2');
function security_review_run($checklist = NULL, $log = FALSE, $help = FALSE) {
if (!$checklist && module_exists('security_review')) {
$checklist = module_invoke_all('security_checks');
}
$results = _security_review_run($checklist, $log);
if ($help && module_exists('security_review') && module_load_include('inc', 'security_review', 'security_review.help')) {
foreach ($results as $module => $checks) {
foreach ($checks as $check_name => $check) {
$function = $check['callback'] . '_help';
if (function_exists($function)) {
$element = call_user_func($function, $check);
$results[$module][$check_name]['help'] = $element;
}
}
}
}
return $results;
}
function _security_review_run($checklist, $log = FALSE) {
$results = array();
foreach ($checklist as $module => $checks) {
foreach ($checks as $check_name => $arguments) {
$check_result = _security_review_run_check($module, $check_name, $arguments, $log);
if (!empty($check_result)) {
$results[$module][$check_name] = $check_result;
}
}
}
return $results;
}
function _security_review_run_check($module, $check_name, $check, $log, $store = FALSE) {
$last_check = array();
if ($store) {
$last_check = security_review_get_last_check($module, $check_name);
}
$check_result = array();
$return = array(
'result' => NULL,
);
if (isset($check['file'])) {
if (isset($check['module'])) {
$module = $check['module'];
}
module_load_include('inc', $module, $check['file']);
}
$function = $check['callback'];
if (function_exists($function)) {
$return = call_user_func($function, $last_check);
}
$check_result = array_merge($check, $return);
$check_result['lastrun'] = time();
if ($log && !is_null($return['result'])) {
$variables = array(
'!name' => $check_result['title'],
);
if ($check_result['result']) {
_security_review_log($module, $check_name, '!name check passed', $variables, WATCHDOG_INFO);
}
else {
_security_review_log($module, $check_name, '!name check failed', $variables, WATCHDOG_ERROR);
}
}
return $check_result;
}
function _security_review_security_checks() {
$checks['file_perms'] = array(
'title' => t('File system permissions'),
'callback' => 'security_review_check_file_perms',
'success' => t('Drupal installation files and directories (except required) are not writable by the server.'),
'failure' => t('Some files and directories in your install are writable by the server.'),
);
$checks['input_formats'] = array(
'title' => t('Input formats'),
'callback' => 'security_review_check_input_formats',
'success' => t('Untrusted users are not allowed to input dangerous HTML tags.'),
'failure' => t('Untrusted users are allowed to input dangerous HTML tags.'),
);
$checks['nodes'] = array(
'title' => t('Content'),
'callback' => 'security_review_check_nodes',
'success' => t('Dangerous tags were not found in the body of any nodes.'),
'failure' => t('Dangerous tags were found in the body of nodes.'),
);
$checks['comments'] = array(
'title' => t('Comments'),
'callback' => 'security_review_check_comments',
'success' => t('Dangerous tags were not found in any comments.'),
'failure' => t('Dangerous tags were found in comments.'),
);
$checks['error_reporting'] = array(
'title' => t('Error reporting'),
'callback' => 'security_review_check_error_reporting',
'success' => t('Error reporting set to log only.'),
'failure' => t('Errors are written to the screen.'),
);
$checks['private_files'] = array(
'title' => t('Private files'),
'callback' => 'security_review_check_private_files',
'success' => t('Private files directory is outside the web server root.'),
'failure' => t('Private files is enabled but the specified directory is not secure outside the web server root.'),
);
$checks['upload_extensions'] = array(
'title' => t('Allowed upload extensions'),
'callback' => 'security_review_check_upload_extensions',
'success' => t('Only safe extensions are allowed for uploaded files.'),
'failure' => t('Unsafe file extensions are allowed in uploads.'),
);
if (module_exists('dblog')) {
$checks['query_errors'] = array(
'title' => t('Database errors'),
'callback' => 'security_review_check_query_errors',
'success' => t('Few query errors from the same IP.'),
'failure' => t('Query errors from the same IP. These may be a SQL injection attack or an attempt at information disclosure.'),
);
$checks['failed_logins'] = array(
'title' => t('Failed logins'),
'callback' => 'security_review_check_failed_logins',
'success' => t('Few failed login attempts from the same IP.'),
'failure' => t('Failed login attempts from the same IP. These may be a brute-force attack to gain access to your site.'),
);
}
$checks['admin_permissions'] = array(
'title' => t('Drupal admin permissions'),
'callback' => 'security_review_check_admin_permissions',
'success' => t('Untrusted roles do not have administrative permissions.'),
'failure' => t('Untrusted roles have been granted administrative permissions.'),
);
$checks['name_passwords'] = array(
'title' => t('Username as password'),
'callback' => 'security_review_check_name_passwords',
'success' => t('Trusted accounts do not have their password set to their username.'),
'failure' => t('Some trusted accounts have set their password the same as their username.'),
);
$checks['password_in_emails'] = array(
'title' => t('Password included in user emails'),
'callback' => 'security_review_check_email_passwords',
'success' => t('User passwords are not included in emails.'),
'failure' => t('User passwords are included in emails.'),
);
if (module_exists('php')) {
$checks['untrusted_php'] = array(
'title' => t('PHP access'),
'callback' => 'security_review_check_php_filter',
'success' => t('Untrusted users do not have access to use the PHP input format.'),
'failure' => t('Untrusted users have access to use the PHP input format.'),
);
}
return array(
'security_review' => $checks,
);
}
function _filefield_security_checks() {
$checks['filefield_extensions'] = array(
'title' => t('Filefield allowed uploads'),
'callback' => 'security_review_check_filefield_extensions',
'success' => t('Only safe extensions are allowed for Filefield uploaded files.'),
'failure' => t('Unsafe file extensions are allowed in Filefield uploads.'),
'module' => 'security_review',
'file' => 'security_review',
);
return array(
'filefield' => $checks,
);
}
function _views_security_checks() {
$checks['access'] = array(
'title' => t('Views access'),
'callback' => 'security_review_check_views_access',
'success' => t('Views are access controlled.'),
'failure' => t('There are Views that do not provide any access checks.'),
'module' => 'security_review',
'file' => 'security_review',
);
return array(
'views' => $checks,
);
}
function security_review_check_file_perms() {
$result = TRUE;
$file_path = './' . rtrim(file_directory_path(), '/');
$ignore = array(
'..',
'CVS',
$file_path,
);
$temp_path = variable_get('file_directory_temp', '');
if (!empty($temp_path)) {
$ignore[] = './' . rtrim($temp_path, '/');
}
drupal_alter('security_review_file_ignore', $ignore);
$files = _security_review_check_file_perms_scan('.', $ignore);
$create_status = $append_status = FALSE;
$directory = drupal_get_path('module', 'security_review');
$file = './' . $directory . '/file_write_test.' . date('Ymdhis');
if ($file_create = @fopen($file, 'w')) {
$create_status = fwrite($file_create, t("This is a vulnerable directory.\n"));
fclose($file_create);
}
$file = './' . $directory . '/IGNOREME.txt';
if ($file_append = @fopen($file, 'a')) {
$append_status = fwrite($file_append, date('Ymdhis') . "\n");
fclose($file_append);
}
if (count($files) || $create_status || $append_status) {
$result = FALSE;
}
return array(
'result' => $result,
'value' => $files,
);
}
function _security_review_check_file_perms_scan($directory, $ignore) {
$items = array();
if (is_readable($directory) && ($handle = opendir($directory))) {
while (($file = readdir($handle)) !== FALSE) {
if ($file[0] != "." && !in_array($file, $ignore) && !(is_link($file) && readlink($file) == '.')) {
$file = $directory . "/" . $file;
if (is_dir($file) && !in_array($file, $ignore)) {
$items = array_merge($items, _security_review_check_file_perms_scan($file, $ignore));
if (is_writable($file)) {
$items[] = preg_replace("/\\/\\//si", "/", $file);
}
}
elseif (is_writable($file) && !in_array($file, $ignore)) {
$items[] = preg_replace("/\\/\\//si", "/", $file);
}
}
}
closedir($handle);
}
return $items;
}
function security_review_check_input_formats() {
$result = TRUE;
$formats = filter_formats();
$check_result_value = array();
$untrusted_roles = security_review_untrusted_roles();
$default_format = variable_get('filter_default_format', FILTER_FORMAT_DEFAULT);
foreach ($formats as $id => $format) {
$format_roles = array_filter(explode(',', $format->roles));
if ($format->format == $default_format) {
$intersect = drupal_map_assoc(array_keys(user_roles()));
}
else {
$intersect = array_intersect($format_roles, $untrusted_roles);
}
if (!empty($intersect)) {
$filters = filter_list_format($format->format);
if (in_array('filter/0', array_keys($filters))) {
$setting = variable_get("filter_html_" . $format->format, FILTER_HTML_STRIP);
if ($setting == FILTER_HTML_STRIP) {
$allowed_tags = variable_get("allowed_html_" . $format->format, '');
$unsafe_tags = security_review_unsafe_tags();
foreach ($unsafe_tags as $tag) {
if (strpos($allowed_tags, '<' . $tag . '>') !== FALSE) {
$check_result_value['tags'][$id] = $tag;
}
}
}
}
else {
$check_result_value['formats'][$id] = $format->name;
}
}
}
if (!empty($check_result_value)) {
$result = FALSE;
}
return array(
'result' => $result,
'value' => $check_result_value,
);
}
function security_review_check_php_filter() {
$result = TRUE;
$formats = filter_formats();
$check_result_value = array();
$untrusted_roles = security_review_untrusted_roles();
$default_format = variable_get('filter_default_format', FILTER_FORMAT_DEFAULT);
foreach ($formats as $id => $format) {
$format_roles = array_filter(explode(',', $format->roles));
if ($format->format == $default_format) {
$intersect = drupal_map_assoc(array_keys(user_roles()));
}
else {
$intersect = array_intersect($format_roles, $untrusted_roles);
}
if (!empty($intersect)) {
$filters = filter_list_format($format->format);
if (in_array('php/0', array_keys($filters))) {
$result = FALSE;
$check_result_value['formats'][$id] = $format->name;
$check_result_value['roles'] = $intersect;
}
}
}
return array(
'result' => $result,
'value' => $check_result_value,
);
}
function security_review_check_error_reporting() {
$error_level = variable_get('error_level', NULL);
if (is_null($error_level) || intval($error_level) == 1) {
$result = FALSE;
}
else {
$result = TRUE;
}
return array(
'result' => $result,
);
}
function security_review_check_private_files() {
$file_downloads = variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC);
if ($file_downloads == FILE_DOWNLOADS_PRIVATE) {
$file_directory_path = file_directory_path();
if (strpos($file_directory_path, '/') === 0) {
$result = TRUE;
}
elseif (strpos($file_directory_path, '../') === 0) {
$result = FALSE;
}
else {
$result = FALSE;
}
}
else {
$result = NULL;
}
return array(
'result' => $result,
);
}
function security_review_check_upload_extensions() {
$result = TRUE;
$check_result_value = array();
if (module_exists('upload')) {
$extensions = variable_get('upload_extensions_default', NULL);
if (!is_null($extensions)) {
$unsafe_extensions = security_review_unsafe_extensions();
foreach ($unsafe_extensions as $unsafe_extension) {
if (strpos($extensions, $unsafe_extension) !== FALSE) {
$check_result_value[] = $unsafe_extension;
}
}
}
if (!empty($check_result_value)) {
$result = FALSE;
}
}
else {
$result = NULL;
}
return array(
'result' => $result,
'value' => $check_result_value,
);
}
function security_review_check_query_errors($last_check = NULL) {
$timestamp = NULL;
$check_result_value = array();
$sql = "SELECT message, hostname FROM {watchdog} WHERE type = 'php' AND severity = %d";
if (!is_null($last_check)) {
$sql .= " AND timestamp >= %d";
$timestamp = $last_check['lastrun'];
}
$results = db_query($sql, WATCHDOG_ERROR, $timestamp);
while ($row = db_fetch_array($results)) {
if (strpos($row['message'], 'SELECT') !== FALSE) {
$entries[$row['hostname']][] = $row;
}
}
$result = TRUE;
if (!empty($entries)) {
foreach ($entries as $ip => $records) {
if (count($records) > 10) {
$check_result_value[] = $ip;
}
}
}
if (!empty($check_result_value)) {
$result = FALSE;
}
else {
$result = NULL;
}
return array(
'result' => $result,
'value' => $check_result_value,
);
}
function security_review_check_failed_logins($last_check = NULL) {
$result = TRUE;
$timestamp = NULL;
$check_result_value = array();
$sql = "SELECT message, hostname FROM {watchdog} WHERE type = 'user' AND severity = %d";
if (!is_null($last_check)) {
$sql .= " AND timestamp >= %d";
$timestamp = $last_check['lastrun'];
}
$results = db_query($sql, WATCHDOG_NOTICE, $timestamp);
while ($row = db_fetch_array($results)) {
if (strpos($row['message'], 'Login attempt failed') !== FALSE) {
$entries[$row['hostname']][] = $row;
}
}
if (!empty($entries)) {
foreach ($entries as $ip => $records) {
if (count($records) > 10) {
$check_result_value[] = $ip;
}
}
}
if (!empty($check_result_value)) {
$result = FALSE;
}
else {
$result = NULL;
}
return array(
'result' => $result,
'value' => $check_result_value,
);
}
function security_review_check_admin_permissions() {
$result = TRUE;
$check_result_value = array();
$untrusted = security_review_untrusteds_permissions();
$admin_perms = security_review_admin_permissions();
foreach ($untrusted as $rid => $permissions) {
$intersect = array_intersect($permissions, $admin_perms);
if (!empty($intersect)) {
$check_result_value[$rid] = $intersect;
}
}
if (!empty($check_result_value)) {
$result = FALSE;
}
return array(
'result' => $result,
'value' => $check_result_value,
);
}
function security_review_check_nodes($last_check = NULL) {
$result = TRUE;
$check_result_value = array();
$timestamp = NULL;
$sql = "SELECT n.nid FROM {node} n INNER JOIN {node_revisions} r ON n.vid = r.vid WHERE r.body LIKE '%s' ";
if (!is_null($last_check) && $last_check['result'] == '1') {
$sql .= " AND n.changed >= %d";
$timestamp = $last_check['lastrun'];
}
foreach (array(
'Javascript' => '%<script%',
'PHP' => '%<?php%',
) as $description => $comparison) {
$results = pager_query($sql, 50, 0, NULL, $comparison, $timestamp);
while ($row = db_fetch_array($results)) {
$check_result_value[] = array(
$description => $row['nid'],
);
}
if (!empty($check_result_value)) {
$result = FALSE;
}
}
return array(
'result' => $result,
'value' => $check_result_value,
);
}
function security_review_check_comments($last_check = NULL) {
$result = TRUE;
$check_result_value = array();
$timestamp = NULL;
if (module_exists('comment')) {
$sql = "SELECT nid, cid FROM {comments} WHERE comment LIKE '%s'";
if (!is_null($last_check) && $last_check['result'] == '1') {
$sql .= " AND timestamp >= %d";
$timestamp = $last_check['lastrun'];
}
foreach (array(
'Javascript' => '%<script%',
'PHP' => '%<?php%',
) as $description => $comparison) {
$results = pager_query($sql, 20, 0, NULL, $comparison, $timestamp);
while ($row = db_fetch_array($results)) {
$check_result_value[$row['cid']] = array(
$description => $row['nid'],
);
}
if (!empty($check_result_value)) {
$result = FALSE;
}
}
}
else {
$result = NULL;
}
return array(
'result' => $result,
'value' => $check_result_value,
);
}
function security_review_check_name_passwords($last_check = NULL) {
$result = TRUE;
$check_result_value = array();
$timestamp = NULL;
$trusted_roles = security_review_trusted_roles();
if (!empty($trusted_roles)) {
$trusted_roles = array_keys($trusted_roles);
$check_result_value = _security_review_weak_passwords($trusted_roles);
}
if (!empty($check_result_value)) {
$result = FALSE;
}
return array(
'result' => $result,
'value' => $check_result_value,
);
}
function _security_review_weak_passwords($trusted_roles) {
$weak_users = array();
$sql = "SELECT u.uid, u.name, COUNT(rid) AS count FROM {users} u LEFT JOIN\n {users_roles} ur ON u.uid = ur.uid AND ur.rid in (" . db_placeholders($trusted_roles) . ")\n WHERE pass = md5(name) GROUP BY uid";
$results = db_query($sql, $trusted_roles);
while ($row = db_fetch_object($results)) {
$record[] = $row;
if ($row->count > 0) {
$weak_users[$row->uid] = $row->name;
}
}
$weak_uid1 = db_fetch_object(db_query("SELECT u.uid, u.name, 1 AS count FROM {users} u WHERE pass = md5(name) AND uid = 1"));
if (!empty($weak_uid1->count)) {
$weak_users[$weak_uid1->uid] = $weak_uid1->name;
}
return $weak_users;
}
function security_review_check_filefield_extensions($last_check = NULL) {
$result = TRUE;
$check_result_value = array();
$unsafe_extensions = security_review_unsafe_extensions();
$untrusted = security_review_untrusteds_permissions();
$fields = filefield_get_field_list();
foreach ($fields as $field) {
$extensions = $field['widget']['file_extensions'];
if (empty($extensions)) {
$check_result_value[$field['field_name']]['empty'] = TRUE;
}
else {
foreach ($unsafe_extensions as $unsafe_extension) {
if (strpos($extensions, $unsafe_extension) !== FALSE) {
$check_result_value[$field['field_name']]['extensions'][] = $unsafe_extension;
}
}
}
}
if (!empty($check_result_value)) {
$result = FALSE;
}
return array(
'result' => $result,
'value' => $check_result_value,
);
}
function security_review_check_views_access($last_check = NULL) {
$result = TRUE;
$check_result_value = array();
$timestamp = NULL;
$views = views_get_all_views();
foreach ($views as $view) {
if ($view->disabled !== TRUE) {
foreach ($view->display as $display_name => $display) {
if (isset($display->display_options['access']) && $display->display_options['access']['type'] == 'none') {
$check_result_value[$view->name][] = $display_name;
}
}
}
}
if (!empty($check_result_value)) {
$result = FALSE;
}
return array(
'result' => $result,
'value' => $check_result_value,
);
}
function security_review_check_email_passwords($last_check = NULL) {
$result = TRUE;
drupal_load('module', 'user');
$mail_templates = array(
'register_no_approval_required_subject',
'register_no_approval_required_body',
'register_admin_created_subject',
'register_admin_created_body',
'register_pending_approval_subject',
'register_pending_approval_admin_subject',
'register_pending_approval_body',
'register_pending_approval_admin_body',
'password_reset_subject',
'password_reset_body',
'status_activated_subject',
'status_activated_body',
'status_blocked_subject',
'status_blocked_body',
'status_deleted_subject',
'status_deleted_body',
);
$check_result_value = array();
foreach ($mail_templates as $template_name) {
$text = _user_mail_text($template_name);
if (strpos($text, '!password') !== FALSE) {
$check_result_value[] = $template_name;
}
}
if (!empty($check_result_value)) {
$result = FALSE;
}
return array(
'result' => $result,
'value' => $check_result_value,
);
}
function security_review_unsafe_tags() {
return array(
'applet',
'area',
'audio',
'base',
'basefont',
'body',
'button',
'comment',
'embed',
'eval',
'form',
'frame',
'frameset',
'head',
'html',
'iframe',
'image',
'img',
'input',
'isindex',
'label',
'link',
'map',
'math',
'meta',
'noframes',
'noscript',
'object',
'optgroup',
'option',
'param',
'script',
'select',
'style',
'svg',
'table',
'td',
'textarea',
'title',
'audio',
'video',
'vmlframe',
);
}
function security_review_unsafe_extensions() {
return array(
'swf',
'exe',
'html',
'htm',
'php',
'phtml',
'py',
'js',
'vb',
'vbe',
'vbs',
);
}
function security_review_default_untrusted_roles() {
$rids = array(
DRUPAL_ANONYMOUS_RID,
);
$user_register = variable_get('user_register', 1);
if ($user_register == 1) {
$rids[] = DRUPAL_AUTHENTICATED_RID;
}
return $rids;
}
function security_review_untrusted_roles() {
$defaults = security_review_default_untrusted_roles();
$roles = variable_get('security_review_untrusted_roles', $defaults);
return array_filter($roles);
}
function security_review_untrusteds_permissions() {
static $permissions;
if (empty($permissions)) {
$permissions = array();
$untrusted_roles = security_review_untrusted_roles();
foreach ($untrusted_roles as $rid) {
$perms = array();
$results = db_fetch_array(db_query('SELECT r.rid, p.perm FROM {role} r LEFT JOIN {permission} p ON r.rid = p.rid WHERE r.rid = %d', $rid));
if ($results !== FALSE) {
$perms = explode(',', str_replace(', ', ',', $results['perm']));
$permissions[$rid] = $perms;
}
}
}
return $permissions;
}
function security_review_trusted_roles() {
$trusted_roles = array();
$untrusted_roles = security_review_untrusted_roles();
$result = db_query('SELECT rid, name FROM {role} WHERE rid NOT IN (' . db_placeholders($untrusted_roles) . ')', $untrusted_roles);
while ($role = db_fetch_object($result)) {
$trusted_roles[$role->rid] = $role->name;
}
return array_filter($trusted_roles);
}
function security_review_admin_permissions() {
return array(
'administer users',
'administer permissions',
'administer site configuration',
'administer filters',
'administer content types',
'administer nodes',
);
}
function security_review_role_permission($rid, $permission) {
$return = FALSE;
$result = db_fetch_array(db_query("SELECT perm FROM {permission} WHERE rid = %d", $rid));
if ($result['perm'] && strpos($result['perm'], $permission) !== FALSE) {
$return = TRUE;
}
return $return;
}
function security_review_get_checks() {
$checks = _security_review_security_checks();
if (module_exists('filefield') && function_exists('filefield_get_field_list')) {
$filefield = _filefield_security_checks();
$checks = array_merge($checks, $filefield);
}
if (module_exists('views') && function_exists('views_get_all_views')) {
$views = _views_security_checks();
$checks = array_merge($checks, $views);
}
return $checks;
}
function _security_review_log($module, $check_name, $message, $variables, $type) {
module_invoke_all('security_review_log', $module, $check_name, $message, $variables, $type);
}