PageRenderTime 46ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/system/core/Security.php

https://github.com/chriskelley/CodeIgniter
PHP | 900 lines | 469 code | 85 blank | 346 comment | 18 complexity | 827f6b042c4f5f5e07c398115cbc2756 MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  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. $this->_xss_hash = md5(uniqid(mt_rand()));
  434. }
  435. return $this->_xss_hash;
  436. }
  437. // --------------------------------------------------------------------
  438. /**
  439. * HTML Entities Decode
  440. *
  441. * A replacement for html_entity_decode()
  442. *
  443. * The reason we are not using html_entity_decode() by itself is because
  444. * while it is not technically correct to leave out the semicolon
  445. * at the end of an entity most browsers will still interpret the entity
  446. * correctly. html_entity_decode() does not convert entities without
  447. * semicolons, so we are left with our own little solution here. Bummer.
  448. *
  449. * @link http://php.net/html-entity-decode
  450. *
  451. * @param string $str Input
  452. * @param string $charset Character set
  453. * @return string
  454. */
  455. public function entity_decode($str, $charset = NULL)
  456. {
  457. if (strpos($str, '&') === FALSE)
  458. {
  459. return $str;
  460. }
  461. if (empty($charset))
  462. {
  463. $charset = config_item('charset');
  464. }
  465. do
  466. {
  467. $matches = $matches1 = 0;
  468. $str = html_entity_decode($str, ENT_COMPAT, $charset);
  469. $str = preg_replace('~&#x(0*[0-9a-f]{2,5})~ei', 'chr(hexdec("\\1"))', $str, -1, $matches);
  470. $str = preg_replace('~&#([0-9]{2,4})~e', 'chr(\\1)', $str, -1, $matches1);
  471. }
  472. while ($matches OR $matches1);
  473. return $str;
  474. }
  475. // --------------------------------------------------------------------
  476. /**
  477. * Sanitize Filename
  478. *
  479. * @param string $str Input file name
  480. * @param bool $relative_path Whether to preserve paths
  481. * @return string
  482. */
  483. public function sanitize_filename($str, $relative_path = FALSE)
  484. {
  485. $bad = array(
  486. '../', '<!--', '-->', '<', '>',
  487. "'", '"', '&', '$', '#',
  488. '{', '}', '[', ']', '=',
  489. ';', '?', '%20', '%22',
  490. '%3c', // <
  491. '%253c', // <
  492. '%3e', // >
  493. '%0e', // >
  494. '%28', // (
  495. '%29', // )
  496. '%2528', // (
  497. '%26', // &
  498. '%24', // $
  499. '%3f', // ?
  500. '%3b', // ;
  501. '%3d' // =
  502. );
  503. if ( ! $relative_path)
  504. {
  505. $bad[] = './';
  506. $bad[] = '/';
  507. }
  508. $str = remove_invisible_characters($str, FALSE);
  509. do
  510. {
  511. $old = $str;
  512. $str = str_replace($bad, '', $str);
  513. }
  514. while ($old !== $str);
  515. return stripslashes($str);
  516. }
  517. // ----------------------------------------------------------------
  518. /**
  519. * Strip Image Tags
  520. *
  521. * @param string $str
  522. * @return string
  523. */
  524. public function strip_image_tags($str)
  525. {
  526. return preg_replace(array('#<img\s+.*?src\s*=\s*["\'](.+?)["\'].*?\>#', '#<img\s+.*?src\s*=\s*(.+?).*?\>#'), '\\1', $str);
  527. }
  528. // ----------------------------------------------------------------
  529. /**
  530. * Compact Exploded Words
  531. *
  532. * Callback method for xss_clean() to remove whitespace from
  533. * things like 'j a v a s c r i p t'.
  534. *
  535. * @used-by CI_Security::xss_clean()
  536. * @param array $matches
  537. * @return string
  538. */
  539. protected function _compact_exploded_words($matches)
  540. {
  541. return preg_replace('/\s+/s', '', $matches[1]).$matches[2];
  542. }
  543. // --------------------------------------------------------------------
  544. /**
  545. * Remove Evil HTML Attributes (like event handlers and style)
  546. *
  547. * It removes the evil attribute and either:
  548. *
  549. * - Everything up until a space. For example, everything between the pipes:
  550. *
  551. * <code>
  552. * <a |style=document.write('hello');alert('world');| class=link>
  553. * </code>
  554. *
  555. * - Everything inside the quotes. For example, everything between the pipes:
  556. *
  557. * <code>
  558. * <a |style="document.write('hello'); alert('world');"| class="link">
  559. * </code>
  560. *
  561. * @param string $str The string to check
  562. * @param bool $is_image Whether the input is an image
  563. * @return string The string with the evil attributes removed
  564. */
  565. protected function _remove_evil_attributes($str, $is_image)
  566. {
  567. // All javascript event handlers (e.g. onload, onclick, onmouseover), style, and xmlns
  568. $evil_attributes = array('on\w*', 'style', 'xmlns', 'formaction');
  569. if ($is_image === TRUE)
  570. {
  571. /*
  572. * Adobe Photoshop puts XML metadata into JFIF images,
  573. * including namespacing, so we have to allow this for images.
  574. */
  575. unset($evil_attributes[array_search('xmlns', $evil_attributes)]);
  576. }
  577. do {
  578. $count = 0;
  579. $attribs = array();
  580. // find occurrences of illegal attribute strings with quotes (042 and 047 are octal quotes)
  581. preg_match_all('/('.implode('|', $evil_attributes).')\s*=\s*(\042|\047)([^\\2]*?)(\\2)/is', $str, $matches, PREG_SET_ORDER);
  582. foreach ($matches as $attr)
  583. {
  584. $attribs[] = preg_quote($attr[0], '/');
  585. }
  586. // find occurrences of illegal attribute strings without quotes
  587. preg_match_all('/('.implode('|', $evil_attributes).')\s*=\s*([^\s>]*)/is', $str, $matches, PREG_SET_ORDER);
  588. foreach ($matches as $attr)
  589. {
  590. $attribs[] = preg_quote($attr[0], '/');
  591. }
  592. // replace illegal attribute strings that are inside an html tag
  593. if (count($attribs) > 0)
  594. {
  595. $str = preg_replace('/(<?)(\/?[^><]+?)([^A-Za-z<>\-])(.*?)('.implode('|', $attribs).')(.*?)([\s><]?)([><]*)/i', '$1$2 $4$6$7$8', $str, -1, $count);
  596. }
  597. }
  598. while ($count);
  599. return $str;
  600. }
  601. // --------------------------------------------------------------------
  602. /**
  603. * Sanitize Naughty HTML
  604. *
  605. * Callback method for xss_clean() to remove naughty HTML elements.
  606. *
  607. * @used-by CI_Security::xss_clean()
  608. * @param array $matches
  609. * @return string
  610. */
  611. protected function _sanitize_naughty_html($matches)
  612. {
  613. return '&lt;'.$matches[1].$matches[2].$matches[3] // encode opening brace
  614. // encode captured opening or closing brace to prevent recursive vectors:
  615. .str_replace(array('>', '<'), array('&gt;', '&lt;'), $matches[4]);
  616. }
  617. // --------------------------------------------------------------------
  618. /**
  619. * JS Link Removal
  620. *
  621. * Callback method for xss_clean() to sanitize links.
  622. *
  623. * This limits the PCRE backtracks, making it more performance friendly
  624. * and prevents PREG_BACKTRACK_LIMIT_ERROR from being triggered in
  625. * PHP 5.2+ on link-heavy strings.
  626. *
  627. * @used-by CI_Security::xss_clean()
  628. * @param array $match
  629. * @return string
  630. */
  631. protected function _js_link_removal($match)
  632. {
  633. return str_replace($match[1],
  634. preg_replace('#href=.*?(?:alert\(|alert&\#40;|javascript:|livescript:|mocha:|charset=|window\.|document\.|\.cookie|<script|<xss|data\s*:)#si',
  635. '',
  636. $this->_filter_attributes(str_replace(array('<', '>'), '', $match[1]))
  637. ),
  638. $match[0]);
  639. }
  640. // --------------------------------------------------------------------
  641. /**
  642. * JS Image Removal
  643. *
  644. * Callback method for xss_clean() to sanitize image tags.
  645. *
  646. * This limits the PCRE backtracks, making it more performance friendly
  647. * and prevents PREG_BACKTRACK_LIMIT_ERROR from being triggered in
  648. * PHP 5.2+ on image tag heavy strings.
  649. *
  650. * @used-by CI_Security::xss_clean()
  651. * @param array $match
  652. * @return string
  653. */
  654. protected function _js_img_removal($match)
  655. {
  656. return str_replace($match[1],
  657. preg_replace('#src=.*?(?:alert\(|alert&\#40;|javascript:|livescript:|mocha:|charset=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)#si',
  658. '',
  659. $this->_filter_attributes(str_replace(array('<', '>'), '', $match[1]))
  660. ),
  661. $match[0]);
  662. }
  663. // --------------------------------------------------------------------
  664. /**
  665. * Attribute Conversion
  666. *
  667. * @used-by CI_Security::xss_clean()
  668. * @param array $match
  669. * @return string
  670. */
  671. protected function _convert_attribute($match)
  672. {
  673. return str_replace(array('>', '<', '\\'), array('&gt;', '&lt;', '\\\\'), $match[0]);
  674. }
  675. // --------------------------------------------------------------------
  676. /**
  677. * Filter Attributes
  678. *
  679. * Filters tag attributes for consistency and safety.
  680. *
  681. * @used-by CI_Security::_js_img_removal()
  682. * @used-by CI_Security::_js_link_removal()
  683. * @param string $str
  684. * @return string
  685. */
  686. protected function _filter_attributes($str)
  687. {
  688. $out = '';
  689. if (preg_match_all('#\s*[a-z\-]+\s*=\s*(\042|\047)([^\\1]*?)\\1#is', $str, $matches))
  690. {
  691. foreach ($matches[0] as $match)
  692. {
  693. $out .= preg_replace('#/\*.*?\*/#s', '', $match);
  694. }
  695. }
  696. return $out;
  697. }
  698. // --------------------------------------------------------------------
  699. /**
  700. * HTML Entity Decode Callback
  701. *
  702. * @used-by CI_Security::xss_clean()
  703. * @param array $match
  704. * @return string
  705. */
  706. protected function _decode_entity($match)
  707. {
  708. return $this->entity_decode($match[0], strtoupper(config_item('charset')));
  709. }
  710. // --------------------------------------------------------------------
  711. /**
  712. * Validate URL entities
  713. *
  714. * @used-by CI_Security::xss_clean()
  715. * @param string $str
  716. * @return string
  717. */
  718. protected function _validate_entities($str)
  719. {
  720. /*
  721. * Protect GET variables in URLs
  722. */
  723. // 901119URL5918AMP18930PROTECT8198
  724. $str = preg_replace('|\&([a-z\_0-9\-]+)\=([a-z\_0-9\-]+)|i', $this->xss_hash().'\\1=\\2', $str);
  725. /*
  726. * Validate standard character entities
  727. *
  728. * Add a semicolon if missing. We do this to enable
  729. * the conversion of entities to ASCII later.
  730. */
  731. $str = preg_replace('#(&\#?[0-9a-z]{2,})([\x00-\x20])*;?#i', '\\1;\\2', $str);
  732. /*
  733. * Validate UTF16 two byte encoding (x00)
  734. *
  735. * Just as above, adds a semicolon if missing.
  736. */
  737. $str = preg_replace('#(&\#x?)([0-9A-F]+);?#i', '\\1\\2;', $str);
  738. /*
  739. * Un-Protect GET variables in URLs
  740. */
  741. return str_replace($this->xss_hash(), '&', $str);
  742. }
  743. // ----------------------------------------------------------------------
  744. /**
  745. * Do Never Allowed
  746. *
  747. * @used-by CI_Security::xss_clean()
  748. * @param string
  749. * @return string
  750. */
  751. protected function _do_never_allowed($str)
  752. {
  753. $str = str_replace(array_keys($this->_never_allowed_str), $this->_never_allowed_str, $str);
  754. foreach ($this->_never_allowed_regex as $regex)
  755. {
  756. $str = preg_replace('#'.$regex.'#is', '[removed]', $str);
  757. }
  758. return $str;
  759. }
  760. // --------------------------------------------------------------------
  761. /**
  762. * Set CSRF Hash and Cookie
  763. *
  764. * @return string
  765. */
  766. protected function _csrf_set_hash()
  767. {
  768. if ($this->_csrf_hash === '')
  769. {
  770. // If the cookie exists we will use it's value.
  771. // We don't necessarily want to regenerate it with
  772. // each page load since a page could contain embedded
  773. // sub-pages causing this feature to fail
  774. if (isset($_COOKIE[$this->_csrf_cookie_name]) &&
  775. preg_match('#^[0-9a-f]{32}$#iS', $_COOKIE[$this->_csrf_cookie_name]) === 1)
  776. {
  777. return $this->_csrf_hash = $_COOKIE[$this->_csrf_cookie_name];
  778. }
  779. $this->_csrf_hash = md5(uniqid(rand(), TRUE));
  780. $this->csrf_set_cookie();
  781. }
  782. return $this->_csrf_hash;
  783. }
  784. }
  785. /* End of file Security.php */
  786. /* Location: ./system/core/Security.php */