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

/system/core/Security.php

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