$t('PHPIDS'), 'value' => $t('Found'), ); if (!file_exists(realpath(dirname(__FILE__) . '/IDS/default_filter.xml'))) { $requirements['phpids']['value'] = $t('Not found'); $requirements['phpids']['description'] = $t('You must dowload the latest PHPIDS package and place in the phpids module folder. Warning: the PHP4 version is not supported.'); $requirements['phpids']['severity'] = REQUIREMENT_ERROR; } return $requirements; } /** * Small function to display warnings on logs/configure page */ function _phpids_packagecheck() { if (!file_exists(realpath(dirname(__FILE__) . '/IDS/default_filter.xml'))) { return drupal_set_message(t('You must dowload the latest PHPIDS package and place in the phpids module folder. Warning: the PHP4 version is not supported.'),'error'); } } /** * Display help and module information */ function phpids_help($section='') { $output = ''; switch ($section) { case "admin/help#phpids": $output = '

'. t("Add PHPIDS as a security layer for Drupal"). '

'; break; } return $output; } /** * Permissions function */ function phpids_perm() { return array('access phpids logs', 'administer phpids'); } /** * Implementation of hook_cron(). * Deletes logs after a certain amount of time (default: one week) */ function phpids_cron() { db_query('DELETE FROM {phpids} WHERE timestamp < %d', time() - variable_get('phpids_clear', 604800)); } /** * Implementation of hook_menu() */ function phpids_menu($may_cache) { $items = array(); $access_logs = user_access('access phpids logs'); $access_conf = user_access('administer phpids'); if ($may_cache) { $items[] = array( 'path' => 'admin/logs/phpids', 'title' => t('PHPIDS log'), 'description' => t('View PHPIDS logs'), 'callback' => 'phpids_logs', 'access' => $access_logs, 'weight' => 15); $items[] = array( 'path' => 'admin/logs/phpids/event', 'title' => t('Details'), 'callback' => 'phpids_event', 'access' => $access_logs, 'type' => MENU_CALLBACK); $items[] = array( 'path' => 'admin/logs/phpids/log', 'title' => t('Logs'), 'type' => MENU_DEFAULT_LOCAL_TASK, 'access' => $access_logs, 'weight' => -10); $items[] = array( 'path' => 'admin/logs/phpids/settings', 'title' => t('Configure'), 'callback' => 'drupal_get_form', 'callback arguments' => array('phpids_admin_settings'), 'type' => MENU_LOCAL_TASK, 'access' => $access_conf); $items[] = array( 'path' => 'phpidswarning', 'title' => t('PHPIDS warning'), 'callback' => 'phpids_warning', 'access' => TRUE, 'type' => MENU_CALLBACK ); } return $items; } /** * Implementation of hook_init() * @ignore : value depends which action will happen * 0 = do nothing * 1 = only log * 2 = log & actions */ function phpids_init() { if (file_exists(realpath(dirname(__FILE__) . '/IDS/default_filter.xml'))) { global $user, $base_root; // default is logging $ignore = 1; // anonymous user if ($user->uid == 0) { $anon = variable_get('phpids_anonymous',2); if ($anon == 2) $ignore = 2; } // authenticated user - always ignore user 1 if ($user->uid != 0) { if ($user->uid == 1) $ignore = 0; else { $auth = variable_get('phpids_authenticated',2); if ($auth == 1) $ignore = 0; if ($auth == 3) $ignore = 2; } } // start PHPIDS if ignore is not 0 if ($ignore != 0) { $request_uri = $base_root . request_uri(); // set include path and required the needed files $phpids_path = realpath(dirname(__FILE__)); set_include_path(get_include_path(). PATH_SEPARATOR. $phpids_path); require_once 'IDS/Monitor.php'; require_once 'IDS/Filter/Storage.php'; $storage = new IDS_Filter_Storage(); $storage->getFilterFromXML($phpids_path.'/IDS/default_filter.xml'); // instanciate the needed stuff // do we need to monitor other global variables ? $request = array_merge($_GET,$_POST); $request = new IDS_Monitor($request, $storage); $report = $request->run(); // if report is not empty, always log // depending on variables, take other actions if impact level matches settings criteria. if (!$report->isEmpty()) { // default action is log $action = 0; // level of severity $severity = $report->getImpact(); // get variables to see if we need to take more action than only logging $mail_level = variable_get('phpids_maillevel',9); $mail_sent = variable_get('phpids_mail',''); $warn_level = variable_get('phpids_warnlevel',27); if ($severity >= $mail_level && !empty($mail_sent) && $ignore == 2) $action = 1; if ($severity >= $warn_level && $ignore == 2) $action = 2; // create detailed report $message = 'All tags: ' . join(", ", $report->getTags()) . '
'; // iterate through the result an get every event (IDS_Event) foreach ($report as $event) { $message .= '
Variable: '.$event->getName().' | Value: ' . htmlspecialchars($event->getValue()) . '
'; $message .= 'Impact: '.$event->getImpact().' | Tags: ' . join(", ", $event->getTags()) . '
'; // iterator throught every filter $message .= ''; } // log the impact phpids_addevent($user,$message,$severity,$action,$request_uri); // send out mail if needed // TODO: more info in mail if ($action == 1) { $body = 'Check your logs to see a full detail of the report.'; drupal_mail('',$mail_sent,'PHPIDS detected an attack with impact '.$severity,$body); } // Warning : redirect the user to a warning page so nothing can happen to the system if ($action == 2) { drupal_goto('phpidswarning'); exit(); } } } } } /** * Insert an event into the database */ function phpids_addevent($user,$message,$severity,$action,$request_uri) { db_query("INSERT into {phpids} (uid,message,severity,paction,location,referer,hostname,timestamp) VALUES (%d,'%s','%d',%d,'%s','%s','%s','%d')", $user->uid,$message,$severity,$action,$request_uri,referer_uri(),$_SERVER['REMOTE_ADDR'],time()); } /** * Callback function to return PHPIDS logs * Taken from the watchdog overview */ function phpids_logs() { _phpids_packagecheck(); $header = array( array('data' => t('Date'), 'field' => 'p.pid', 'sort' => 'desc'), array('data' => t('Level'), 'field' => 'p.severity'), array('data' => t('Attack'), 'field' => 'p.message'), array('data' => t('User'), 'field' => 'u.name'), array('data' => t('IP'), 'field' => 'p.hostname'), array('data' => t('Action'), 'field' => 'p.paction') ); $phpids_action = array('Log','Mail','Warning','Ban'); $sql = "SELECT p.pid, p.uid, p.severity, p.paction, p.timestamp, p.message, p.hostname, u.name FROM {phpids} p INNER JOIN {users} u ON p.uid = u.uid"; $tablesort = tablesort_sql($header); $result = pager_query($sql . $tablesort, 50); while ($phpids = db_fetch_object($result)) { $rows[] = array('data' => array( // Cells format_date($phpids->timestamp, 'small'), $phpids->severity, l(truncate_utf8($phpids->message, 36, TRUE, TRUE), 'admin/logs/phpids/event/'. $phpids->pid, array(), NULL, NULL, FALSE, TRUE), theme('username', $phpids), $phpids->hostname, $phpids_action[$phpids->paction] ), ); } if (!$rows) { $rows[] = array(array('data' => t('No log messages available.'), 'colspan' => 6)); } $output .= theme('table', $header, $rows); $output .= theme('pager', NULL, 50, 0); return $output; } /** * Callback function to return detail of one event */ function phpids_event($id) { $phpids_action = array('Log','Mail','Warning','Ban'); $row = db_fetch_object(db_query("select p.uid, p.severity, p.paction, p.timestamp, p.message, p.hostname, u.name from {phpids} p INNER JOIN {users} u ON p.uid = u.uid where pid = %d",$id)); $output = '

'.t('Total impact').': '.$row->severity.'
'; $output .= t('Date').': '.format_date($row->timestamp,'small').'
'; $output .= t('User').': '.theme('username',$row); // create deny user link if user has the appropriate rights, and if userid is not 0 or 1. $output .= _phpids_create_rule_link('user',$row); $output .= '
'; $output .= t('Host').': '.$row->hostname; // create deny ip link if user has the appropriate rights. $output .= _phpids_create_rule_link('host',$row); $output .= '
'; $output .= t('Action').': '.$phpids_action[$row->paction].'

'; $output .= '

'.t('Detailed information about attack').'
'.wordwrap($row->message,'100',' ',TRUE).'

'; return $output; } /* * Callback function to configure PHPIDS */ function phpids_admin_settings() { _phpids_packagecheck(); // general settings $period = drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200), 'format_interval'); $period['1000000000'] = t('Never'); $form['general'] = array( '#type' => 'fieldset', '#title' => t('General'), '#description' => t('A user with access to user access control will see a link to create/edit access rules when viewing a detail of an attack for a host/user.'), ); $form['general']['phpids_clear'] = array( '#type' => 'select', '#title' => t('Discard PHPIDS log entries older than'), '#default_value' => variable_get('phpids_clear', 604800), '#options' => $period, '#description' => t('The time PHPIDS log entries should be kept. Older entries will be automatically discarded. Requires crontab.'), ); $form['general']['phpids_maillevel'] = array( '#type' => 'textfield', '#title' => t('Mail impact'), '#default_value' => variable_get('phpids_maillevel',9), '#description' => t('Sends out mail when this level of impact is reached.'), ); $form['general']['phpids_mail'] = array( '#type' => 'textfield', '#title' => t('Email'), '#default_value' => variable_get('phpids_mail',''), '#description' => t('Leave empty if you don\'t want to send out email. Action field on logs overview will display "log" then.'), ); $form['general']['phpids_warnlevel'] = array( '#type' => 'textfield', '#title' => t('Warning impact'), '#default_value' => variable_get('phpids_warnlevel',27), '#description' => t('Redirects to a warning page after this level of impact is reached.'), ); // finetine filter settings $form['filters'] = array( '#type' => 'fieldset', '#title' => t('Ignore filters'), '#description' => t('Finetune settings when PHPIDS shouldn\'t take action. Keep in mind that user 1 is always ignored and anonymous users are always monitored!'), ); $options_anon = array(1 => t('Log anonymous users without actions'), 2 => t('Log anonymous users and take actions')); $form['filters']['phpids_anonymous'] = array( '#type' => 'select', '#title' => t('Anonymous users'), '#description' => t('Choose a setting for anonymous users.'), '#default_value' => variable_get('phpids_anonymous',1), '#options' => $options_anon, ); $options_auth = array(1 => t('Do not log authenticated users'), 2 => t('Log authenticated users without actions'), 3 => t('Log authenticated users and take actions')); $form['filters']['phpids_authenticated'] = array( '#type' => 'select', '#title' => t('Authenticated users'), '#description' => t('Choose a setting for authenticated users.'), '#default_value' => variable_get('phpids_authenticated',2), '#options' => $options_auth, ); return system_settings_form($form); } /** * Warning page: display this page if the attack has reached warning level thus * making the action of the (anonymous) user completely worthless. */ function phpids_warning() { $output = t('We have detected malicious input and blocked your attempt.
If you keep experiencing problems but feel like you are doing nothing wrong, please contact the site administrator.'); return $output; } /** * Create access rule add/edit link * Checks if type is in access table or not * Only display if user has proper rights */ function _phpids_create_rule_link($type,$row) { if (user_access('administer access control')) { $return = TRUE; $url_type = 'add'; switch ($type){ case'user': if ($row->uid != 1 && $row->uid != 0) { $value = $row->name; $aid = _phpids_check_access_rule($type,$row->name); } else $return = FALSE; break; case'host': $value = $row->hostname; $aid = _phpids_check_access_rule($type,$row->hostname); break; } if ($aid != FALSE) $url_type = 'edit'; if ($return == TRUE) { switch ($url_type){ case'add': return ' | '.l(t("Add access rule for").' '.$type,'admin/user/rules/add/'.$value.'/'.$type.'',NULL,drupal_get_destination()); break; case'edit': // http://swenteldoos/phpids/?q=admin/user/rules/edit/5 return ' | '.l(t("Change access rule for").' '.$type,'admin/user/rules/edit/'.$aid.'',NULL,drupal_get_destination()); break; } } } } /** * Return aid from access table if match is found. * We look for an exact match! */ function _phpids_check_access_rule($type,$value) { $access = db_fetch_object(db_query("SELECT aid FROM {access} where type = '%s' AND mask = '%s'",$type,$value)); if (!empty($access)) return $access->aid; else return FALSE; } ?>