之前在很多的网站都看到了360webscan的攻击拦截脚本,正好分析并学习一下。
下载地址:http://webscan.360.cn/protect/down?domain=blog.dyboy.cn
最后一个 domain
参数改为自己的线上网站域名
为了本地测试:我下载http://webscan.360.cn/protect/down?domain=www.test.com
0x01 安装
将下载的 360webscan.zip
解压后,得到360safe
文件夹,并上传至网站根目录
在全局加载的文件中(示例网站根目录下:index.php
),加入如下代码:
if(is_file($_SERVER['DOCUMENT_ROOT'].'/360safe/360webscan.php')){
require_once($_SERVER['DOCUMENT_ROOT'].'/360safe/360webscan.php');
} //注意文件路径
访问:http://www.test.com/360safe/360webscan.php post-data: webscan_act=ckinstall
但是并没有出现安装信息,原因是:http://safe.webscan.360.cn
该域名已经无法访问(后面涉及到这个网址的函数都不无法正常执行),因此着重分析拦截过滤的一个过程。
看到这个脚本文件的最后编辑时间为2014年…
0x02 结构分析
在 webscan_cache.php
中
默认拦截,POST/GET/COOKIE/REFERER
这四个参数
同时还有白名单功能
//url白名单,可以自定义添加url白名单,默认是对phpcms的后台url放行 //写法:比如phpcms 后台操作url index.php?m=admin php168的文章提交链接post.php?job=postnew&step=post ,dedecms 空间设置edit_space_info.php $webscan_white_url = array('index.php' => 'm=admin','post.php' => 'job=postnew&step=post','edit_space_info.php'=>'');
很清晰的解释了
再看 360webscan.php
所有的过滤规则以及函数实现都在此文件
0x03 功能测试
在按照上述安装方法安装后,测试访问:http://www.test.com/index.php?test=<script>alert(1)</script>
XSS拦截显示:
比如注入等都会被拦截
0x04 拦截规则
//get拦截规则 $getfilter = "\\<.+javascript:window\\[.{1}\\\\x|<.*=(&#\\d+?;?)+?>|<.*(data|src)=data:text\\/html.*>|\\b(alert\\(|confirm\\(|expression\\(|prompt\\(|benchmark\s*?\(.*\)|sleep\s*?\(.*\)|load_file\s*?\\()|<[a-z]+?\\b[^>]*?\\bon([a-z]{4,})\s*?=|^\\+\\/v(8|9)|\\b(and|or)\\b\\s*?([\\(\\)'\"\\d]+?=[\\(\\)'\"\\d]+?|[\\(\\)'\"a-zA-Z]+?=[\\(\\)'\"a-zA-Z]+?|>|<|\s+?[\\w]+?\\s+?\\bin\\b\\s*?\(|\\blike\\b\\s+?[\"'])|\\/\\*.*\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?(`|'|\")\s*)|UPDATE\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?(`|'|\")\s*)SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE)@{0,2}(\\(.+\\)|\\s+?.+?\\s+?|(`|'|\").*?(`|'|\"))FROM(\\(.+\\)|\\s+?.+?|(`|'|\").*?(`|'|\"))|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)"; //post拦截规则 $postfilter = "<.*=(&#\\d+?;?)+?>|<.*data=data:text\\/html.*>|\\b(alert\\(|confirm\\(|expression\\(|prompt\\(|benchmark\s*?\(.*\)|sleep\s*?\(.*\)|load_file\s*?\\()|<[^>]*?\\b(onerror|onmousemove|onload|onclick|onmouseover)\\b|\\b(and|or)\\b\\s*?([\\(\\)'\"\\d]+?=[\\(\\)'\"\\d]+?|[\\(\\)'\"a-zA-Z]+?=[\\(\\)'\"a-zA-Z]+?|>|<|\s+?[\\w]+?\\s+?\\bin\\b\\s*?\(|\\blike\\b\\s+?[\"'])|\\/\\*.*\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?(`|'|\")\s*)|UPDATE\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?(`|'|\")\s*)SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE)(\\(.+\\)|\\s+?.+?\\s+?|(`|'|\").*?(`|'|\"))FROM(\\(.+\\)|\\s+?.+?|(`|'|\").*?(`|'|\"))|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)"; //cookie拦截规则 $cookiefilter = "benchmark\s*?\(.*\)|sleep\s*?\(.*\)|load_file\s*?\\(|\\b(and|or)\\b\\s*?([\\(\\)'\"\\d]+?=[\\(\\)'\"\\d]+?|[\\(\\)'\"a-zA-Z]+?=[\\(\\)'\"a-zA-Z]+?|>|<|\s+?[\\w]+?\\s+?\\bin\\b\\s*?\(|\\blike\\b\\s+?[\"'])|\\/\\*.*\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?(`|'|\")\s*)|UPDATE\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?(`|'|\")\s*)SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE)@{0,2}(\\(.+\\)|\\s+?.+?\\s+?|(`|'|\").*?(`|'|\"))FROM(\\(.+\\)|\\s+?.+?|(`|'|\").*?(`|'|\"))|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)"; //获取指令 $webscan_action = isset($_POST['webscan_act'])&&webscan_cheack() ? trim($_POST['webscan_act']) : ''; //referer获取 $webscan_referer = empty($_SERVER['HTTP_REFERER']) ? array() : array('HTTP_REFERER'=>$_SERVER['HTTP_REFERER']);
0x05 运行分析
在程序的底部调用函数,过滤判断四种参数是否存在非法攻击字符串,如果是在白名单目录下(webscan_white()
函数 ),就不会调用第二层的判断(四种拦截方式)
继续跟进:webscan_white()
/** * 拦截目录白名单 */ function webscan_white($webscan_white_name,$webscan_white_url=array()) { $url_path=$_SERVER['SCRIPT_NAME']; //修复之前是PHP_SELF $url_var=$_SERVER['QUERY_STRING']; if (preg_match("/".$webscan_white_name."/is",$url_path)==1&&!empty($webscan_white_name)) { return false; } foreach ($webscan_white_url as $key => $value) { if(!empty($url_var)&&!empty($value)){ if (stristr($url_path,$key)&&stristr($url_var,$value)) { return false; } } elseif (empty($url_var)&&empty($value)) { if (stristr($url_path,$key)) { return false; } } } return true; }
1.如果你输入 /test.php/123456
的话 $_SERVER['SCRIPT_NAME']
结果是/test.php
。所以为了安全起见,为了指向自身,应该用$_SERVER['SCRIPT_NAME']
2.$_SERVER['QUERY_STRING']
获取 ?
后面的字符串,例如:index.php?action=login&username=123&pass=123
,那么获取的结果就是:action=login&username=123&pass=123
3.preg_mactch
函数: 搜索subject
与pattern
给定的正则表达式的一个匹配.
reference: http://php.net/manual/zh/function.preg-match.php int preg_match ( string $pattern , string $subject [, array &$matches [, int $flags = 0 [, int $offset = 0 ]]] )
正则语法:http://php.net/manual/zh/reference.pcre.pattern.syntax.php
翻译了一下
Regex quick reference [abc] A single character: a, b or c 单独的字符 [^abc] Any single character but a, b, or c 匹配字符除了abc [a-z] Any single character in the range a-z 匹配a到z的字符 [a-zA-Z] Any single character in the range a-z or A-Z 匹配a到z或A到Z的字符 ^ Start of line 一行的开始 $ End of line 一行的结束 \A Start of string 字符串开头 \z End of string 字符串结尾 . Any single character 任何字符 \s Any whitespace character 任何空白字符 \S Any non-whitespace character 任何非空白字符 \d Any digit 任何数字 \D Any non-digit 任何非数字 \w Any word character (letter, number, underscore) 任何的单词字符(字母,数字,下划线) \W Any non-word character 任何非单词字符 \b Any word boundary character 任何单词边界字符 (...) Capture everything enclosed 捕获所未包裹有内容 (a|b) a or b a或b a? Zero or one of a 有0个或1个字符a a* Zero or more of a 有0个或多个字符a a+ One or more of a 有1个或多个字符a a{3} Exactly 3 of a 有3个字符a a{3,} 3 or more of a 有3个或多个字符a a{3,6} Between 3 and 6 of a 有3到6个字符a options: i case insensitive m make dot match newlines x ignore whitespace in regex o perform #{...} substitutions only once 可选设置:i不区分大小写,m使得.(点符号)匹配换行符,x忽略正则表达式中的空格,o只执行一次#{...}中内容替换
其中的\\
等价于\
\\\\
等价于\\
等价于/
4.strsti()
函数:返回 haystack
字符串从 needle
第一次出现的位置开始到结尾的字符串。
reference: http://php.net/manual/zh/function.stristr.php string stristr ( string $haystack , mixed $needle [, bool $before_needle = FALSE ] )
在整个白名单判断函数中,如果匹配上了,那么就返回false
,就不做拦截检测,针对白名单这一点其实是有漏洞可绕过的,传递的第一个参数$webscan_white_name
是一个全局参数在webscan_cache.php
文件中
//后台白名单,后台操作将不会拦截,添加"|"隔开白名单目录下面默认是网址带 `admin` `/dede/` 放行 `$webscan_white_directory='admin|\/dede\/'`;
这样的话,那么我们只要在admin
或者 dede
目录下的任何操作都不会被拦截。如果存在后台注入的话,同时在后台添加了白名单,那么拦截就不再有效果了。
同时提一点:如上代码,注释了一下 $url_path=$_SERVER['SCRIPT_NAME']; //修复之前是PHP_SELF
,这里存在一个安全问题,直接引用一下离别歌大佬的博文:
然后再给大家说明一下$_SERVER['PHP_SELF']是什么: PHP_SELF指当前的页面绝对地址,比如我们的网站: https://www.leavesongs.com/hehe/index.php 那么PHP_SELF就是/hehe/index.php。 但有个小问题很多人没有注意到,当url是PATH_INFO的时候,比如 https://www.leavesongs.com/hehe/index.php/phithon 那么PHP_SELF就是/hehe/index.php/phithon 也就是说,其实PHP_SELF有一部分是我们可以控制的。
ok,那么如果目录不在白名单中,那么就会下一步匹配参数是否在白名单中,如果能够匹配上也返回false
进入过滤检测手中,比如xss过滤:
这样的:http://www.test.com/index.php?id=123%3Ciframe%20src=http://www.xxx.com/1.js%3E 是不会被过滤的
然后调用webscan_StopAttack()
函数将拦截规则与当前的GET/POST/COOKIE/REFERER
参数匹配!
那么直接看GET
请求中的过滤规则吧!
//get拦截规则 $getfilter = "\\<.+javascript:window\\[.{1}\\\\x|<.*=(&#\\d+?;?)+?>|<.*(data|src)=data:text\\/html.*>|\\b(alert\\(|confirm\\(|expression\\(|prompt\\(|benchmark\s*?\(.*\)|sleep\s*?\(.*\)|load_file\s*?\\()|<[a-z]+?\\b[^>]*?\\bon([a-z]{4,})\s*?=|^\\+\\/v(8|9)|\\b(and|or)\\b\\s*?([\\(\\)'\"\\d]+?=[\\(\\)'\"\\d]+?|[\\(\\)'\"a-zA-Z]+?=[\\(\\)'\"a-zA-Z]+?|>|<|\s+?[\\w]+?\\s+?\\bin\\b\\s*?\(|\\blike\\b\\s+?[\"'])|\\/\\*.*\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?(`|'|\")\s*)|UPDATE\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?(`|'|\")\s*)SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE)@{0,2}(\\(.+\\)|\\s+?.+?\\s+?|(`|'|\").*?(`|'|\"))FROM(\\(.+\\)|\\s+?.+?|(`|'|\").*?(`|'|\"))|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
简单解读,只要规则中出现的单词或连续字符,那么在访问链接URL中就不能存在这些关键词,否则就会被拦截。
为什么要简单解读呐?因为这TM的规则太复杂了…
可以把 |
分割开的看成一个小规则,这样子来分别分析
在上面我们看到iframe
关键词没被过滤,那么改为如下的:
//添加一个iframe关键词 iframe| $getfilter = "iframe|\\<.+javascript:window\\[.{1}\\\\x|<.*=(&#\\d+?;?)+?>|<.*(data|src)=data:text\\/html.*>|\\b(alert\\(|confirm\\(|expression\\(|prompt\\(|benchmark\s*?\(.*\)|sleep\s*?\(.*\)|load_file\s*?\\()|<[a-z]+?\\b[^>]*?\\bon([a-z]{4,})\s*?=|^\\+\\/v(8|9)|\\b(and|or)\\b\\s*?([\\(\\)'\"\\d]+?=[\\(\\)'\"\\d]+?|[\\(\\)'\"a-zA-Z]+?=[\\(\\)'\"a-zA-Z]+?|>|<|\s+?[\\w]+?\\s+?\\bin\\b\\s*?\(|\\blike\\b\\s+?[\"'])|\\/\\*.*\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?(`|'|\")\s*)|UPDATE\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?(`|'|\")\s*)SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE)@{0,2}(\\(.+\\)|\\s+?.+?\\s+?|(`|'|\").*?(`|'|\"))FROM(\\(.+\\)|\\s+?.+?|(`|'|\").*?(`|'|\"))|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
这样就起到了拦截效果
其他的请求都是类似的,正则语法真难!真香!
如果匹配到了需要拦截过滤的关键词,就会调用webscan_pape()
函数,及调用拦截结果显示页面,如上图所示。
0x06 总结
正则语法看得心力憔悴,更多的匹配规则得自己下来写一写,然后在本地环境输出查看!
脚本防火墙真方便!正则匹配就好了,在这个360webscan
的过滤插件中,还是看到了函数封装的美感,Do you like these?
版权声明:《 [PHP防火墙]输入内容存在危险字符,安全起见,已被本站拦截 》为DYBOY原创文章,转载请注明出处!
最后编辑:2018-8-19 17:08:16
2019-04-30 23:51
2018-09-29 22:35
你看看新版的:https://pastebin.com/g9TkMxbc
这个正则之全面。。。看吐了都,据我的水平,目前这个正则几乎绕不过。。。
2018-10-09 12:10