我正在用PHP编写DbAdapter。为了避免SQL注入攻击,对于条件选择,我需要一种方法来检查将要运行的SQL查询的完整性。鉴于准备好的语句会使实现变得非常复杂,是否有一种快速的方法可以在类的心脏执行之前检查sql查询(尤其是WHERE
子句)的健全性?例如,对于恶意或可疑查询,返回假值的辅助方法会很好。
我的班级代码:
require_once './config.php';
class DbAdapter
{
private $link;
/**
* DbAdapter constructor.
*/
public function __construct()
{
$this->link = new mysqli(DBHOST, DBUSER, DBPASS, DBNAME);
if ($this->link->connect_errno) {
die($this->link->connect_error);
}
}
/**
* @param $table
* @param array $columns
* @param string $condition
* @return bool|mysqli_result
*/
public function select($table, $columns = [], $condition = "")
{
$colsString = $this->extractCols($columns);
$whereString = $this->extractConditions($condition);
$sql = "SELECT $colsString FROM `$table` " . $whereString;
return $this->link->query($sql);
}
public function __destruct()
{
$this->link->close();
}
private function extractCols(array $columns)
{
if(!$columns) { return '*';}
else {
$str = "";
foreach($columns as $col) {
$str .= "$col,";
}
return trim($str, ',');
}
}
private function extractConditions(string $conditions)
{
if(!$conditions) {
return "";
}
else {
$where = "WHERE ";
foreach ($conditions as $key => $value){
$where .= "$key=" . $conditions[$key] . "&";
}
return trim($where, "&");
}
}
}
答案 0 :(得分:2)
您可以像在EXPLAIN SELECT foo FROM table_bar
中那样使用EXPLAIN
。然而,如何以编程方式解释“理智”的结果是一个更加困难的问题。您需要以编程方式定义“理智性”,例如“检查多于 n 个行”或“涉及多于 t 个表”。
您提到您的动机包括希望“避免sql注入攻击”。如果那是让您担心的事情,那么这里最重要的是避免将任何用户数据连接到查询中。如果您将任何用户数据进行串联,则SQL injection是可能的,而且很难检测到。更好地只是完全阻止它。
坦率地说,这段代码使我的头发直立起来:
$where = "WHERE ";
foreach ($conditions as $key => $value){
$where .= "$key=" . $conditions[$key] . "&";
}
没有办法使它足够安全或充分检查。您可能会认为,“是的,但是所有条件都应仅包含数字”或类似的易于验证的内容,但您不能放心地依靠它。明年,下周或明天修改代码并添加字符串参数时会发生什么?即时漏洞。
您需要使用准备好的语句,而不是将变量连接到查询中。仅转义变量是不够的。参见How can I prevent SQL injection in PHP?。
请注意,通常这是在将查询部署到生产之前而不是在进行中之前要做的。如果您要建立允许用户建立自己的查询的收费标准,则不可避免地需要对查询进行即时评估。
但是,如果您要处理的只是WHERE
子句中的多个条件,那么只要满足以下两项条件,查询就会很快(并且您不需要使用EXPLAIN
) :
... WHERE id IN (SELECT id from OtherTable WHERE ...) ...
之类的子查询,并且我曾经写过一个工具,它允许在每个主表中都有几百万行的数据库上构建各种复杂的查询并针对MySQL运行。查询大多是简单的WHERE
条件,例如WHERE lastOrder > '2018-01-01'
,还有一些(大多是硬编码的)JOIN
和子查询可能性。我只是积极地建立索引,不需要EXPLAIN
进行任何操作;它从未真正遇到任何性能瓶颈。
答案 1 :(得分:1)
从根本上来说,允许任意输入成为SQL代码的一部分是错误的设计。没有办法使这个“理智”。
诸如Database Firewall之类的某些技术会尝试执行您要求的操作,以检测何时SQL注入攻击破坏了查询。问题是,很难区分遭到入侵的SQL查询和仅包含合法动态内容的SQL查询。
结果是注射检测方法不可靠。他们无法检测到所有注射案例,也将它们误认为是合法的注射案例。
另一种方法是使用SQL查询的白名单。也就是说,枚举给定应用程序使用的所有合法SQL查询形式,并仅允许那些查询运行。这要求您在部署之前以一种“教学模式”运行该应用程序,以标识所有合法的SQL查询。然后打开数据库防火墙,以阻止测试运行时所有未知的SQL查询。
这也有缺点。它不考虑需要完全动态的SQL查询,例如数据透视表查询或构造条件(例如,您的查询根据应用逻辑在WHERE子句中获得可变数量的术语)。
防止SQL注入的最佳方法仍然是使用代码审查。确保使用准备好的语句将任何动态值作为查询参数传递。您声称这使代码“非常复杂”,但事实并非如此。
$sql = "SELECT ...";
$stmt = $pdo->prepare($sql);
$stmt->execute($paramValuesArray);
至少我们可以说,编写所有显示的将条款附加到SQL语句的代码并非难事。