免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 5046 | 回复: 13

超强过滤,彻底对SQL注入说拜拜! [复制链接]

论坛徽章:
0
发表于 2009-06-27 13:04 |显示全部楼层
再严密的PHPer也有疏忽的时候,比如输入过滤就是。我一直在想是否可以一劳永逸的解决这个问题,起码可以解决所有SQL注入的问题!最好的办法就是自动对客户端提交的数据进行过滤,然后在程序中使用 $_POST $_GET $_FILES $_COOKIE 之类的变量时,不必考虑过滤,直接就当做安全的值来用该多爽!
于是就有了下面这个东西:

[[i] 本帖最后由 csfrank 于 2009-6-27 13:11 编辑 [/i]]

评分

参与人数 1可用积分 +3 收起 理由
bs + 3 好学的人才

查看全部评分

论坛徽章:
0
发表于 2009-06-27 13:09 |显示全部楼层
class Cleaner
{
    //[注意]①先替换双空格再替换单空格既可以保证显示连续空格,又不会出现超长行不能换行的问题;② \r\n 一定要在 \r 和 \n 之前优先被替换
&nbsp;&nbsp;&nbsp;&nbsp;private static $replace=array("\t"=>'&#9;', "\r\n"=>'<br />', "\n"=>'<br />', "\r"=>'<br />', '  '=>'&#32;&nbsp;', ' '=>'&#32;', '"'=>'&quot;', '$'=>'&#36;', '&'=>'&amp;', "'"=>'&#39;', '<'=>'&lt;', '>'=>'&gt;', '\\'=>'&#92;', '`'=>'&#96;');
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;private static function clean_str(&$str)
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(preg_match('/[\\x00-\\x08\\x0B-\\x0C\\x0E-\\x1F\\x7F]/S',$str)) $str=''; //这些非打印字符不应该出现!视为攻击,直接清空!
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else $str=strtr(trim($str),self::$replace); //返回值一定是字符串,至多是 ''
&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;private static function clean_key(&$arr)
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$keys=array_keys($arr);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;foreach($keys as $k) if(is_string($k))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{//由于仅处理字符串键,所以键顺序会被打乱[字符串键全跑到整数键后面去了]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$tmp=$arr[$k];
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unset($arr[$k]);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self::clean_str($k);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(''!==$k) $arr[$k]=$tmp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;private static function clean_REQUEST(&$arr)
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(is_array($arr))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self::clean_key($arr);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$keys=array_keys($arr);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;foreach($keys as $k) self::clean_REQUEST($arr[$k]);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;elseif(is_string($arr)) self::clean_str($arr);
&nbsp;&nbsp;&nbsp;&nbsp;}

论坛徽章:
0
发表于 2009-06-27 13:10 |显示全部楼层
接上贴

&nbsp;&nbsp;&nbsp;&nbsp;private static function clean_ONE_FILE($finfo, &$name, &$type, &$tmp_name, &$error, &$size)
&nbsp;&nbsp;&nbsp;&nbsp;{//所有传入的引用变量的嵌套结构都是相同的
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(is_array($name))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{//虽然键顺序被打乱,但是对应关系依然正确
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self::clean_key($name);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self::clean_key($type);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self::clean_key($tmp_name);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self::clean_key($error);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self::clean_key($size);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$keys=array_keys($name);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;foreach($keys as $k) self::clean_ONE_FILE($finfo, $name[$k], $type[$k], $tmp_name[$k], $error[$k], $size[$k]);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$name=basename($name);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self::clean_str($name);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self::clean_str($type);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(UPLOAD_ERR_OK==$error)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{//仅纠正上传成功的文件TYPE
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if($mime=finfo_file($finfo, $tmp_name))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{//成功获取正确的MIME类型,纠正客户端输入
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$pos=strpos($mime,';');
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$type=$pos?substr($mime,0,$pos):$mime;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{//不能识别MIME,客户端在欺骗
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$type='MIME_CHEAT';
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$error=UPLOAD_ERR_PARTIAL;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;private static function clean_FILES()
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self::clean_key($_FILES);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$keys=array_keys($_FILES);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$finfo=finfo_open(FILEINFO_MIME);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;foreach($keys as $k) self::clean_ONE_FILE($finfo, $_FILES[$k]['name'], $_FILES[$k]['type'], $_FILES[$k]['tmp_name'], $_FILES[$k]['error'], $_FILES[$k]['size']);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;finfo_close($finfo);
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;private static $uncleaned=TRUE;
&nbsp;&nbsp;&nbsp;&nbsp;public static function clean_input()
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(self::$uncleaned)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(!empty($_GET))     self::clean_REQUEST($_GET);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(!empty($_POST))    self::clean_REQUEST($_POST);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(!empty($_COOKIE))  self::clean_REQUEST($_COOKIE);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(!empty($_REQUEST)) self::clean_REQUEST($_REQUEST);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(!empty($_SERVER))  self::clean_REQUEST($_SERVER);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(!empty($_FILES))   self::clean_FILES();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self::$uncleaned=FALSE;
&nbsp;&nbsp;&nbsp;&nbsp;}

}

论坛徽章:
0
发表于 2009-06-27 13:11 |显示全部楼层
使用方法很简单:在每个PHP页面的开头处,调用一下
Cleaner::clean_input();

即可。
当然这种过滤实质是一种修改用户数据的做法,缺点也是有,但是在web页面上使用,应该没什么大问题。
此种做法,毛病多多,欢迎拍砖!
更欢迎指出代码的不足之处!

论坛徽章:
0
发表于 2009-06-27 14:16 |显示全部楼层
很多框架就是这样做的,例如 CodeIgniter。
但是一般采取 lazy action 的做法,需要的时候才过滤。类似于 get_input($k),这样:

一可以保证不处理不需要的东西;
二可以保证如果万一需要原始数据的话可以取得到;
三是除了过滤之外,其实还有验证数据有效性的问题,也可以一起做;
四是可以对不同的过滤/验证,使用不同的方法(例如每种数据库的 escape 是不尽相同的)。

评分

参与人数 1可用积分 +5 收起 理由
bs + 5 精采

查看全部评分

论坛徽章:
0
发表于 2009-06-27 14:36 |显示全部楼层

回复 #5 dz902 的帖子

get_input($k) 这种还是可能会忘记,这样做的目的就是不要在使用数据的时候想着过滤。
要原始数据也很简单:html_entity_decode 一下就好了
验证数据有效性倒是必须在使用的时候做,这个没办法,毕竟有效规则事先不能知道。
这种过滤对所有的数据库都有效。

论坛徽章:
0
发表于 2009-06-27 15:11 |显示全部楼层
原帖由 csfrank 于 2009-6-27 14:36 发表
get_input($k) 这种还是可能会忘记,这样做的目的就是不要在使用数据的时候想着过滤。
要原始数据也很简单:html_entity_decode 一下就好了
验证数据有效性倒是必须在使用的时候做,这个没办法,毕竟有效规则事先不能知道。
这种过滤对所有的数据库都有效。


get_input($k) 只是原理而已,框架大都不直接 get_input($k) 的办法,但是都是类似原理。我说 get_input($k) 而不是 get_input() 的意思,是说,在用到该变量的时候再进行处理,或者说,只处理自己会用到的变量,是更加高效的办法。另外,「可能会忘记」是不能成为理由的,不应该依靠程序来解决人的思维问题。

另外,如果只是用 html_entity_encode 来做过滤的话,就必须保证处理的数据的格式一定是 HTML,但是现实中可能并不一定,例如:在后台编辑网页模板的时候,难道每次都用 html_entity_decode 过一次吗?难道这个就不会被忘记么?

验证数据的有效性的规则当然是事先知道的,假设 discuz 的论坛,topic.php 有收到两个参数,一个 forum_id 一个 topic_id,这两个参数的规则自然会是:非零整数;又或者,register.php 收到 username,password,confirm_password,email 的数据,这几个参数的规则自然是:username 的长度必须为 3-12 且不能为空,password 和 confirm_password 必须一致,email 必须符合 email 的格式等等。这些都是可以事先知道的。

另外,「对于所有的数据库都有效」是建立在一个基础上的,那就是,所有有关的程序必须的输入性数据必须使用 html_entity_encode 过一次,并且所有的用户提交数据均以 HTML 的方式来储存(如果再用 decode 的话,就失去这个 Cleaner 的意义了)。

关于验证,CodeIgniter 使用这个方法做:


  1. $config = array(
  2.                array(
  3.                      'field'   => 'username',
  4.                      'label'   => 'Username',
  5.                      'rules'   => 'required|min_length[5]|max_length[12]'
  6.                   ),
  7.                array(
  8.                      'field'   => 'password',
  9.                      'label'   => 'Password',
  10.                      'rules'   => 'required|min_length[5]'
  11.                   ),
  12.                array(
  13.                      'field'   => 'passconf',
  14.                      'label'   => 'Password Confirmation',
  15.                      'rules'   => 'required'
  16.                   ),   
  17.                array(
  18.                      'field'   => 'email',
  19.                      'label'   => 'Email',
  20.                      'rules'   => 'required'
  21.                   )
  22.             );
复制代码


在另外一个框架中,过滤和验证用:


  1. try {
  2.     $data = $request->ask_for(array('id'              => 0,
  3.                                     'title'           => '',
  4.                                     'content'         => '',
  5.                                     'category_id'     => 0,
  6.                                     'post_date'       => -1,
  7.                                     'is_draft'        => 'n',
  8.                                     'allow_comments'  => 'n',
  9.                                     'allow_trackback' => 'n',
  10.                                     'excerpt'         => '',
  11.                                     'slug'            => '',
  12.                                     'tags'            => ''),
  13.                                array('id' => 'zero positive integer',
  14.                                      'title' => 'required stop',
  15.                                      'category_id' => 'positive integer',
  16.                                      'is_draft' => array('y', 'n'),
  17.                                      'allow_comments' => array('y'),
  18.                                      'allow_trackback' => array('y')),
  19.                                'post');
  20. } catch (Validation_Errors $e) {
  21.     // process validation errors
  22. }

  23. // use data
复制代码


这样做的好处,还是刚才说的:不改变原始数据,所以在和其他的程序配合的时候,不会让其他程序出错;要用的时候再过滤和验证,提高效率;过滤和验证一体化;知道错误发生在哪儿,是什么错误,等等。

论坛徽章:
0
发表于 2009-06-27 17:55 |显示全部楼层
不错

论坛徽章:
0
发表于 2009-06-27 18:09 |显示全部楼层
建议读一下PHPBB3

论坛徽章:
0
发表于 2009-06-28 08:51 |显示全部楼层

回复 #7 dz902 的帖子

非常不错!
特别是【不改变原始数据,所以在和其他的程序配合的时候,不会让其他程序出错】
看来我需要多读读优秀的代码啊!眼界太窄了
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP