PageRenderTime 53ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/system/core/Security.php

https://github.com/CodeIgniter-TW/Web
PHP | 893 lines | 464 code | 83 blank | 346 comment | 17 complexity | 7cb571ac671082fe3f1da667a8a3ec7a MD5 | raw file
  1. <?php
  2. /**
  3. * CodeIgniter
  4. *
  5. * An open source application development framework for PHP 5.2.4 or newer
  6. *
  7. * NOTICE OF LICENSE
  8. *
  9. * Licensed under the Open Software License version 3.0
  10. *
  11. * This source file is subject to the Open Software License (OSL 3.0) that is
  12. * bundled with this package in the files license.txt / license.rst. It is
  13. * also available through the world wide web at this URL:
  14. * http://opensource.org/licenses/OSL-3.0
  15. * If you did not receive a copy of the license and are unable to obtain it
  16. * through the world wide web, please send an email to
  17. * licensing@ellislab.com so we can send you a copy immediately.
  18. *
  19. * @package CodeIgniter
  20. * @author EllisLab Dev Team
  21. * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/)
  22. * @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
  23. * @link http://codeigniter.com
  24. * @since Version 1.0
  25. * @filesource
  26. */
  27. defined('BASEPATH') OR exit('No direct script access allowed');
  28. /**
  29. * Security Class
  30. *
  31. * @package CodeIgniter
  32. * @subpackage Libraries
  33. * @category Security
  34. * @author EllisLab Dev Team
  35. * @link http://codeigniter.com/user_guide/libraries/security.html
  36. */
  37. class CI_Security {
  38. /**
  39. * XSS Hash
  40. *
  41. * Random Hash for protecting URLs.
  42. *
  43. * @var string
  44. */
  45. protected $_xss_hash = '';
  46. /**
  47. * CSRF Hash
  48. *
  49. * Random hash for Cross Site Request Forgery protection cookie
  50. *
  51. * @var string
  52. */
  53. protected $_csrf_hash = '';
  54. /**
  55. * CSRF Expire time
  56. *
  57. * Expiration time for Cross Site Request Forgery protection cookie.
  58. * Defaults to two hours (in seconds).
  59. *
  60. * @var int
  61. */
  62. protected $_csrf_expire = 7200;
  63. /**
  64. * CSRF Token name
  65. *
  66. * Token name for Cross Site Request Forgery protection cookie.
  67. *
  68. * @var string
  69. */
  70. protected $_csrf_token_name = 'ci_csrf_token';
  71. /**
  72. * CSRF Cookie name
  73. *
  74. * Cookie name for Cross Site Request Forgery protection cookie.
  75. *
  76. * @var string
  77. */
  78. protected $_csrf_cookie_name = 'ci_csrf_token';
  79. /**
  80. * List of never allowed strings
  81. *
  82. * @var array
  83. */
  84. protected $_never_allowed_str = array(
  85. 'document.cookie' => '[removed]',
  86. 'document.write' => '[removed]',
  87. '.parentNode' => '[removed]',
  88. '.innerHTML' => '[removed]',
  89. 'window.location' => '[removed]',
  90. '-moz-binding' => '[removed]',
  91. '<!--' => '&lt;!--',
  92. '-->' => '--&gt;',
  93. '<![CDATA[' => '&lt;![CDATA[',
  94. '<comment>' => '&lt;comment&gt;'
  95. );
  96. /**
  97. * List of never allowed regex replacements
  98. *
  99. * @var array
  100. */
  101. protected $_never_allowed_regex = array(
  102. 'javascript\s*:',
  103. 'expression\s*(\(|&\#40;)', // CSS and IE
  104. 'vbscript\s*:', // IE, surprise!
  105. 'Redirect\s+302',
  106. "([\"'])?data\s*:[^\\1]*?base64[^\\1]*?,[^\\1]*?\\1?"
  107. );
  108. /**
  109. * Class constructor
  110. *
  111. * @return void
  112. */
  113. public function __construct()
  114. {
  115. // Is CSRF protection enabled?
  116. if (config_item('csrf_protection') === TRUE)
  117. {
  118. // CSRF config
  119. foreach (array('csrf_expire', 'csrf_token_name', 'csrf_cookie_name') as $key)
  120. {
  121. if (FALSE !== ($val = config_item($key)))
  122. {
  123. $this->{'_'.$key} = $val;
  124. }
  125. }
  126. // Append application specific cookie prefix
  127. if (config_item('cookie_prefix'))
  128. {
  129. $this->_csrf_cookie_name = config_item('cookie_prefix').$this->_csrf_cookie_name;
  130. }
  131. // Set the CSRF hash
  132. $this->_csrf_set_hash();
  133. }
  134. log_message('debug', 'Security Class Initialized');
  135. }
  136. // --------------------------------------------------------------------
  137. /**
  138. * CSRF Verify
  139. *
  140. * @return CI_Security
  141. */
  142. public function csrf_verify()
  143. {
  144. // If it's not a POST request we will set the CSRF cookie
  145. if (strtoupper($_SERVER['REQUEST_METHOD']) !== 'POST')
  146. {
  147. return $this->csrf_set_cookie();
  148. }
  149. // Check if URI has been whitelisted from CSRF checks
  150. if ($exclude_uris = config_item('csrf_exclude_uris'))
  151. {
  152. $uri = load_class('URI', 'core');
  153. if (in_array($uri->uri_string(), $exclude_uris))
  154. {
  155. return $this;
  156. }
  157. }
  158. // Do the tokens exist in both the _POST and _COOKIE arrays?
  159. if ( ! isset($_POST[$this->_csrf_token_name], $_COOKIE[$this->_csrf_cookie_name])
  160. OR $_POST[$this->_csrf_token_name] !== $_COOKIE[$this->_csrf_cookie_name]) // Do the tokens match?
  161. {
  162. $this->csrf_show_error();
  163. }
  164. // We kill this since we're done and we don't want to polute the _POST array
  165. unset($_POST[$this->_csrf_token_name]);
  166. // Regenerate on every submission?
  167. if (config_item('csrf_regenerate'))
  168. {
  169. // Nothing should last forever
  170. unset($_COOKIE[$this->_csrf_cookie_name]);
  171. $this->_csrf_hash = '';
  172. }
  173. $this->_csrf_set_hash();
  174. $this->csrf_set_cookie();
  175. log_message('debug', 'CSRF token verified');
  176. return $this;
  177. }
  178. // --------------------------------------------------------------------
  179. /**
  180. * CSRF Set Cookie
  181. *
  182. * @codeCoverageIgnore
  183. * @return CI_Security
  184. */
  185. public function csrf_set_cookie()
  186. {
  187. $expire = time() + $this->_csrf_expire;
  188. $secure_cookie = (bool) config_item('cookie_secure');
  189. if ($secure_cookie && ! is_https())
  190. {
  191. return FALSE;
  192. }
  193. setcookie(
  194. $this->_csrf_cookie_name,
  195. $this->_csrf_hash,
  196. $expire,
  197. config_item('cookie_path'),
  198. config_item('cookie_domain'),
  199. $secure_cookie,
  200. config_item('cookie_httponly')
  201. );
  202. log_message('debug', 'CRSF cookie Set');
  203. return $this;
  204. }
  205. // --------------------------------------------------------------------
  206. /**
  207. * Show CSRF Error
  208. *
  209. * @return void
  210. */
  211. public function csrf_show_error()
  212. {
  213. show_error('The action you have requested is not allowed.');
  214. }
  215. // --------------------------------------------------------------------
  216. /**
  217. * Get CSRF Hash
  218. *
  219. * @see CI_Security::$_csrf_hash
  220. * @return string CSRF hash
  221. */
  222. public function get_csrf_hash()
  223. {
  224. return $this->_csrf_hash;
  225. }
  226. // --------------------------------------------------------------------
  227. /**
  228. * Get CSRF Token Name
  229. *
  230. * @see CI_Security::$_csrf_token_name
  231. * @return string CSRF token name
  232. */
  233. public function get_csrf_token_name()
  234. {
  235. return $this->_csrf_token_name;
  236. }
  237. // --------------------------------------------------------------------
  238. /**
  239. * XSS Clean
  240. *
  241. * Sanitizes data so that Cross Site Scripting Hacks can be
  242. * prevented. This method does a fair amount of work but
  243. * it is extremely thorough, designed to prevent even the
  244. * most obscure XSS attempts. Nothing is ever 100% foolproof,
  245. * of course, but I haven't been able to get anything passed
  246. * the filter.
  247. *
  248. * Note: Should only be used to deal with data upon submission.
  249. * It's not something that should be used for general
  250. * runtime processing.
  251. *
  252. * @link http://channel.bitflux.ch/wiki/XSS_Prevention
  253. * Based in part on some code and ideas from Bitflux.
  254. *
  255. * @link http://ha.ckers.org/xss.html
  256. * To help develop this script I used this great list of
  257. * vulnerabilities along with a few other hacks I've
  258. * harvested from examining vulnerabilities in other programs.
  259. *
  260. * @param string|string[] $str Input data
  261. * @param bool $is_image Whether the input is an image
  262. * @return string
  263. */
  264. public function xss_clean($str, $is_image = FALSE)
  265. {
  266. // Is the string an array?
  267. if (is_array($str))
  268. {
  269. while (list($key) = each($str))
  270. {
  271. $str[$key] = $this->xss_clean($str[$key]);
  272. }
  273. return $str;
  274. }
  275. // Remove Invisible Characters and validate entities in URLs
  276. $str = $this->_validate_entities(remove_invisible_characters($str));
  277. /*
  278. * URL Decode
  279. *
  280. * Just in case stuff like this is submitted:
  281. *
  282. * <a href="http://%77%77%77%2E%67%6F%6F%67%6C%65%2E%63%6F%6D">Google</a>
  283. *
  284. * Note: Use rawurldecode() so it does not remove plus signs
  285. */
  286. $str = rawurldecode($str);
  287. /*
  288. * Convert character entities to ASCII
  289. *
  290. * This permits our tests below to work reliably.
  291. * We only convert entities that are within tags since
  292. * these are the ones that will pose security problems.
  293. */
  294. $str = preg_replace_callback("/[a-z]+=([\'\"]).*?\\1/si", array($this, '_convert_attribute'), $str);
  295. $str = preg_replace_callback('/<\w+.*/si', array($this, '_decode_entity'), $str);
  296. // Remove Invisible Characters Again!
  297. $str = remove_invisible_characters($str);
  298. /*
  299. * Convert all tabs to spaces
  300. *
  301. * This prevents strings like this: ja vascript
  302. * NOTE: we deal with spaces between characters later.
  303. * NOTE: preg_replace was found to be amazingly slow here on
  304. * large blocks of data, so we use str_replace.
  305. */
  306. $str = str_replace("\t", ' ', $str);
  307. // Capture converted string for later comparison
  308. $converted_string = $str;
  309. // Remove Strings that are never allowed
  310. $str = $this->_do_never_allowed($str);
  311. /*
  312. * Makes PHP tags safe
  313. *
  314. * Note: XML tags are inadvertently replaced too:
  315. *
  316. * <?xml
  317. *
  318. * But it doesn't seem to pose a problem.
  319. */
  320. if ($is_image === TRUE)
  321. {
  322. // Images have a tendency to have the PHP short opening and
  323. // closing tags every so often so we skip those and only
  324. // do the long opening tags.
  325. $str = preg_replace('/<\?(php)/i', '&lt;?\\1', $str);
  326. }
  327. else
  328. {
  329. $str = str_replace(array('<?', '?'.'>'), array('&lt;?', '?&gt;'), $str);
  330. }
  331. /*
  332. * Compact any exploded words
  333. *
  334. * This corrects words like: j a v a s c r i p t
  335. * These words are compacted back to their correct state.
  336. */
  337. $words = array(
  338. 'javascript', 'expression', 'vbscript', 'script', 'base64',
  339. 'applet', 'alert', 'document', 'write', 'cookie', 'window'
  340. );
  341. foreach ($words as $word)
  342. {
  343. $word = implode('\s*', str_split($word)).'\s*';
  344. // We only want to do this when it is followed by a non-word character
  345. // That way valid stuff like "dealer to" does not become "dealerto"
  346. $str = preg_replace_callback('#('.substr($word, 0, -3).')(\W)#is', array($this, '_compact_exploded_words'), $str);
  347. }
  348. /*
  349. * Remove disallowed Javascript in links or img tags
  350. * We used to do some version comparisons and use of stripos for PHP5,
  351. * but it is dog slow compared to these simplified non-capturing
  352. * preg_match(), especially if the pattern exists in the string
  353. */
  354. do
  355. {
  356. $original = $str;
  357. if (preg_match('/<a/i', $str))
  358. {
  359. $str = preg_replace_callback('#<a\s+([^>]*?)(?:>|$)#si', array($this, '_js_link_removal'), $str);
  360. }
  361. if (preg_match('/<img/i', $str))
  362. {
  363. $str = preg_replace_callback('#<img\s+([^>]*?)(?:\s?/?>|$)#si', array($this, '_js_img_removal'), $str);
  364. }
  365. if (preg_match('/script|xss/i', $str))
  366. {
  367. $str = preg_replace('#</*(?:script|xss).*?>#si', '[removed]', $str);
  368. }
  369. }
  370. while ($original !== $str);
  371. unset($original);
  372. // Remove evil attributes such as style, onclick and xmlns
  373. $str = $this->_remove_evil_attributes($str, $is_image);
  374. /*
  375. * Sanitize naughty HTML elements
  376. *
  377. * If a tag containing any of the words in the list
  378. * below is found, the tag gets converted to entities.
  379. *
  380. * So this: <blink>
  381. * Becomes: &lt;blink&gt;
  382. */
  383. $naughty = 'alert|applet|audio|basefont|base|behavior|bgsound|blink|body|embed|expression|form|frameset|frame|head|html|ilayer|iframe|input|isindex|layer|link|meta|object|plaintext|style|script|textarea|title|video|xml|xss';
  384. $str = preg_replace_callback('#<(/*\s*)('.$naughty.')([^><]*)([><]*)#is', array($this, '_sanitize_naughty_html'), $str);
  385. /*
  386. * Sanitize naughty scripting elements
  387. *
  388. * Similar to above, only instead of looking for
  389. * tags it looks for PHP and JavaScript commands
  390. * that are disallowed. Rather than removing the
  391. * code, it simply converts the parenthesis to entities
  392. * rendering the code un-executable.
  393. *
  394. * For example: eval('some code')
  395. * Becomes: eval&#40;'some code'&#41;
  396. */
  397. $str = preg_replace('#(alert|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)#si',
  398. '\\1\\2&#40;\\3&#41;',
  399. $str);
  400. // Final clean up
  401. // This adds a bit of extra precaution in case
  402. // something got through the above filters
  403. $str = $this->_do_never_allowed($str);
  404. /*
  405. * Images are Handled in a Special Way
  406. * - Essentially, we want to know that after all of the character
  407. * conversion is done whether any unwanted, likely XSS, code was found.
  408. * If not, we return TRUE, as the image is clean.
  409. * However, if the string post-conversion does not matched the
  410. * string post-removal of XSS, then it fails, as there was unwanted XSS
  411. * code found and removed/changed during processing.
  412. */
  413. if ($is_image === TRUE)
  414. {
  415. return ($str === $converted_string);
  416. }
  417. log_message('debug', 'XSS Filtering completed');
  418. return $str;
  419. }
  420. // --------------------------------------------------------------------
  421. /**
  422. * XSS Hash
  423. *
  424. * Generates the XSS hash if needed and returns it.
  425. *
  426. * @see CI_Security::$_xss_hash
  427. * @return string XSS hash
  428. */
  429. public function xss_hash()
  430. {
  431. if ($this->_xss_hash === '')
  432. {
  433. mt_srand();
  434. $this->_xss_hash = md5(time() + mt_rand(0, 1999999999));
  435. }
  436. return $this->_xss_hash;
  437. }
  438. // --------------------------------------------------------------------
  439. /**
  440. * HTML Entities Decode
  441. *
  442. * A replacement for html_entity_decode()
  443. *
  444. * The reason we are not using html_entity_decode() by itself is because
  445. * while it is not technically correct to leave out the semicolon
  446. * at the end of an entity most browsers will still interpret the entity
  447. * correctly. html_entity_decode() does not convert entities without
  448. * semicolons, so we are left with our own little solution here. Bummer.
  449. *
  450. * @link http://php.net/html-entity-decode
  451. *
  452. * @param string $str Input
  453. * @param string $charset Character set
  454. * @return string
  455. */
  456. public function entity_decode($str, $charset = NULL)
  457. {
  458. if (strpos($str, '&') === FALSE)
  459. {
  460. return $str;
  461. }
  462. if (empty($charset))
  463. {
  464. $charset = config_item('charset');
  465. }
  466. do
  467. {
  468. $matches = $matches1 = 0;
  469. $str = html_entity_decode($str, ENT_COMPAT, $charset);
  470. $str = preg_replace('~&#x(0*[0-9a-f]{2,5})~ei', 'chr(hexdec("\\1"))', $str, -1, $matches);
  471. $str = preg_replace('~&#([0-9]{2,4})~e', 'chr(\\1)', $str, -1, $matches1);
  472. }
  473. while ($matches OR $matches1);
  474. return $str;
  475. }
  476. // --------------------------------------------------------------------
  477. /**
  478. * Sanitize Filename
  479. *
  480. * @param string $str Input file name
  481. * @param bool $relative_path Whether to preserve paths
  482. * @return string
  483. */
  484. public function sanitize_filename($str, $relative_path = FALSE)
  485. {
  486. $bad = array(
  487. '../', '<!--', '-->', '<', '>',
  488. "'", '"', '&', '$', '#',
  489. '{', '}', '[', ']', '=',
  490. ';', '?', '%20', '%22',
  491. '%3c', // <
  492. '%253c', // <
  493. '%3e', // >
  494. '%0e', // >
  495. '%28', // (
  496. '%29', // )
  497. '%2528', // (
  498. '%26', // &
  499. '%24', // $
  500. '%3f', // ?
  501. '%3b', // ;
  502. '%3d' // =
  503. );
  504. if ( ! $relative_path)
  505. {
  506. $bad[] = './';
  507. $bad[] = '/';
  508. }
  509. $str = remove_invisible_characters($str, FALSE);
  510. return stripslashes(str_replace($bad, '', $str));
  511. }
  512. // ----------------------------------------------------------------
  513. /**
  514. * Strip Image Tags
  515. *
  516. * @param string $str
  517. * @return string
  518. */
  519. public function strip_image_tags($str)
  520. {
  521. return preg_replace(array('#<img\s+.*?src\s*=\s*["\'](.+?)["\'].*?\>#', '#<img\s+.*?src\s*=\s*(.+?).*?\>#'), '\\1', $str);
  522. }
  523. // ----------------------------------------------------------------
  524. /**
  525. * Compact Exploded Words
  526. *
  527. * Callback method for xss_clean() to remove whitespace from
  528. * things like 'j a v a s c r i p t'.
  529. *
  530. * @used-by CI_Security::xss_clean()
  531. * @param array $matches
  532. * @return string
  533. */
  534. protected function _compact_exploded_words($matches)
  535. {
  536. return preg_replace('/\s+/s', '', $matches[1]).$matches[2];
  537. }
  538. // --------------------------------------------------------------------
  539. /**
  540. * Remove Evil HTML Attributes (like event handlers and style)
  541. *
  542. * It removes the evil attribute and either:
  543. *
  544. * - Everything up until a space. For example, everything between the pipes:
  545. *
  546. * <code>
  547. * <a |style=document.write('hello');alert('world');| class=link>
  548. * </code>
  549. *
  550. * - Everything inside the quotes. For example, everything between the pipes:
  551. *
  552. * <code>
  553. * <a |style="document.write('hello'); alert('world');"| class="link">
  554. * </code>
  555. *
  556. * @param string $str The string to check
  557. * @param bool $is_image Whether the input is an image
  558. * @return string The string with the evil attributes removed
  559. */
  560. protected function _remove_evil_attributes($str, $is_image)
  561. {
  562. // All javascript event handlers (e.g. onload, onclick, onmouseover), style, and xmlns
  563. $evil_attributes = array('on\w*', 'style', 'xmlns', 'formaction');
  564. if ($is_image === TRUE)
  565. {
  566. /*
  567. * Adobe Photoshop puts XML metadata into JFIF images,
  568. * including namespacing, so we have to allow this for images.
  569. */
  570. unset($evil_attributes[array_search('xmlns', $evil_attributes)]);
  571. }
  572. do {
  573. $count = 0;
  574. $attribs = array();
  575. // find occurrences of illegal attribute strings with quotes (042 and 047 are octal quotes)
  576. preg_match_all('/('.implode('|', $evil_attributes).')\s*=\s*(\042|\047)([^\\2]*?)(\\2)/is', $str, $matches, PREG_SET_ORDER);
  577. foreach ($matches as $attr)
  578. {
  579. $attribs[] = preg_quote($attr[0], '/');
  580. }
  581. // find occurrences of illegal attribute strings without quotes
  582. preg_match_all('/('.implode('|', $evil_attributes).')\s*=\s*([^\s>]*)/is', $str, $matches, PREG_SET_ORDER);
  583. foreach ($matches as $attr)
  584. {
  585. $attribs[] = preg_quote($attr[0], '/');
  586. }
  587. // replace illegal attribute strings that are inside an html tag
  588. if (count($attribs) > 0)
  589. {
  590. $str = preg_replace('/(<?)(\/?[^><]+?)([^A-Za-z<>\-])(.*?)('.implode('|', $attribs).')(.*?)([\s><]?)([><]*)/i', '$1$2 $4$6$7$8', $str, -1, $count);
  591. }
  592. }
  593. while ($count);
  594. return $str;
  595. }
  596. // --------------------------------------------------------------------
  597. /**
  598. * Sanitize Naughty HTML
  599. *
  600. * Callback method for xss_clean() to remove naughty HTML elements.
  601. *
  602. * @used-by CI_Security::xss_clean()
  603. * @param array $matches
  604. * @return string
  605. */
  606. protected function _sanitize_naughty_html($matches)
  607. {
  608. return '&lt;'.$matches[1].$matches[2].$matches[3] // encode opening brace
  609. // encode captured opening or closing brace to prevent recursive vectors:
  610. .str_replace(array('>', '<'), array('&gt;', '&lt;'), $matches[4]);
  611. }
  612. // --------------------------------------------------------------------
  613. /**
  614. * JS Link Removal
  615. *
  616. * Callback method for xss_clean() to sanitize links.
  617. *
  618. * This limits the PCRE backtracks, making it more performance friendly
  619. * and prevents PREG_BACKTRACK_LIMIT_ERROR from being triggered in
  620. * PHP 5.2+ on link-heavy strings.
  621. *
  622. * @used-by CI_Security::xss_clean()
  623. * @param array $match
  624. * @return string
  625. */
  626. protected function _js_link_removal($match)
  627. {
  628. return str_replace($match[1],
  629. preg_replace('#href=.*?(?:alert\(|alert&\#40;|javascript:|livescript:|mocha:|charset=|window\.|document\.|\.cookie|<script|<xss|data\s*:)#si',
  630. '',
  631. $this->_filter_attributes(str_replace(array('<', '>'), '', $match[1]))
  632. ),
  633. $match[0]);
  634. }
  635. // --------------------------------------------------------------------
  636. /**
  637. * JS Image Removal
  638. *
  639. * Callback method for xss_clean() to sanitize image tags.
  640. *
  641. * This limits the PCRE backtracks, making it more performance friendly
  642. * and prevents PREG_BACKTRACK_LIMIT_ERROR from being triggered in
  643. * PHP 5.2+ on image tag heavy strings.
  644. *
  645. * @used-by CI_Security::xss_clean()
  646. * @param array $match
  647. * @return string
  648. */
  649. protected function _js_img_removal($match)
  650. {
  651. return str_replace($match[1],
  652. preg_replace('#src=.*?(?:alert\(|alert&\#40;|javascript:|livescript:|mocha:|charset=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)#si',
  653. '',
  654. $this->_filter_attributes(str_replace(array('<', '>'), '', $match[1]))
  655. ),
  656. $match[0]);
  657. }
  658. // --------------------------------------------------------------------
  659. /**
  660. * Attribute Conversion
  661. *
  662. * @used-by CI_Security::xss_clean()
  663. * @param array $match
  664. * @return string
  665. */
  666. protected function _convert_attribute($match)
  667. {
  668. return str_replace(array('>', '<', '\\'), array('&gt;', '&lt;', '\\\\'), $match[0]);
  669. }
  670. // --------------------------------------------------------------------
  671. /**
  672. * Filter Attributes
  673. *
  674. * Filters tag attributes for consistency and safety.
  675. *
  676. * @used-by CI_Security::_js_img_removal()
  677. * @used-by CI_Security::_js_link_removal()
  678. * @param string $str
  679. * @return string
  680. */
  681. protected function _filter_attributes($str)
  682. {
  683. $out = '';
  684. if (preg_match_all('#\s*[a-z\-]+\s*=\s*(\042|\047)([^\\1]*?)\\1#is', $str, $matches))
  685. {
  686. foreach ($matches[0] as $match)
  687. {
  688. $out .= preg_replace('#/\*.*?\*/#s', '', $match);
  689. }
  690. }
  691. return $out;
  692. }
  693. // --------------------------------------------------------------------
  694. /**
  695. * HTML Entity Decode Callback
  696. *
  697. * @used-by CI_Security::xss_clean()
  698. * @param array $match
  699. * @return string
  700. */
  701. protected function _decode_entity($match)
  702. {
  703. return $this->entity_decode($match[0], strtoupper(config_item('charset')));
  704. }
  705. // --------------------------------------------------------------------
  706. /**
  707. * Validate URL entities
  708. *
  709. * @used-by CI_Security::xss_clean()
  710. * @param string $str
  711. * @return string
  712. */
  713. protected function _validate_entities($str)
  714. {
  715. /*
  716. * Protect GET variables in URLs
  717. */
  718. // 901119URL5918AMP18930PROTECT8198
  719. $str = preg_replace('|\&([a-z\_0-9\-]+)\=([a-z\_0-9\-]+)|i', $this->xss_hash().'\\1=\\2', $str);
  720. /*
  721. * Validate standard character entities
  722. *
  723. * Add a semicolon if missing. We do this to enable
  724. * the conversion of entities to ASCII later.
  725. */
  726. $str = preg_replace('#(&\#?[0-9a-z]{2,})([\x00-\x20])*;?#i', '\\1;\\2', $str);
  727. /*
  728. * Validate UTF16 two byte encoding (x00)
  729. *
  730. * Just as above, adds a semicolon if missing.
  731. */
  732. $str = preg_replace('#(&\#x?)([0-9A-F]+);?#i', '\\1\\2;', $str);
  733. /*
  734. * Un-Protect GET variables in URLs
  735. */
  736. return str_replace($this->xss_hash(), '&', $str);
  737. }
  738. // ----------------------------------------------------------------------
  739. /**
  740. * Do Never Allowed
  741. *
  742. * @used-by CI_Security::xss_clean()
  743. * @param string
  744. * @return string
  745. */
  746. protected function _do_never_allowed($str)
  747. {
  748. $str = str_replace(array_keys($this->_never_allowed_str), $this->_never_allowed_str, $str);
  749. foreach ($this->_never_allowed_regex as $regex)
  750. {
  751. $str = preg_replace('#'.$regex.'#is', '[removed]', $str);
  752. }
  753. return $str;
  754. }
  755. // --------------------------------------------------------------------
  756. /**
  757. * Set CSRF Hash and Cookie
  758. *
  759. * @return string
  760. */
  761. protected function _csrf_set_hash()
  762. {
  763. if ($this->_csrf_hash === '')
  764. {
  765. // If the cookie exists we will use it's value.
  766. // We don't necessarily want to regenerate it with
  767. // each page load since a page could contain embedded
  768. // sub-pages causing this feature to fail
  769. if (isset($_COOKIE[$this->_csrf_cookie_name]) &&
  770. preg_match('#^[0-9a-f]{32}$#iS', $_COOKIE[$this->_csrf_cookie_name]) === 1)
  771. {
  772. return $this->_csrf_hash = $_COOKIE[$this->_csrf_cookie_name];
  773. }
  774. $this->_csrf_hash = md5(uniqid(rand(), TRUE));
  775. $this->csrf_set_cookie();
  776. }
  777. return $this->_csrf_hash;
  778. }
  779. }
  780. /* End of file Security.php */
  781. /* Location: ./system/core/Security.php */