前言
前不久的时候,发小 说不敢把自己的代码发到github上面,怕很多人找bug比较麻烦,我同时也在思考一个问题,平时审计或者黑盒的时候都没有留意,总是想着如何进攻别人,现在换一个角度来总结一下,如何做到百分百防止注入呢,也许从这个角度学习会让我进步更快。
一、浅谈sql注入
mysql_real_escape_string
在测试是否存在sql注入的时候,主要是看是否有可控的变量,那么在程序员的角度上,可能会用mysql_real_escape_string,是一个字符串转义函数。它将做的只是转义危险字符,以便它们可以安全地用于单个查询字符串。但是,如果您不事先清理输入,那么您将容易受到某些攻击向量的攻击。
想象一下下面的sql:
$result = "SELECT fields FROM table WHERE id = ".mysql_real_escape_string($_POST['id']);
您应该能够看到这很容易被利用。
想象一下id参数包含常见的攻击向量:
1 OR 1=1
那里没有要编码的危险字符,因此它将直接通过转义过滤器。
记录一个sql语句
1 or is_admin=1 order by id limit 1
产生
SELECT fields FROM table WHERE id=1 or is_admin=1 order by id limit 1
这允许攻击者在这个完全虚构的示例中返回第一个管理员的详细信息。
所以在使用mysql_real_escape_string这个函数拼接到sql语句时一定要加引号,把参数限制在里面
自己定义的转义函数
有些人会把所有的输入都经过验证,如定义一个函数,来检验用户输入的是否都是数字
function Numbers($input) {
$input = preg_replace("/[^0-9]/","", $input);
if($input == '') $input = 0;
return $input;
}
所以不是使用
$result = "SELECT fields FROM table WHERE id = ".mysqlrealescapestring("1 OR 1=1");
他会用
$result = "SELECT fields FROM table WHERE id = ".Numbers("1 OR 1=1");
但是从功能搜索框的功能角度上来考虑,这显然有很多限制,如在csdn上面就不能使用。
PDO与MySQLi
无论您使用哪种转义方法,避免 SQL 注入攻击的正确方法是将数据与 SQL 分离,以便数据保持数据,永远不会被 SQL 解析器解释为命令。可以使用格式正确的数据部分创建 SQL 语句,但如果您不完全了解详细信息,则应始终使用准备好的语句和参数化查询。这些是与任何参数分开发送到数据库服务器并由其解析的 SQL 语句。这样攻击者就不可能注入恶意 SQL。
你基本上有两种选择来实现这一点:
1.使用PDO(对于任何支持的数据库驱动程序):
$stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
$stmt->execute([ '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); // 's' specifies the variable type => 'string'
$stmt->execute();
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
// Do something with $row
}
但是这个绝对依旧是有前提的,虽然利用起来相对苛刻,还是提一下吧
- 使用 MySQL 的现代版本(5.1 后期,所有 5.5、5.6 等)和PDO 的 DSN 字符集参数(在 PHP ≥ 5.3.6 中)
- 不要使用易受攻击的字符集进行连接编码(你只使用utf8///等latin1)ascii
- 启用NO_BACKSLASH_ESCAPESSQL 模式
这三种情况任意一种你是100%安全的。
否则,即使您使用 PDO 准备语句,您也很可能受到攻击……
不得不提一下的是,mysql_real_escape_string只是DBMS里面MySQL的,PostgreSQL 里面会有pg_escape_string(),一般作者都会用DBMS自带的转义函数,如果不用自带的可以用比如说addslashes()。
addslashes绕过的方法在特定情况下其实也挺多,总结一下大概就是因为编码的原因导致绕过了要转义的字符,或者直接没有引号限制,列举一下:
- 虽然使用了addslashes转义,但是缺没有加引号,直接无视过滤。
- 使用了数据库字符gbk编码
- 因为编码解码或者编码函数原因导致宽字节注入
所以,如果真的想要安全一些,最稳妥的方法就是请您避免 SQL 注入攻击的正确方法是将数据与 SQL 分离,以便数据保持数据,永远不会被SQL 解析器解释为命令。