yedong0839 2014-09-19 09:39:17 11387次浏览 1条回复 7 0 0

接着上篇继续翻译,上篇地址

SQL注入

原理

简单的说,把未经过滤和验证的数据直接拼装SQL语句,会存在SQL注入漏洞。

<?php
// 警告,以下是不安全的写法
Yii::app()->db
->createCommand("DELETE FROM mytable WHERE id = " . $_GET['id'])
->execute();
$comments = Comment::model->findAll("user_id = " . $_GET['id']);

上面示例中的第一个sql语句,如果GET参数是”4 or 1=1”,这会导致表中的所有数据被删除;
第二个sql语句中,如果GET参数是2 UNION SELECT ,会导致数据库的任意数据都被查询出来。

YII如何防范

使用YII提供的函数操作

如下例

<?php
$id = intval($_GET['id']);
MyModel::model()->findByPk($id)->delete();
// 使用类型转换
$comments = Comment::model->findAllByAttributes(array('user_id' => (int)$_GET['id']);

使用YII函数要比纯SQL语句安全些;但,对于YII函数来说,使用数组比字符串更安全;如下例:

<?php
//存在sql注入
$comments = Comment::model->findAll("post_id = $postId AND author_id IN (" . join(',', $ids) . ")");
// 安全
$comments = Comment::model->findAllByAttributes(array("post_id" => $postId, "author_id" => $ids));
SQL语句预编译

当必须要使用原生的SQL的时候,比如一个SQL语句有两个参数,如下所示:

SELECT CONCAT(prefix, title) AS title, author_id, post_id, submit_date
FROM t_comment
WHERE (date > '{$date}' OR date IS NULL) AND title LIKE '%{$text}%'

遇到这种情况,有以下两种相对安全的写法:

  • 给每个参数加引号(不推荐)
  • 使用预编译SQL(推荐)
    当使用第一种方式的时候,可以使用YII的CDbConnection::quoteValue();
    比如"date > '{$date}'",可以写成 "date > " . Yii::app()->db->quoteValue($date)
    数据库服务器先编译完传入的SQL语句,再将接收到的参数插入到SQL语句的占位符。但,当数据库服务器不支持预编译时,PHP就会模拟这个过程,这也可能有SQL注入的隐患(预编译的详细原理可以参考这篇博文)。
    在YII中SQL预编译的过程可以如下所示的代码:
<?php  
// 占位符没有引号
$sql = "SELECT CONCAT(prefix, title) AS title, author_id, post_id, date "
	. "FROM t_comment "
	. "WHERE (date > :date OR date IS NULL) AND title LIKE :text"
 
// 第一种写法
$command = Yii::app()->db->createCommand($sql);
$command->bindParam(":date", $date, PDO::PARAM_STR);
$command->bindParam(":text", "%{$text}%", PDO::PARAM_STR);
$results = $command->execute();
 
// 第二种写法
$command = Yii::app()->db->createCommand($sql);
$results = $command->execute(array(':date' => $date, ':text' => "%{$text}%"));

当使用ActiveRecordr的时候用SQL预编译,语法会更加简练,如下所示:

<?php
    $comments = Comment::model->findAllBySql($sql, array(':date' => $date, ':text' => "%{$text}%"));
对SQL语句中LIKE的一些补充

在上面的示例中,即使不存在SQL注入隐患,此SQL语句中的like的使用也值得商榷。’%like%’不使用索引的,而’like%’是可以使用索引的;所以,如果将’like%’转换成’%like%’,当like的字段值很大的时候,会严重影响效率。
建议当需要使用到’%like%’的时候,尽量使用其它比较符(<=,>,……)替换。可以使用YII的CDbCriteria::compare()CDbCriteria::addSearchCondition()函数,而简化操作。

对预编译SQL中参数占位符补充

从YII1.1.8起,占位符不再用”?”标识,而是使用”:”标识;

对预编译SQL的效率的补充

预编译稍长的SQL要比不编译要稍慢些,这对系统的整体性能影响非常小。但如果同一个SQL运行多次,预编译的效率优势就体现出来了。然而,如果使用的PHP模拟预编译,则跟不编译SQL没有区别。

如果预编译不满足应用的实际需求

虽然预编译能防止SQL注入,但有些时候因为SQL语句的各个部分都是变量,所以不能使用预编译。如下所示:

SELECT *
FROM {$mytable}
WHERE {$myfield} LIKE '{$value}%' AND post_date < {$date}
ORDER BY {$myfield}
LIMIT {$mylimit}

遇到这类情况,一般使用白名单过滤SQL语句的每个部分。YII提供如下类似的过滤方法:

<?php
if (!Comment::model()->hasAttribute($myfield)) {
    die("Error");
}

更加常用的是使用YII的” Query Builde”,但不能跟CDbCriteria结合使用。 多数时候,我们可能是通过Model来查询,可以使用find*()类的方法与CDbCriteria一起使用。如下:

<?php
// YII会检测字段的合法性
$criteria = new CDbCriteria(
    array(
        'order' => $myfield,
        'limit' => $mylimit,
    )
);
$criteria->compare($myfield, $value, true); // LIKE % :$value会被转义
$criteria->compare('post_date', '<:date');
$criteria->params = array(':value' => $value, ':date' => $date);
$comments = Comment::model()->findAll($criteria)

YII的GII模块使用CGridView提供数据。CDataProvider使用CDbCriteria为CGridView提供数据,所以当使用CGridView的时候,YII会自动过滤与验证用户输入的查询条件。
一个完整的示例如下:

<?php
// 当不是原生的SQL语句的时候,YII会自动验证字段的合法性
$criteria = new CDbCriteria();
$criteria->order = $myfield;
$criteria->limit = $mylimit;
$criteria->addSearchCondition($myfield, $value, true); // true ==> LIKE '%...%'
$criteria->addCondition("post_date < :date");
$comments = Comment::model()->findAll($criteria, array(':value' => $value, ':date' => $date));  
SQL注入的总结

当使用Model进行查询时,有如下五种方式:

1.  CActiveRecord::findByPk() 或者 CActiveRecord::findAllByPk().(推荐)
2.  CActiveRecord::findByAttributes() 或者 CActiveRecord::findByAttributes()
3.  X::model()->find($criteria, array(':param1' => $value1)) 或者 ->findAll(...)
4.  X::model()->find($sql, array(':param1' => $value1)) 或者->findAll(...)
5.  X::model()->findBySql($sql, array(':param1' => $value1)) 或者 ->findAll(...)

当不是基于Model查询时,要使用预编译,如下所示:

<?php
$r = Yii::app()->db
->createCommand($sql)
->queryAll(array(':param1' => $value1)); 

使用这种方式时,切记要对用户输入的进行过滤与验证。

  • 回复于 2014-09-19 18:04 举报

    这还是说的Yii1.15啊

    url: 'http://www.yiichina.com/follow?id=28519',
    dataType: 'json',
    success: function(data) {
    	if(data.action == 'create')
    	{
    	}
    	else
    	{
    		$.ajax({
    			url: 'http://www.yiichina.com/follow?id=28519',
        		});
    	}
        },
    

    });

您需要登录后才可以回复。登录 | 立即注册