关于许多有用的答案,我希望为这个线程增加一些价值。
SQL 注入是一种可以通过用户输入(由用户填写然后在查询中使用的输入)来完成的攻击。SQL 注入模式是正确的查询语法,我们可以称之为:出于不良原因的不良查询,并且我们假设可能有一个坏人试图获取影响安全三原则(机密性)的秘密信息(绕过访问控制) 、完整性和可用性)。
现在,我们的重点是防止SQL注入攻击等安全威胁,问的问题(如何使用PHP防止SQL注入攻击),更现实一点,数据过滤或清除输入数据是在使用用户输入数据时的情况这样的查询,使用 PHP 或任何其他编程语言并非如此,或者更多人建议使用现代技术,例如准备好的语句或任何其他当前支持 SQL 注入预防的工具,是否认为这些工具不再可用?您如何保护您的应用程序?
我反对 SQL 注入的方法是:在将用户输入数据发送到数据库之前(在任何查询中使用它之前)清除用户输入数据。
数据过滤(将不安全数据转换为安全数据)
考虑到PDO和MySQLi不可用。如何保护您的应用程序?你强迫我使用它们吗?PHP 以外的其他语言呢?我更喜欢提供一般性的想法,因为它可以用于更广泛的边界,而不仅仅是特定的语言。
- SQL用户(限制用户权限):最常见的SQL操作是(SELECT,UPDATE,INSERT),那么,为什么要给不需要的用户UPDATE权限呢?比如登录和搜索页面都只使用了SELECT,那么,为什么在这些页面中使用DB用户的权限很高呢?
规则:不要为所有权限创建一个数据库用户。对于所有 SQL 操作,您可以创建您的方案(如(deluser、selectuser、updateuser)作为用户名以便于使用。
参见最小特权原则。
数据过滤:在构建任何查询用户输入之前,应该对其进行验证和过滤。对于程序员来说,为每个用户输入变量定义一些属性很重要:
数据类型、数据模式和数据长度。必须使用精确规则精确验证 (x 和 y) 之间的数字字段,对于字符串(文本)字段:模式就是这种情况,例如,用户名必须只包含一些字符,让我们说[a-zA-Z0-9_-.]。长度在 (x 和 n) 之间变化,其中 x 和 n (整数,x <=n)。
规则:创建精确的过滤器和验证规则对我来说是最佳实践。
使用其他工具:在这里,我也同意您的说法,即准备好的语句(参数化查询)和存储过程。这里的缺点是这些方法需要大多数用户不具备的高级技能。这里的基本思想是区分 SQL 查询和内部使用的数据。即使是不安全的数据也可以使用这两种方法,因为这里的用户输入数据不会向原始查询添加任何内容,例如 (any 或 x=x)。
有关更多信息,请阅读OWASP SQL 注入预防备忘单。
现在,如果你是高级用户,开始使用这个防御就可以了,但是,对于初学者来说,如果他们不能快速实现存储过程并准备好语句,那么最好尽可能过滤输入数据。
最后,让我们考虑一个用户在下面发送这个文本而不是输入他/她的用户名:
[1] UNION SELECT IF(SUBSTRING(Password,1,1)='2',BENCHMARK(100000,SHA1(1)),0) User,Password FROM mysql.user WHERE User = 'root'
无需任何准备好的语句和存储过程即可尽早检查此输入,但为了安全起见,在用户数据过滤和验证之后开始使用它们。
最后一点是检测需要更多努力和复杂性的意外行为;不建议将其用于普通的 Web 应用程序。
上述用户输入中的意外行为是 SELECT、UNION、IF、SUBSTRING、BENCHMARK、SHA 和 root。一旦检测到这些词,就可以避免输入。
更新 1:
有网友评论说这个帖子没用,OK!以下是OWASP.ORG 提供的内容:
主要防御:
选项 #1:使用准备好的语句(参数化查询)
选项 #2:使用存储过程
选项 #3:转义所有用户提供的输入
其他防御:
同时执行:最小权限
同时执行:白名单输入验证
您可能知道,声明一篇文章应该有一个有效的论据支持,至少有一个参考!否则,将被视为攻击和恶意索赔!
更新 2:
来自 PHP 手册,PHP:Prepared Statements - Manual:
转义和 SQL 注入
绑定变量将被服务器自动转义。服务器在执行之前将它们的转义值插入到语句模板的适当位置。必须向服务器提供绑定变量类型的提示,以创建适当的转换。有关详细信息,请参阅 mysqli_stmt_bind_param() 函数。
服务器内的值的自动转义有时被认为是防止 SQL 注入的安全功能。如果输入值被正确转义,则可以使用非准备语句实现相同程度的安全性。
更新 3:
我创建了测试用例来了解 PDO 和 MySQLi 在使用准备好的语句时如何将查询发送到 MySQL 服务器:
PDO:
$user = "''1''"; // Malicious keyword
$sql = 'SELECT * FROM awa_user WHERE userame =:username';
$sth = $dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
$sth->execute(array(':username' => $user));
查询日志:
189 Query SELECT * FROM awa_user WHERE userame ='\'\'1\'\''
189 Quit
MySQLi:
$stmt = $mysqli->prepare("SELECT * FROM awa_user WHERE username =?")) {
$stmt->bind_param("s", $user);
$user = "''1''";
$stmt->execute();
查询日志:
188 Prepare SELECT * FROM awa_user WHERE username =?
188 Execute SELECT * FROM awa_user WHERE username ='\'\'1\'\''
188 Quit
很明显,准备好的语句也在转义数据,仅此而已。
正如上述声明中也提到的,
服务器内的值的自动转义有时被认为是防止 SQL 注入的安全功能。如果输入值被正确转义,则使用非准备语句可以实现相同程度的安全性
因此,这证明了intval()
在发送任何查询之前对整数值进行数据验证是一个好主意。此外,在发送查询之前阻止恶意用户数据是一种正确有效的方法。
请参阅此问题以获取更多详细信息:PDO 将原始查询发送到 MySQL 而 Mysqli 发送准备好的查询,两者都产生相同的结果
参考:
- SQL 注入备忘单
- SQL 注入
- 信息安全
- 安全原则
- 数据验证