PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/fuel/core/classes/security.php

https://bitbucket.org/trujka/codegrounds
PHP | 412 lines | 270 code | 48 blank | 94 comment | 19 complexity | bbaef64c499926ee5ec4e2439e5cec87 MD5 | raw file
Possible License(s): MIT, BSD-3-Clause, LGPL-2.1
  1. <?php
  2. /**
  3. * Part of the Fuel framework.
  4. *
  5. * @package Fuel
  6. * @version 1.6
  7. * @author Fuel Development Team
  8. * @license MIT License
  9. * @copyright 2010 - 2013 Fuel Development Team
  10. * @link http://fuelphp.com
  11. */
  12. namespace Fuel\Core;
  13. class SecurityException extends \DomainException {}
  14. /**
  15. * Security Class
  16. *
  17. * @package Fuel
  18. * @category Core
  19. * @author Dan Horrigan
  20. * @link http://docs.fuelphp.com/classes/security.html
  21. */
  22. class Security
  23. {
  24. /**
  25. * @var string the token as submitted in the cookie from the previous request
  26. */
  27. protected static $csrf_old_token = false;
  28. /**
  29. * @var string the array key for cookie & post vars to check for the token
  30. */
  31. protected static $csrf_token_key = false;
  32. /**
  33. * @var string the token for the next request
  34. */
  35. protected static $csrf_token = false;
  36. /**
  37. * Class init
  38. *
  39. * Fetches CSRF settings and current token
  40. *
  41. * @throws SecurityException it the CSRF token validation failed
  42. * @throws FuelException if no security output filter is defined
  43. */
  44. public static function _init()
  45. {
  46. static::$csrf_token_key = \Config::get('security.csrf_token_key', 'fuel_csrf_token');
  47. static::$csrf_old_token = \Input::cookie(static::$csrf_token_key, false);
  48. // if csrf automatic checking is enabled, and it fails validation, bail out!
  49. if (\Config::get('security.csrf_autoload', true))
  50. {
  51. $check_token_methods = \Config::get('security.csrf_autoload_methods', array('post', 'put', 'delete'));
  52. if (in_array(strtolower(\Input::method()), $check_token_methods) and ! static::check_token())
  53. {
  54. throw new \SecurityException('CSRF validation failed, Possible hacking attempt detected!');
  55. }
  56. }
  57. // throw an exception if the output filter setting is missing from the app config
  58. if (\Config::get('security.output_filter', null) === null)
  59. {
  60. throw new \FuelException('There is no security.output_filter defined in your application config file');
  61. }
  62. // deal with duplicate filters, no need to slow the framework down
  63. foreach (array('output_filter', 'uri_filter', 'input_filter') as $setting)
  64. {
  65. $config = \Config::get('security.'.$setting, array());
  66. is_array($config) and \Config::set('security.'.$setting, \Arr::unique($config));
  67. }
  68. }
  69. /**
  70. * Cleans the request URI
  71. *
  72. * @param string $uri uri to clean
  73. * @param bool $strict whether to remove relative directories
  74. */
  75. public static function clean_uri($uri, $strict = false)
  76. {
  77. $filters = \Config::get('security.uri_filter', array());
  78. $filters = is_array($filters) ? $filters : array($filters);
  79. $strict and $uri = preg_replace(array("/\.+\//", '/\/+/'), '/', $uri);
  80. return static::clean($uri, $filters);
  81. }
  82. /**
  83. * Cleans the global $_GET, $_POST and $_COOKIE arrays
  84. */
  85. public static function clean_input()
  86. {
  87. $_GET = static::clean($_GET);
  88. $_POST = static::clean($_POST);
  89. $_COOKIE = static::clean($_COOKIE);
  90. }
  91. /**
  92. * Generic variable clean method
  93. */
  94. public static function clean($var, $filters = null, $type = 'security.input_filter')
  95. {
  96. is_null($filters) and $filters = \Config::get($type, array());
  97. $filters = is_array($filters) ? $filters : array($filters);
  98. foreach ($filters as $filter)
  99. {
  100. // is this filter a callable local function?
  101. if (is_string($filter) and is_callable('static::'.$filter))
  102. {
  103. $var = static::$filter($var);
  104. }
  105. // is this filter a callable function?
  106. elseif (is_callable($filter))
  107. {
  108. if (is_array($var))
  109. {
  110. foreach($var as $key => $value)
  111. {
  112. $var[$key] = call_user_func($filter, $value);
  113. }
  114. }
  115. else
  116. {
  117. $var = call_user_func($filter, $var);
  118. }
  119. }
  120. // assume it's a regex of characters to filter
  121. else
  122. {
  123. if (is_array($var))
  124. {
  125. foreach($var as $key => $value)
  126. {
  127. $var[$key] = preg_replace('#['.$filter.']#ui', '', $value);
  128. }
  129. }
  130. else
  131. {
  132. $var = preg_replace('#['.$filter.']#ui', '', $var);
  133. }
  134. }
  135. }
  136. return $var;
  137. }
  138. public static function xss_clean($value)
  139. {
  140. if ( ! is_array($value))
  141. {
  142. if ( ! function_exists('htmLawed'))
  143. {
  144. import('htmlawed/htmlawed', 'vendor');
  145. }
  146. return htmLawed($value, array('safe' => 1, 'balanced' => 0));
  147. }
  148. foreach ($value as $k => $v)
  149. {
  150. $value[$k] = static::xss_clean($v);
  151. }
  152. return $value;
  153. }
  154. public static function strip_tags($value)
  155. {
  156. if ( ! is_array($value))
  157. {
  158. $value = filter_var($value, FILTER_SANITIZE_STRING);
  159. }
  160. else
  161. {
  162. foreach ($value as $k => $v)
  163. {
  164. $value[$k] = static::strip_tags($v);
  165. }
  166. }
  167. return $value;
  168. }
  169. public static function htmlentities($value, $flags = null, $encoding = null, $double_encode = null)
  170. {
  171. static $already_cleaned = array();
  172. is_null($flags) and $flags = \Config::get('security.htmlentities_flags', ENT_QUOTES);
  173. is_null($encoding) and $encoding = \Fuel::$encoding;
  174. is_null($double_encode) and $double_encode = \Config::get('security.htmlentities_double_encode', false);
  175. // Nothing to escape for non-string scalars, or for already processed values
  176. if (is_bool($value) or is_int($value) or is_float($value) or in_array($value, $already_cleaned, true))
  177. {
  178. return $value;
  179. }
  180. if (is_string($value))
  181. {
  182. $value = htmlentities($value, $flags, $encoding, $double_encode);
  183. }
  184. elseif (is_array($value) or ($value instanceof \Iterator and $value instanceof \ArrayAccess))
  185. {
  186. // Add to $already_cleaned variable when object
  187. is_object($value) and $already_cleaned[] = $value;
  188. foreach ($value as $k => $v)
  189. {
  190. $value[$k] = static::htmlentities($v, $flags, $encoding, $double_encode);
  191. }
  192. }
  193. elseif ($value instanceof \Iterator or get_class($value) == 'stdClass')
  194. {
  195. // Add to $already_cleaned variable
  196. $already_cleaned[] = $value;
  197. foreach ($value as $k => $v)
  198. {
  199. $value->{$k} = static::htmlentities($v, $flags, $encoding, $double_encode);
  200. }
  201. }
  202. elseif (is_object($value))
  203. {
  204. // Check if the object is whitelisted and return when that's the case
  205. foreach (\Config::get('security.whitelisted_classes', array()) as $class)
  206. {
  207. if (is_a($value, $class))
  208. {
  209. // Add to $already_cleaned variable
  210. $already_cleaned[] = $value;
  211. return $value;
  212. }
  213. }
  214. // Throw exception when it wasn't whitelisted and can't be converted to String
  215. if ( ! method_exists($value, '__toString'))
  216. {
  217. throw new \RuntimeException('Object class "'.get_class($value).'" could not be converted to string or '.
  218. 'sanitized as ArrayAccess. Whitelist it in security.whitelisted_classes in app/config/config.php '.
  219. 'to allow it to be passed unchecked.');
  220. }
  221. $value = static::htmlentities((string) $value, $flags, $encoding, $double_encode);
  222. }
  223. return $value;
  224. }
  225. /**
  226. * Check CSRF Token
  227. *
  228. * @param string CSRF token to be checked, checks post when empty
  229. * @return bool
  230. */
  231. public static function check_token($value = null)
  232. {
  233. $value = $value ?: \Input::post(static::$csrf_token_key, \Input::json(static::$csrf_token_key, 'fail'));
  234. // always reset token once it's been checked and still the same
  235. if (static::fetch_token() == static::$csrf_old_token and ! empty($value))
  236. {
  237. static::set_token(true);
  238. }
  239. return $value === static::$csrf_old_token;
  240. }
  241. /**
  242. * Fetch CSRF Token for the next request
  243. *
  244. * @return string
  245. */
  246. public static function fetch_token()
  247. {
  248. if (static::$csrf_token !== false)
  249. {
  250. return static::$csrf_token;
  251. }
  252. static::set_token();
  253. return static::$csrf_token;
  254. }
  255. /**
  256. * Generate new token. Based on an example from OWASP
  257. *
  258. * @return string
  259. */
  260. public static function generate_token()
  261. {
  262. $token_base = time() . uniqid() . \Config::get('security.token_salt', '') . mt_rand(0, mt_getrandmax());
  263. if (function_exists('hash_algos') and in_array('sha512', hash_algos()))
  264. {
  265. $token = hash('sha512', $token_base);
  266. }
  267. else
  268. {
  269. $token = md5($token_base);
  270. }
  271. return $token;
  272. }
  273. protected static function set_token($reset = false)
  274. {
  275. // re-use old token when found (= not expired) and expiration is used (otherwise always reset)
  276. if ( ! $reset and static::$csrf_old_token and \Config::get('security.csrf_expiration', 0) > 0)
  277. {
  278. static::$csrf_token = static::$csrf_old_token;
  279. }
  280. // set new token for next session when necessary
  281. else
  282. {
  283. static::$csrf_token = static::generate_token();
  284. $expiration = \Config::get('security.csrf_expiration', 0);
  285. \Cookie::set(static::$csrf_token_key, static::$csrf_token, $expiration);
  286. }
  287. }
  288. /**
  289. * JS fetch token
  290. *
  291. * Produces JavaScript fuel_csrf_token() function that will return the current
  292. * CSRF token when called. Use to fill right field on form submit for AJAX operations.
  293. *
  294. * @return string
  295. */
  296. public static function js_fetch_token()
  297. {
  298. $output = '<script type="text/javascript">
  299. function fuel_csrf_token()
  300. {
  301. if (document.cookie.length > 0)
  302. {
  303. var c_name = "'.static::$csrf_token_key.'";
  304. c_start = document.cookie.indexOf(c_name + "=");
  305. if (c_start != -1)
  306. {
  307. c_start = c_start + c_name.length + 1;
  308. c_end = document.cookie.indexOf(";" , c_start);
  309. if (c_end == -1)
  310. {
  311. c_end=document.cookie.length;
  312. }
  313. return unescape(document.cookie.substring(c_start, c_end));
  314. }
  315. }
  316. return "";
  317. }'.PHP_EOL;
  318. $output .= '</script>'.PHP_EOL;
  319. return $output;
  320. }
  321. /**
  322. * JS set token
  323. *
  324. * Produces JavaScript fuel_set_csrf_token() function that will update the current
  325. * CSRF token in the form when called, based on the value of the csrf cookie
  326. *
  327. * @return string
  328. */
  329. public static function js_set_token()
  330. {
  331. $output = '<script type="text/javascript">
  332. function fuel_set_csrf_token(form)
  333. {
  334. if (document.cookie.length > 0 && typeof form != undefined)
  335. {
  336. var c_name = "'.static::$csrf_token_key.'";
  337. c_start = document.cookie.indexOf(c_name + "=");
  338. if (c_start != -1)
  339. {
  340. c_start = c_start + c_name.length + 1;
  341. c_end = document.cookie.indexOf(";" , c_start);
  342. if (c_end == -1)
  343. {
  344. c_end=document.cookie.length;
  345. }
  346. value=unescape(document.cookie.substring(c_start, c_end));
  347. if (value != "")
  348. {
  349. for(i=0; i<form.elements.length; i++)
  350. {
  351. if (form.elements[i].name == c_name)
  352. {
  353. form.elements[i].value = value;
  354. break;
  355. }
  356. }
  357. }
  358. }
  359. }
  360. }'.PHP_EOL;
  361. $output .= '</script>'.PHP_EOL;
  362. return $output;
  363. }
  364. }