PageRenderTime 45ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/tools/jscan_http/jscan_http.php

https://bitbucket.org/eddieajau/the-art-of-joomla-archive
PHP | 430 lines | 263 code | 57 blank | 110 comment | 35 complexity | b5a3f1cc3a15bf4520f4df8f6c23cb53 MD5 | raw file
  1. #!/usr/bin/php
  2. <?php
  3. /**
  4. * @version $Id: jscan_http.php 344 2010-10-18 07:22:53Z eddieajau $
  5. * @copyright Copyright 2010 New Life in IT Pty Ltd. All rights reserved.
  6. * @license GNU General Public License <http://www.fsf.org/licensing/licenses/gpl.html>
  7. * @link http://www.theartofjoomla.com
  8. */
  9. // Maximum error reporting.
  10. error_reporting(E_ALL|E_STRICT);
  11. ini_set('display_errors', 1);
  12. /**
  13. * Main class.
  14. */
  15. class main
  16. {
  17. /**
  18. * @var string The version of the script.
  19. */
  20. private $version = '1.0.1';
  21. /**
  22. * @var array An arary of options.
  23. */
  24. private $config = null;
  25. /**
  26. * @var array An array of command line options.
  27. */
  28. private $options = null;
  29. /**
  30. * @var string A list of the required and options short arguments (requires value 'n:'; optional 'n::', no argument 'n').
  31. */
  32. private $shortargs = 'a:x:n:d:b:vu:h';
  33. /**
  34. * @var string A list of the required and options long arguments (PHP 5.3 only).
  35. */
  36. private $longargs = array('help');
  37. /**
  38. * @var string The path where the script is being executed.
  39. */
  40. private $cwd = '';
  41. /**
  42. * Constructor
  43. */
  44. function __construct()
  45. {
  46. // Get the current directory
  47. $this->cwd = getcwd();
  48. // Get the command line options
  49. $opts = getopt($this->shortargs/*, $this->longargs*/);
  50. $args = $GLOBALS['argv'];
  51. // Following code based on
  52. // http://au2.php.net/manual/en/function.getopt.php#79286
  53. // by Francois Hill
  54. foreach ($opts as $o => $a)
  55. {
  56. // Look for all occurrences of option in argv and remove if found :
  57. // ----------------------------------------------------------------
  58. // Look for occurrences of -o (simple option with no value) or -o<val> (no space in between):
  59. while ($k = array_search("-".$o.$a,$args))
  60. {
  61. // If found remove from argv:
  62. if ($k) {
  63. unset($args[$k]);
  64. }
  65. }
  66. // Look for remaining occurrences of -o <val> (space in between):
  67. while ($k = array_search('-'.$o,$args))
  68. {
  69. // If found remove both option and value from argv:
  70. if($k) {
  71. unset($args[$k]);
  72. unset($args[$k+1]);
  73. }
  74. }
  75. }
  76. $this->options = (object) $opts;
  77. $this->argv = array_merge($args);
  78. // Load the ini file.
  79. $cfgFile = dirname(__FILE__).'/config.ini';
  80. if (file_exists($cfgFile)) {
  81. $this->config = (object) parse_ini_file($cfgFile);
  82. }
  83. else {
  84. $this->config = new stdClass;
  85. }
  86. // Display help if set.
  87. if (isset($this->options->h)) {
  88. $this->help();
  89. exit(0);
  90. }
  91. }
  92. /**
  93. * Get an option or configuration setting.
  94. *
  95. * @param string The name of the option or configuration variable.
  96. * @param string An optional default to return if the option is not set.
  97. */
  98. protected function get($key, $default = null)
  99. {
  100. if (isset($this->options->$key)) {
  101. return empty($this->options->$key) ? true : $this->options->$key;
  102. }
  103. else if (isset($this->config->$key)) {
  104. return $this->config->$key;
  105. }
  106. else {
  107. return $default;
  108. }
  109. }
  110. /**
  111. * Displays a message.
  112. *
  113. * @param string The message.
  114. */
  115. public function out($text = '')
  116. {
  117. echo "\n$text";
  118. }
  119. /**
  120. * Display help text
  121. */
  122. protected function help()
  123. {
  124. $this->out('jscan_http version '.$this->version);
  125. $this->out();
  126. $this->out('Description:');
  127. $this->out();
  128. $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. ' .
  129. '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. ' .
  130. 'Some files will return warning text, such as "Restricted Access", and these will be ignored and considered safe. ' .
  131. 'Any unexpected output will be logged to the console.');
  132. $this->out();
  133. $this->out('Released under the GNU General Public License.');
  134. $this->out('Copyright 2010 New Life in It Pty Ltd. All rights reserved.');
  135. $this->out('Visit http://www.theartofjoomla.com/extensions/http-scan.html');
  136. $this->out();
  137. $this->out('Installation:');
  138. $this->out();
  139. $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).');
  140. $this->out();
  141. $this->out('Usage:');
  142. $this->out();
  143. $this->out('./jscan_http [options]');
  144. $this->out();
  145. $this->out('Options:');
  146. $this->out();
  147. $this->out('-a "string1|string2" Additional responses that are allowed when a file is directly accessed.');
  148. $this->out('-b directory The base directory of the web server (eg, /usr/local/www).');
  149. $this->out('-d directory An alternative directory to scan (current working directory assumed as default).');
  150. $this->out('-u uri The URI for the web site (defaults to "http://localhost").');
  151. $this->out('-n number Sets a limit on the number of files to scan.');
  152. $this->out('-h See help text.');
  153. $this->out('-v Verbose mode. Show the results for all files parsed, not just those that fail.');
  154. $this->out('-x "regex" A regular expression for file paths to exclude.');
  155. $this->out();
  156. $this->out('Examples:');
  157. $this->out();
  158. $this->out('To scan the 1.6 trunk remotely on localhost.');
  159. $this->out('./jscan_http.php -b /Users/foobar/htdocs -d /Users/foobar/htdocs/Joomla/trunk -x "/tests/"');
  160. $this->out();
  161. $this->out();
  162. }
  163. //
  164. // ^^^ End of generic CLI scaffolding.
  165. //
  166. /**
  167. * Evaluates the output.
  168. *
  169. * @param string The output of the page.
  170. * @return mixed 1 if no output (good), 2 if expected result, otherwise the actual output.
  171. */
  172. function evaluate($output)
  173. {
  174. static $good;
  175. // Empty output is the best output.
  176. if ($output === '') {
  177. return 1;
  178. }
  179. if ($good === null) {
  180. $good = array(
  181. // Newer formats.
  182. 'Restricted access',
  183. 'Restricted access.',
  184. // Older formats.
  185. 'Direct Access to this location is not allowed',
  186. 'Direct Access to this location is not allowed.',
  187. 'Direct Access to this file is not allowed',
  188. 'Direct Access to this file is not allowed.',
  189. 'No direct access allowed ;)',
  190. 'Direct Access Is Not Allowed',
  191. 'Direct access to this file is prohibited',
  192. 'Direct access to this file is prohibited.',
  193. 'JS: No Direct Access',
  194. 'Restricted access',
  195. 'Access Denied!',
  196. 'No Acces to this file!',
  197. 'No access to this file!',
  198. 'Access Denied!',
  199. 'Access Denied.',
  200. 'restriced access',
  201. 'Restricted access to this plugin',
  202. 'Forbidden',
  203. 'Access restricted',
  204. 'Restricted',
  205. 'Sorry Restricted access',
  206. 'Access Denied.',
  207. 'No Direct Access',
  208. 'access denied',
  209. 'Invalid request',
  210. 'Invalid request.',
  211. '=;)',
  212. ';)',
  213. 'No direct access allowed ;)',
  214. // Options for well-behaved sql install files.
  215. '--',
  216. '#',
  217. '# Restricted access',
  218. '-- Restricted access',
  219. '-- Restricted access.',
  220. // Special case for log files.
  221. '#Direct Access To Log Files Not Permitted',
  222. );
  223. // Additional allowed strings.
  224. if ($allowed = $this->get('a')) {
  225. $good = array_merge($good, explode('|', $allowed));
  226. }
  227. }
  228. foreach ($good as $test)
  229. {
  230. if (strcasecmp($test, $output) == 0) {
  231. return 2;
  232. }
  233. }
  234. return false;
  235. }
  236. /**
  237. * Get repsonse code.
  238. *
  239. * @return mixed True if the response is ok, Exception otherwise.
  240. */
  241. private function getReponse($url)
  242. {
  243. $headers = @get_headers($url);
  244. preg_match('#^HTTP/([0-9\.]+)\s+(\d+)\s+(.*)#i', $headers[0], $matches);
  245. if (!isset($matches[1])) {
  246. return new Exception($headers[0], 0);
  247. }
  248. $version = $matches[1];
  249. $code = $matches[2];
  250. $message = $matches[3];
  251. // Test for valid response codes.
  252. if ($code == 200) {
  253. return true;
  254. } else {
  255. return new Exception($message, $code);
  256. }
  257. }
  258. /**
  259. * Trucate a string for output.
  260. *
  261. * @param string The source string.
  262. * @param int The length of the string.
  263. * @return string
  264. */
  265. private function truncate($source, $chars = 50)
  266. {
  267. // Replace white-space and linebreaks.
  268. $source = str_replace(
  269. array(
  270. "\n",
  271. "\r"
  272. ),
  273. ' ',
  274. $source
  275. );
  276. $source = preg_replace('#\s+#', ' ', $source);
  277. return substr($source, 0, $chars);
  278. }
  279. /**
  280. * The run method.
  281. */
  282. public function run()
  283. {
  284. // Let's do some work.
  285. $tStart = microtime(true);
  286. // Get the root directory we are parsing (current directory assumed).
  287. $root = $this->get('d', getcwd());
  288. // Get the host that we need to call.
  289. $host = $this->get('u', 'http://localhost');
  290. $host = rtrim($host, '/');
  291. // Get the base directory of the web server.
  292. $stub = $this->get('b', getcwd());
  293. // Get a counter to limit the number of files to scan.
  294. $limit = $this->get('n', 0);
  295. // Regex of paths to exclude.
  296. $exclude = $this->get('x');
  297. $this->out();
  298. $this->out(sprintf('JSCAN_HTTP Version %s', $this->version));
  299. $this->out(sprintf('Scanning directory: %s (change with -d option)', $root));
  300. $this->out(sprintf('Scanning URL: %s%s (change with -u and -b options)', $host, $stub));
  301. if ($limit) {
  302. $this->out(sprintf('Using -n option. Limiting scan to %d files.', $limit));
  303. }
  304. if ($exclude) {
  305. $this->out(sprintf('Using -x option. Exclusing files matching pattern of #%s#.', $exclude));
  306. }
  307. $this->out();
  308. //
  309. // Beginning parsing web pages.
  310. //
  311. $nFiles = 0;
  312. $nScans = 0;
  313. $nFails = 0;
  314. // Get the recursive directory iterator.
  315. $it = new RecursiveDirectoryIterator($root);
  316. //print_r(get_class_methods($it));
  317. foreach (new RecursiveIteratorIterator($it) as $artifact)
  318. {
  319. // Check if the current artifact is a file.
  320. if ($artifact->isFile()) {
  321. // Ignore this file.
  322. if (((string) $artifact) == __FILE__) {
  323. continue;
  324. }
  325. $nFiles++;
  326. // Check the file is a .php file.
  327. if (preg_match("#\.php$#", (string) $artifact)) {
  328. // Strip the stub so we can add this to the host.
  329. $path = str_replace($stub, '', $artifact);
  330. $file = str_replace($root, '', $artifact);
  331. // Check exclude regex.
  332. if (!empty($exclude)) {
  333. if (preg_match("#$exclude#", $file)) {
  334. continue;
  335. }
  336. }
  337. $nScans++;
  338. // Test the response.
  339. $response = $this->getReponse($host.$path);
  340. if ($response instanceof Exception) {
  341. $this->out(sprintf('%3d * %-60s >>> %s', $response->getCode(), $file, $response->getMessage()));
  342. $nFails++;
  343. }
  344. else {
  345. $output = trim(file_get_contents($host.$path));
  346. $result = $this->evaluate($output);
  347. if ($result === false) {
  348. $this->out(sprintf('%3d * %-60s >>> %s', $result, $file, $this->truncate($output, 40)));
  349. $nFails++;
  350. }
  351. else if ($this->get('v')) {
  352. $this->out(sprintf('%3d %-60s', $result, $file));
  353. }
  354. }
  355. // Check the count
  356. if ($limit && $nScans >= $limit) {
  357. break;
  358. }
  359. }
  360. }
  361. }
  362. $tFinish = microtime(true);
  363. $this->out(sprintf(
  364. 'Files scanned: %d (out of %d); Possible failures found: %d; Processing time: %.3fs',
  365. $nScans, $nFiles, $nFails, $tFinish - $tStart
  366. ));
  367. }
  368. }
  369. // Instantiate the main class and run.
  370. $main = new main;
  371. $main->run();
  372. $main->out();
  373. $main->out();