<?php
// $Id: security_scanner.module,v 1.16 2008/07/26 09:42:29 ingo86 Exp $
// Including DrupalWebTestCase() class dependencies
include_once drupal_get_path('module',
'simpletest') .
'/simpletest/web_tester.php';
include_once drupal_get_path('module',
'simpletest') .
'/simpletest/unit_tester.php';
include_once drupal_get_path('module',
'simpletest') .
'/drupal_test_case.php';
//include_once drupal_get_path('module', 'simpletest') . '/drupal_unit_tests.php';
include_once drupal_get_path('module',
'simpletest') .
'/drupal_web_test_case.php';
// Make my own class that extends DrupalWebTestCase()
// This has to be removed when this variable will switch to public into the main simpletest module.
class DrupalSecurityScannerClass extends DrupalWebTestCase {
public $curl_options =
array();
}
/**
* Takes a path and returns an absolute path.
*
* @param @path
* The path, can be a Drupal path or a site-relative path. It might have a
* query, too. Can even be an absolute path which is just passed through.
* @return
* An absolute path.
*/
function getAbsoluteUrl($path) {
$options =
array('absolute' =>
TRUE);
// This is more crude than the menu_is_external but enough here.
if (empty($parts['host'])) {
$path = $parts['path'];
if (substr($path,
0,
$n) ==
$base_path) {
}
if (isset($parts['query'])) {
$options['query'] = $parts['query'];
}
$path =
url($path,
$options);
}
return $path;
}
/**
* Implementation of hook_menu().
*/
function security_scanner_menu() {
$items['admin/settings/security_scanner'] =
array(
'title' => 'Security Scanner',
'page callback' => 'page_security_scanner',
'access arguments' =>
array('access scanner'),
);
return $items;
}
/**
* Settings page for security scanner module
*/
function page_security_scanner() {
}
/**
* Form page into security scanner
*/
function security_scanner_form($form_state) {
$options =
array('0' =>
t('Crawl'),
'1' =>
t('Seed'),
'2' =>
t('ReCrawl'));
$form['settings']['mode'] =
array(
'#type' => 'radios',
'#default_value' =>
variable_get('security_scanner_settings',
0),
'#options' => $options,
'#description' =>
t('Set the mode that the security scanner will works.'),
);
'#type' => 'submit',
'#value' => 'Submit',
);
return $form;
}
/**
* security_scanner_form_submit():
* Provide submit handler to settings form;
*/
function security_scanner_form_submit($form, &$form_state) {
variable_set('security_scanner_settings',
$form_state['values']['mode']);
}
/**
* Implementation of the crawler function.
* Flow:
* 1- set crawler_id but leave status = 0
* 2- make status = 1
* 3- make status = 2
*/
function crawler_framework($selection_query, $initial_status, $function_callback) {
// Initialize the crawler
db_query("INSERT INTO {crawler} VALUES (default)");
$selection_query +=
array(
'join' => '',
'where' => '',
);
$fields =
empty($selection_query['fields']) ?
'' :
','.
implode(', ',
$selection_query['fields']);
$sql = 'SELECT l.path'. $fields .' FROM {crawler_links} AS l '. $selection_query['join'] .' WHERE crawler_id = %d and status = %d' . $selection_query['where'];
array_unshift($selection_query['parameters'],
$crawler_id,
$initial_status+1);
$status = $initial_status;
//Mark the extracted page as visited
$status++;
db_query("UPDATE {crawler_links} SET crawler_id = %d, status = %d WHERE status = %d LIMIT 1",
$crawler_id,
$status,
$initial_status);
// Get the link from crawler_links table
// Update the status field to sign as executed that link
// (The following two lines could be move to the end of the function i think without problems)
db_query("UPDATE {crawler_links} SET status = status + 1 WHERE status = %d AND crawler_id = %d",
$status,
$crawler_id);
$status++;
// Create a new object and parse the page
$obj = new DrupalSecurityScannerClass();
// Set the cookie
$session_cookie =
variable_get('security_scanner_cookie',
'');
$obj->
curl_options =
array(
CURLOPT_COOKIE => $session_cookie,
CURLOPT_USERAGENT => 'security_scanner',
);
$obj->drupalGet($selected_results['path']);
$obj->parse();
$function_callback($obj, $selected_results);
$obj->curlClose();
}
}
/**
* Crawler: page processing function
*/
function security_scanner_page_processing($obj, $selected_results) {
$links = $obj->elements->xpath('//a');
foreach($links as $link) {
$url_to_save = (string)$link->attributes()->href;
$absolute = getAbsoluteUrl($url_to_save);
// Get the page but check:
// a - if it's logout link, that makes me lose the cookie.
// b - if it's security scanner, skip
// c - if it's xss_injector, skip. That will launch the crawler
// d - if it's cron.php, that will make a loop
if (($parsed_url['query'] != 'q=logout') && ($parsed_url['query'] != 'q=admin/settings/security_scanner') && ($parsed_url['query'] != 'q=admin/settings/xss_injector') && ($parsed_url['file'] != 'cron.php')) {
// Here we use IGNORE to insert only one time a link into the table. ("path" is a unique index)
db_query("INSERT IGNORE INTO {crawler_links} (id, path, crawler_id, status) VALUES ('','%s','','')",
$absolute);
}
}
}
// Get the forms inside the page
$inputs = $obj->elements->xpath("//input[@name='form_id']");
foreach($inputs as $input) {
$form_id = (string)$input->attributes()->id;
// Debug line! HAS TO BE REMOVED
echo $form_id.
"Form inserted! <br />";
// Here we use again IGNORE to insert only one time a form_id into the table. ("form_id" is the primary key)
db_query("INSERT IGNORE INTO {crawler_forms} VALUES ('%s','%d')",
$form_id,
$selected_results['id']);
}
}
/**
* Implementation of the crawler page.
*/
function security_scanner_cron() {
// Check if this is the mode that we set from settings page.
// Check if the auth session cookie value is already into the db, otherwise call
// the function that retrieve this (enable multithreading)
drupal_security_scanner_get_auth_cookie();
}
// Preparing the query that has to be passed to the crawler_framework
$selection_query =
array(
'fields' =>
array('l.id'),
);
echo "I'm crawling...<br />";
crawler_framework($selection_query, 0, 'security_scanner_page_processing');
}
}
/**
* Get the cookie of the admin and insert the first link into the table crawler_links.
* There is an issue, I have to start the crawler from uid different than 1.
*/
function drupal_security_scanner_get_auth_cookie() {
// Add sleep to go round a bug inside a drupal core function. Remove it when it's changed into core.
// Create a new object, set cURL options to call the function drupal_security_scanner_curl_headers that
// saves into the variable table the admin cookie. Then set the cookie.
$obj = new DrupalSecurityScannerClass();
$obj->
curl_options =
array(
CURLOPT_HEADERFUNCTION => 'drupal_security_scanner_curl_headers',
CURLOPT_FOLLOWLOCATION => 0,
);
// Get the page with password reset and push submit button
$obj->drupalGet($initial_path);
$obj->drupalPost($initial_path,'',TRUE);
// Add the first url into the crawler_links table.
db_query("INSERT INTO {crawler_links} (id, path, crawler_id, status) VALUES ('','%s','','')",
url('admin',
array('absolute' =>
TRUE)));
return true;
}
/**
* This function will extract headers and return the lenght.
*/
function drupal_security_scanner_curl_headers($ch = NULL, $header = NULL) {
return $headers;
}
if(!
strncmp($header,
"Set-Cookie:",
11)) {
// get the cookie
$cookie =
explode(';',
$cookiestr);
}
}
/**
* Implementation of hook _perm()
*/
function security_scanner_perm() {
return array('access scanner');
}
/**
* Implementation of hook _help()
*/
function security_scanner_help($path, $arg) {
switch ($path) {
case 'security_scanner':
// Here is some help text for a custom page.
return t('This sentence contains all the letters in the English alphabet.');
}
}
?>