PageRenderTime 54ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/phpmyfaq/inc/Init.php

http://github.com/thorsten/phpMyFAQ
PHP | 324 lines | 214 code | 21 blank | 89 comment | 21 complexity | 6203dbc4b3d9d74a240ab1e80b331606 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, LGPL-2.1, LGPL-3.0
  1. <?php
  2. /**
  3. * Some basic functions and PMF_Init class.
  4. *
  5. * PHP Version 5.3
  6. *
  7. * This Source Code Form is subject to the terms of the Mozilla Public License,
  8. * v. 2.0. If a copy of the MPL was not distributed with this file, You can
  9. * obtain one at http://mozilla.org/MPL/2.0/.
  10. *
  11. * Portions created by Christian Stocker are Copyright (c) 2001-2008 Liip AG.
  12. * All Rights Reserved.
  13. *
  14. * @category phpMyFAQ
  15. * @package PMF_Init
  16. * @author Johann-Peter Hartmann <hartmann@mayflower.de>
  17. * @author Thorsten Rinne <thorsten@phpmyfaq.de>
  18. * @author Stefan Esser <sesser@php.net>
  19. * @author Matteo Scaramuccia <matteo@phpmyfaq.de>
  20. * @author Christian Stocker <chregu@bitflux.ch>
  21. * @copyright 2005-2012 phpMyFAQ Team
  22. * @license http://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
  23. * @link http://www.phpmyfaq.de
  24. * @since 2005-09-24
  25. */
  26. if (!defined('IS_VALID_PHPMYFAQ')) {
  27. exit();
  28. }
  29. /**
  30. * PMF_Init
  31. *
  32. * This class provides methods to clean the request environment from global
  33. * variables, unescaped slashes and XSS in the request string. It also detects
  34. * and sets the current language.
  35. *
  36. * @category phpMyFAQ
  37. * @package PMF_Init
  38. * @author Johann-Peter Hartmann <hartmann@mayflower.de>
  39. * @author Thorsten Rinne <thorsten@phpmyfaq.de>
  40. * @author Stefan Esser <sesser@php.net>
  41. * @author Matteo Scaramuccia <matteo@phpmyfaq.de>
  42. * @author Christian Stocker <chregu@bitflux.ch>
  43. * @copyright 2005-2012 phpMyFAQ Team
  44. * @license http://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
  45. * @link http://www.phpmyfaq.de
  46. * @since 2005-09-24
  47. */
  48. class PMF_Init
  49. {
  50. /**
  51. * cleanRequest
  52. *
  53. * Cleans the request environment from:
  54. * - global variables,
  55. * - unescaped slashes,
  56. * - xss in the request string,
  57. * - uncorrect filenames when file are uploaded.
  58. *
  59. * @return void
  60. */
  61. public static function cleanRequest()
  62. {
  63. if (isset($_SERVER['HTTP_USER_AGENT'])) {
  64. $_SERVER['HTTP_USER_AGENT'] = urlencode($_SERVER['HTTP_USER_AGENT']);
  65. }
  66. // remove global registered variables to avoid injections
  67. if (ini_get('register_globals')) {
  68. self::_unregisterGlobalVariables();
  69. }
  70. // clean external variables
  71. $externals = array('_REQUEST', '_GET', '_POST', '_COOKIE');
  72. foreach ($externals as $external) {
  73. if (isset($GLOBALS[$external]) && is_array($GLOBALS[$external])) {
  74. // first clean XSS issues
  75. $newvalues = $GLOBALS[$external];
  76. $newvalues = self::_removeXSSGPC($newvalues);
  77. // then remove magic quotes
  78. $newvalues = self::_removeMagicQuotesGPC($newvalues);
  79. // clean old array and insert cleaned data
  80. foreach (array_keys($GLOBALS[$external]) as $key) {
  81. $GLOBALS[$external][$key] = null;
  82. unset($GLOBALS[$external][$key]);
  83. }
  84. foreach (array_keys($newvalues) as $key) {
  85. $GLOBALS[$external][$key] = $newvalues[$key];
  86. }
  87. }
  88. }
  89. // clean external filenames (uploaded files)
  90. self::_cleanFilenames();
  91. }
  92. /**
  93. * Clean up a filename: if anything goes wrong, an empty string will be returned
  94. *
  95. * @param string $filename Filename
  96. *
  97. * @return string
  98. */
  99. private static function _basicFilenameClean($filename)
  100. {
  101. global $denyUploadExts;
  102. // Remove the magic quotes if enabled
  103. $filename = (ini_get('magic_quotes_gpc') ? stripslashes($filename) : $filename);
  104. $path_parts = pathinfo($filename);
  105. // We need a filename without any path info
  106. if ($path_parts['basename'] !== $filename) {
  107. return '';
  108. }
  109. // We need a filename with at least 1 chars plus the optional extension
  110. if (isset($path_parts['extension']) && ($path_parts['basename'] == '.'.$path_parts['extension'])) {
  111. return '';
  112. }
  113. if (!isset($path_parts['extension']) && (PMF_String_Basic::strlen($path_parts['basename']) == 0)) {
  114. return '';
  115. }
  116. // Deny some extensions (see inc/constants.php), if any
  117. if (!isset($path_parts['extension'])) {
  118. $path_parts['extension'] = '';
  119. }
  120. if (count($denyUploadExts) > 0) {
  121. if (in_array(strtolower($path_parts['extension']), $denyUploadExts)) {
  122. return '';
  123. }
  124. }
  125. // Clean the file to remove some chars depending on the server OS
  126. // 0. main/rfc1867.c: rfc1867_post_handler removes any char before the last occurence of \/
  127. // 1. Besides \/ on Windows: :*?"<>|
  128. if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
  129. $reservedChars = array(':', '*', '?', '"', '<', '>', "'", '|');
  130. $filename = str_replace($reservedChars, '_', $filename);
  131. }
  132. return $filename;
  133. }
  134. /**
  135. * Clean the filename of any uploaded file by the user and force an error
  136. * when calling is_uploaded_file($_FILES[key]['tmp_name']) if the cleanup goes wrong
  137. *
  138. * @return void
  139. */
  140. private static function _cleanFilenames()
  141. {
  142. reset($_FILES);
  143. while (list($key, $value) = each($_FILES)) {
  144. if (is_array($_FILES[$key]['name'])) {
  145. reset($_FILES[$key]['name']);
  146. // We have a multiple upload with the same name for <input />
  147. while (list($idx, $value2) = each($_FILES[$key]['name'])) {
  148. $_FILES[$key]['name'][$idx] = self::_basicFilenameClean($_FILES[$key]['name'][$idx]);
  149. if ('' == $_FILES[$key]['name'][$idx]) {
  150. $_FILES[$key]['type'][$idx] = '';
  151. $_FILES[$key]['tmp_name'][$idx] = '';
  152. $_FILES[$key]['size'][$idx] = 0;
  153. $_FILES[$key]['error'][$idx] = UPLOAD_ERR_NO_FILE;
  154. }
  155. }
  156. reset($_FILES[$key]['name']);
  157. } else {
  158. $_FILES[$key]['name'] = self::_basicFilenameClean($_FILES[$key]['name']);
  159. if ('' == $_FILES[$key]['name']) {
  160. $_FILES[$key]['type'] = '';
  161. $_FILES[$key]['tmp_name'] = '';
  162. $_FILES[$key]['size'] = 0;
  163. $_FILES[$key]['error'] = UPLOAD_ERR_NO_FILE;
  164. }
  165. }
  166. }
  167. reset($_FILES);
  168. }
  169. /**
  170. * This function deregisters the global variables only when 'register_globals = On'.
  171. * Note: you must assure that 'session_start()' is called AFTER this function and not BEFORE,
  172. * otherwise each $_SESSION key will be set to NULL because $GLOBALS
  173. * has an entry, as copy-by-ref, for each $_SESSION key when 'register_globals = On'.
  174. *
  175. * @return void
  176. */
  177. private static function _unregisterGlobalVariables()
  178. {
  179. if (!ini_get('register_globals')) {
  180. return;
  181. }
  182. if (isset($_REQUEST['GLOBALS']) || isset($_FILES['GLOBALS'])) {
  183. die('GLOBALS overwrite attempt detected.');
  184. }
  185. $noUnset = array('GLOBALS', '_GET', '_POST', '_COOKIE', '_REQUEST', '_SERVER', '_ENV', '_FILES');
  186. $input = array_merge($_GET, $_POST, $_COOKIE, $_SERVER, $_ENV, $_FILES, isset($_SESSION) && is_array($_SESSION) ? $_SESSION : array());
  187. foreach (array_keys($input) as $k) {
  188. if (!in_array($k, $noUnset) && isset($GLOBALS[$k])) {
  189. $GLOBALS[$k] = null;
  190. unset($GLOBALS[$k]);
  191. }
  192. }
  193. }
  194. /**
  195. * This function removes the magic quotes if they are enabled.
  196. *
  197. * @param array $data Array of data
  198. *
  199. * @return array
  200. */
  201. private static function _removeMagicQuotesGPC(Array $data)
  202. {
  203. static $recursionCounter = 0;
  204. // Avoid webserver crashes. For any detail, see: http://www.php-security.org/MOPB/MOPB-02-2007.html
  205. // Note: 1000 is an heuristic value, large enough to be "transparent" to PMF.
  206. if ($recursionCounter > 1000) {
  207. die('Deep recursion attack detected.');
  208. }
  209. if (ini_get('magic_quotes_gpc')) {
  210. $addedData = array();
  211. foreach ($data as $key => $val) {
  212. $key = addslashes($key);
  213. if (is_array($val)) {
  214. $recursionCounter++;
  215. $addedData[$key] = self::_removeMagicQuotesGPC($val);
  216. } else {
  217. $addedData[$key] = $val;
  218. }
  219. }
  220. return $addedData;
  221. }
  222. return $data;
  223. }
  224. /**
  225. * Cleans a html string from some xss issues
  226. *
  227. * @param string $string String
  228. *
  229. * @return string
  230. */
  231. private static function _basicXSSClean($string)
  232. {
  233. if (strpos($string, '\0') !== false) {
  234. return null;
  235. }
  236. if (ini_get('magic_quotes_gpc')) {
  237. $string = stripslashes($string);
  238. }
  239. $string = str_replace(array("&amp;", "&lt;", "&gt;"), array("&amp;amp;", "&amp;lt;", "&amp;gt;"), $string);
  240. // fix &entitiy\n;
  241. $string = preg_replace('#(&\#*\w+)[\x00-\x20]+;#', "$1;", $string);
  242. $string = preg_replace('#(&\#x*)([0-9A-F]+);*#i', "$1$2;", $string);
  243. $string = html_entity_decode($string, ENT_COMPAT, 'utf-8');
  244. // remove any attribute starting with "on" or xmlns
  245. $string = preg_replace('#(<[^>]+[\x00-\x20\"\'\/])(on|xmlns)[^>]*>#iU', "$1>", $string);
  246. // remove javascript: and vbscript: protocol
  247. $string = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([\`\'\"]*)[\\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iU', '$1=$2nojavascript...', $string);
  248. $string = preg_replace('#([a-z]*)[\x00-\x20]*=([\'\"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iU', '$1=$2novbscript...', $string);
  249. $string = preg_replace('#([a-z]*)[\x00-\x20]*=([\'\"]*)[\x00-\x20]*-moz-binding[\x00-\x20]*:#U', '$1=$2nomozbinding...', $string);
  250. $string = preg_replace('#([a-z]*)[\x00-\x20]*=([\'\"]*)[\x00-\x20]*data[\x00-\x20]*:#U', '$1=$2nodata...', $string);
  251. //<span style="width: expression(alert('Ping!'));"></span>
  252. // only works in ie...
  253. $string = preg_replace('#(<[^>]+)style[\x00-\x20]*=[\x00-\x20]*([\`\'\"]*).*expression[\x00-\x20]*\([^>]*>#iU', "$1>", $string);
  254. $string = preg_replace('#(<[^>]+)style[\x00-\x20]*=[\x00-\x20]*([\`\'\"]*).*behaviour[\x00-\x20]*\([^>]*>#iU', "$1>", $string);
  255. $string = preg_replace('#(<[^>]+)style[\x00-\x20]*=[\x00-\x20]*([\`\'\"]*).*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:*[^>]*>#iU', "$1>", $string);
  256. //remove namespaced elements (we do not need them...)
  257. $string = preg_replace('#</*\w+:\w[^>]*>#i', "", $string);
  258. //remove really unwanted tags
  259. do {
  260. $oldstring = $string;
  261. $string = preg_replace('#</*(applet|meta|xml|blink|link|style|script|embed|object|iframe|frame|frameset|ilayer|layer|bgsound|title|base)[^>]*>#i', "", $string);
  262. } while ($oldstring != $string);
  263. return $string;
  264. }
  265. /**
  266. * Removes xss from an array
  267. *
  268. * @param array $data Array of data
  269. *
  270. * @return array
  271. */
  272. private static function _removeXSSGPC(Array $data)
  273. {
  274. static $recursionCounter = 0;
  275. // Avoid webserver crashes. For any detail, see: http://www.php-security.org/MOPB/MOPB-02-2007.html
  276. // Note: 1000 is an heuristic value, large enough to be "transparent" to PMF.
  277. if ($recursionCounter > 1000) {
  278. die('Deep recursion attack detected.');
  279. }
  280. $cleanData = array();
  281. foreach ($data as $key => $val) {
  282. $key = self::_basicXSSClean($key);
  283. if (is_array($val)) {
  284. $recursionCounter++;
  285. $cleanData[$key] = self::_removeXSSGPC($val);
  286. } else {
  287. $cleanData[$key] = self::_basicXSSClean($val);
  288. }
  289. }
  290. return $cleanData;
  291. }
  292. }