发布日期:2018-03-26
如何在PHP中防止SQL注入?+ 查看更多
如何在PHP中防止SQL注入?
+ 查看更多
发布日期:2018-03-10 14:53
分类:PHP
浏览次数:86
如果用户输入未经修改地插入到sql查询中,那么应用程序会变得易受sql注入的破坏,像下面的例子:
$unsafe_variable = $_POST['user_input']; mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");
这是因为用户可以输入一些代码像
会做一些优化,因此参数最终可能也视为数字)。在上面的例子中,如果 $name 变量包含 'Sarah'; DELETE FROM employees ,那么结果仅仅是搜索字符串 "'Sarah'; DELETE FROM employees" ,并且你不会得到一个空表。使用预处理语句的另一个好处是,如果在同一个会话中多次执行相同的语句时,它只会被解析和编译一次,在速度上可以有所提高。
value'); DROP TABLE table;--然后查询会变成:
INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')
可以做什么措施来阻止它的发生?
回答
最经典的办法是使用预处理语句和参数化查询。它们会分别被发送到数据库服务器进行解析。这种方法使得攻击者无法注入恶意的sql。但是,这个事情告诉我们, 不要信任用户的输入,对于用户的任何输入,都需要处理之后再使用 。
你有两种选择来实现该方法:
1、使用PDO(对于任何支持的数据库驱动程序):
$stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name'); $stmt->execute(array('name' => $name)); foreach ($stmt as $row) { // do something with $row }2、使用MySQLi(对于MySQL来说):
$stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?'); $stmt->bind_param('s', $name); $stmt->execute(); $result = $stmt->get_result(); while ($row = $result->fetch_assoc()) { // do something with $row }
如果你连接到除了mysql以外的数据库,有个特定的驱动程序第二个选项你可参考(例如PostgreSQL是*pg_prepare()*和*pg_execute()*)。PDO是通用的选项。
正确设置连接
注意,在默认情况下使用PDO访问mySQL数据库时,实际上不会执行预处理语句。为了解决这个问题,你应该禁止PDO模拟预处理语句。使用PDO创建数据库连接的示例如下:
$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass'); $dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
在上述示例中,错误模式不是严格必需的,但建议将其添加。这样,当出现问题是发生 Fatal Error 时,脚本不会停止运行,并且给了程序员 catch 作为 PDOExceptions 实例被 thrown 的机会。
然而,第一个 setAttribute() 调用是强制的,它禁止PDO模拟预处理语句,而使用真正的预处理语句。这样能确保语句和参数值在发送给MySQL服务器之前没有被PHP解析处理过(这将使得攻击者无法注入恶意SQL)。虽然你可以在构造函数的选项中设置 charset ,但重要的是要注意在老版本的PHP(<5.3.6)中,你在DSN上设置的字符集参数是会被自动忽略的。
解释
当你发送sql语句时,数据库服务器会进行执行前预处理的解析和编译。通过指定参数(一个问号或者在上面例子中命名的 :name ),告诉数据库引擎需要过滤哪些参数。然后当你调用
execute时,预处理语句会和你指定的参数值相结合。需要值得强调的是,*参数值是和经过编译的语句相结合,而不适合sql字符串* 。sql注入的工作原理是在脚本创建sql语句发送到数据库时欺骗使其包含恶意的字符串。所以,通过发送和参数相分离的sql语句,你限制了你不想要的sql注入的风险。当你使用预处理语句时,任何参数都会视为字符串(然而数据库引擎可能
会做一些优化,因此参数最终可能也视为数字)。在上面的例子中,如果 $name 变量包含 'Sarah'; DELETE FROM employees ,那么结果仅仅是搜索字符串 "'Sarah'; DELETE FROM employees" ,并且你不会得到一个空表。使用预处理语句的另一个好处是,如果在同一个会话中多次执行相同的语句时,它只会被解析和编译一次,在速度上可以有所提高。
因为你问到插入如何做,那么这里有个例子你可以参考一下(使用PDO)
$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)'); $preparedStatement->execute(array('column' => $unsafeValue));
预处理语句可以用于动态查询吗?
虽然你仍然可以用预处理语句进行查询参数,但是动态查询它本身的结构不能进行参数化,并且查询的特点是不能被参数化。
对于这些特殊情况,最好的方法是使用可以限制可能值的白名单过滤器。
// Value whitelist // $dir can only be 'DESC' otherwise it will be 'ASC' if (empty($dir) || $dir !== 'DESC') { $dir = 'ASC'; }除了使用预处理函数,你也可以通过PHP函数来 消毒 用户输入的代码,例如下面的函数:
function inject_check($sql_str) { return eregi('select|insert|and|or|update|delete|\'|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile', $sql_str); } function verify_id($id=null) { if(!$id) { exit('没有提交参数!'); } elseif(inject_check($id)) { exit('提交的参数非法!'); } elseif(!is_numeric($id)) { exit('提交的参数非法!'); } $id = intval($id); return $id; } function str_check( $str ) { if(!get_magic_quotes_gpc()) { $str = addslashes($str); // 进行过滤 } $str = str_replace("_", "\_", $str); $str = str_replace("%", "\%", $str); return $str; } function post_check($post) { if(!get_magic_quotes_gpc()) { $post = addslashes($post); } $post = str_replace("_", "\_", $post); $post = str_replace("%", "\%", $post); $post = nl2br($post); $post = htmlspecialchars($post); return $post; }