PageRenderTime 51ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/system/codeigniter/core/Security.php

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