/lenasys/trunk

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/lenasys/trunk

« back to all changes in this revision

Viewing changes to codeigniter/system/core/Security.php

  • Committer: galaxyAbstractor
  • Date: 2013-04-10 15:58:59 UTC
  • mfrom: (20.1.1 lenasys)
  • mto: This revision was merged to the branch mainline in revision 23.
  • Revision ID: galaxyabstractor@gmail.com-20130410155859-cih60kaz5es8savt
CodeIgniter implementation of basic CMS system

Show diffs side-by-side

added added

removed removed

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