/tools/jscan_http/jscan_http.php
PHP | 430 lines | 263 code | 57 blank | 110 comment | 35 complexity | b5a3f1cc3a15bf4520f4df8f6c23cb53 MD5 | raw file
- #!/usr/bin/php
- <?php
- /**
- * @version $Id: jscan_http.php 344 2010-10-18 07:22:53Z eddieajau $
- * @copyright Copyright 2010 New Life in IT Pty Ltd. All rights reserved.
- * @license GNU General Public License <http://www.fsf.org/licensing/licenses/gpl.html>
- * @link http://www.theartofjoomla.com
- */
- // Maximum error reporting.
- error_reporting(E_ALL|E_STRICT);
- ini_set('display_errors', 1);
- /**
- * Main class.
- */
- class main
- {
- /**
- * @var string The version of the script.
- */
- private $version = '1.0.1';
- /**
- * @var array An arary of options.
- */
- private $config = null;
- /**
- * @var array An array of command line options.
- */
- private $options = null;
- /**
- * @var string A list of the required and options short arguments (requires value 'n:'; optional 'n::', no argument 'n').
- */
- private $shortargs = 'a:x:n:d:b:vu:h';
- /**
- * @var string A list of the required and options long arguments (PHP 5.3 only).
- */
- private $longargs = array('help');
- /**
- * @var string The path where the script is being executed.
- */
- private $cwd = '';
- /**
- * Constructor
- */
- function __construct()
- {
- // Get the current directory
- $this->cwd = getcwd();
- // Get the command line options
- $opts = getopt($this->shortargs/*, $this->longargs*/);
- $args = $GLOBALS['argv'];
- // Following code based on
- // http://au2.php.net/manual/en/function.getopt.php#79286
- // by Francois Hill
- foreach ($opts as $o => $a)
- {
- // Look for all occurrences of option in argv and remove if found :
- // ----------------------------------------------------------------
- // Look for occurrences of -o (simple option with no value) or -o<val> (no space in between):
- while ($k = array_search("-".$o.$a,$args))
- {
- // If found remove from argv:
- if ($k) {
- unset($args[$k]);
- }
- }
- // Look for remaining occurrences of -o <val> (space in between):
- while ($k = array_search('-'.$o,$args))
- {
- // If found remove both option and value from argv:
- if($k) {
- unset($args[$k]);
- unset($args[$k+1]);
- }
- }
- }
- $this->options = (object) $opts;
- $this->argv = array_merge($args);
- // Load the ini file.
- $cfgFile = dirname(__FILE__).'/config.ini';
- if (file_exists($cfgFile)) {
- $this->config = (object) parse_ini_file($cfgFile);
- }
- else {
- $this->config = new stdClass;
- }
- // Display help if set.
- if (isset($this->options->h)) {
- $this->help();
- exit(0);
- }
- }
- /**
- * Get an option or configuration setting.
- *
- * @param string The name of the option or configuration variable.
- * @param string An optional default to return if the option is not set.
- */
- protected function get($key, $default = null)
- {
- if (isset($this->options->$key)) {
- return empty($this->options->$key) ? true : $this->options->$key;
- }
- else if (isset($this->config->$key)) {
- return $this->config->$key;
- }
- else {
- return $default;
- }
- }
- /**
- * Displays a message.
- *
- * @param string The message.
- */
- public function out($text = '')
- {
- echo "\n$text";
- }
- /**
- * Display help text
- */
- protected function help()
- {
- $this->out('jscan_http version '.$this->version);
- $this->out();
- $this->out('Description:');
- $this->out();
- $this->out('scan_http is a command line utility that scans the directory of a Joomla site for PHP files and tries to acces them directly via the web server. ' .
- 'Ideally no output should be received from directly accessing any PHP file, with the exception of index.php, index2.php (etc) which should display regular HTML output. ' .
- 'Some files will return warning text, such as "Restricted Access", and these will be ignored and considered safe. ' .
- 'Any unexpected output will be logged to the console.');
- $this->out();
- $this->out('Released under the GNU General Public License.');
- $this->out('Copyright 2010 New Life in It Pty Ltd. All rights reserved.');
- $this->out('Visit http://www.theartofjoomla.com/extensions/http-scan.html');
- $this->out();
- $this->out('Installation:');
- $this->out();
- $this->out('Copy this file into the root of your Joomla web site (or another directory and use the -d option to specify the directory to scan).');
- $this->out();
- $this->out('Usage:');
- $this->out();
- $this->out('./jscan_http [options]');
- $this->out();
- $this->out('Options:');
- $this->out();
- $this->out('-a "string1|string2" Additional responses that are allowed when a file is directly accessed.');
- $this->out('-b directory The base directory of the web server (eg, /usr/local/www).');
- $this->out('-d directory An alternative directory to scan (current working directory assumed as default).');
- $this->out('-u uri The URI for the web site (defaults to "http://localhost").');
- $this->out('-n number Sets a limit on the number of files to scan.');
- $this->out('-h See help text.');
- $this->out('-v Verbose mode. Show the results for all files parsed, not just those that fail.');
- $this->out('-x "regex" A regular expression for file paths to exclude.');
- $this->out();
- $this->out('Examples:');
- $this->out();
- $this->out('To scan the 1.6 trunk remotely on localhost.');
- $this->out('./jscan_http.php -b /Users/foobar/htdocs -d /Users/foobar/htdocs/Joomla/trunk -x "/tests/"');
- $this->out();
- $this->out();
- }
- //
- // ^^^ End of generic CLI scaffolding.
- //
- /**
- * Evaluates the output.
- *
- * @param string The output of the page.
- * @return mixed 1 if no output (good), 2 if expected result, otherwise the actual output.
- */
- function evaluate($output)
- {
- static $good;
- // Empty output is the best output.
- if ($output === '') {
- return 1;
- }
- if ($good === null) {
- $good = array(
- // Newer formats.
- 'Restricted access',
- 'Restricted access.',
- // Older formats.
- 'Direct Access to this location is not allowed',
- 'Direct Access to this location is not allowed.',
- 'Direct Access to this file is not allowed',
- 'Direct Access to this file is not allowed.',
- 'No direct access allowed ;)',
- 'Direct Access Is Not Allowed',
- 'Direct access to this file is prohibited',
- 'Direct access to this file is prohibited.',
- 'JS: No Direct Access',
- 'Restricted access',
- 'Access Denied!',
- 'No Acces to this file!',
- 'No access to this file!',
- 'Access Denied!',
- 'Access Denied.',
- 'restriced access',
- 'Restricted access to this plugin',
- 'Forbidden',
- 'Access restricted',
- 'Restricted',
- 'Sorry Restricted access',
- 'Access Denied.',
- 'No Direct Access',
- 'access denied',
- 'Invalid request',
- 'Invalid request.',
- '=;)',
- ';)',
- 'No direct access allowed ;)',
- // Options for well-behaved sql install files.
- '--',
- '#',
- '# Restricted access',
- '-- Restricted access',
- '-- Restricted access.',
- // Special case for log files.
- '#Direct Access To Log Files Not Permitted',
- );
- // Additional allowed strings.
- if ($allowed = $this->get('a')) {
- $good = array_merge($good, explode('|', $allowed));
- }
- }
- foreach ($good as $test)
- {
- if (strcasecmp($test, $output) == 0) {
- return 2;
- }
- }
- return false;
- }
- /**
- * Get repsonse code.
- *
- * @return mixed True if the response is ok, Exception otherwise.
- */
- private function getReponse($url)
- {
- $headers = @get_headers($url);
- preg_match('#^HTTP/([0-9\.]+)\s+(\d+)\s+(.*)#i', $headers[0], $matches);
- if (!isset($matches[1])) {
- return new Exception($headers[0], 0);
- }
- $version = $matches[1];
- $code = $matches[2];
- $message = $matches[3];
- // Test for valid response codes.
- if ($code == 200) {
- return true;
- } else {
- return new Exception($message, $code);
- }
- }
- /**
- * Trucate a string for output.
- *
- * @param string The source string.
- * @param int The length of the string.
- * @return string
- */
- private function truncate($source, $chars = 50)
- {
- // Replace white-space and linebreaks.
- $source = str_replace(
- array(
- "\n",
- "\r"
- ),
- ' ',
- $source
- );
- $source = preg_replace('#\s+#', ' ', $source);
- return substr($source, 0, $chars);
- }
- /**
- * The run method.
- */
- public function run()
- {
- // Let's do some work.
- $tStart = microtime(true);
- // Get the root directory we are parsing (current directory assumed).
- $root = $this->get('d', getcwd());
- // Get the host that we need to call.
- $host = $this->get('u', 'http://localhost');
- $host = rtrim($host, '/');
- // Get the base directory of the web server.
- $stub = $this->get('b', getcwd());
- // Get a counter to limit the number of files to scan.
- $limit = $this->get('n', 0);
- // Regex of paths to exclude.
- $exclude = $this->get('x');
- $this->out();
- $this->out(sprintf('JSCAN_HTTP Version %s', $this->version));
- $this->out(sprintf('Scanning directory: %s (change with -d option)', $root));
- $this->out(sprintf('Scanning URL: %s%s (change with -u and -b options)', $host, $stub));
- if ($limit) {
- $this->out(sprintf('Using -n option. Limiting scan to %d files.', $limit));
- }
- if ($exclude) {
- $this->out(sprintf('Using -x option. Exclusing files matching pattern of #%s#.', $exclude));
- }
- $this->out();
- //
- // Beginning parsing web pages.
- //
- $nFiles = 0;
- $nScans = 0;
- $nFails = 0;
- // Get the recursive directory iterator.
- $it = new RecursiveDirectoryIterator($root);
- //print_r(get_class_methods($it));
- foreach (new RecursiveIteratorIterator($it) as $artifact)
- {
- // Check if the current artifact is a file.
- if ($artifact->isFile()) {
- // Ignore this file.
- if (((string) $artifact) == __FILE__) {
- continue;
- }
- $nFiles++;
- // Check the file is a .php file.
- if (preg_match("#\.php$#", (string) $artifact)) {
- // Strip the stub so we can add this to the host.
- $path = str_replace($stub, '', $artifact);
- $file = str_replace($root, '', $artifact);
- // Check exclude regex.
- if (!empty($exclude)) {
- if (preg_match("#$exclude#", $file)) {
- continue;
- }
- }
- $nScans++;
- // Test the response.
- $response = $this->getReponse($host.$path);
- if ($response instanceof Exception) {
- $this->out(sprintf('%3d * %-60s >>> %s', $response->getCode(), $file, $response->getMessage()));
- $nFails++;
- }
- else {
- $output = trim(file_get_contents($host.$path));
- $result = $this->evaluate($output);
- if ($result === false) {
- $this->out(sprintf('%3d * %-60s >>> %s', $result, $file, $this->truncate($output, 40)));
- $nFails++;
- }
- else if ($this->get('v')) {
- $this->out(sprintf('%3d %-60s', $result, $file));
- }
- }
- // Check the count
- if ($limit && $nScans >= $limit) {
- break;
- }
- }
- }
- }
- $tFinish = microtime(true);
- $this->out(sprintf(
- 'Files scanned: %d (out of %d); Possible failures found: %d; Processing time: %.3fs',
- $nScans, $nFiles, $nFails, $tFinish - $tStart
- ));
- }
- }
- // Instantiate the main class and run.
- $main = new main;
- $main->run();
- $main->out();
- $main->out();