所以这更多的是关于获得您对最佳方法的意见。
我有一个我认为是相当优雅的方式来构建一个简单的动态SQL语句与一个直接的WHERE子句。WHERE子句可以包含多个字段,但它是有限的,因为它不允许不同的操作符(比较或逻辑)。
我可以这样构建如下代码:
SELECT * from table_name WHERE field_1 = "value_1" AND field_2 = "value_2";
//or I can do
SELECT * from table_name WHERE field_1 = "value_1" OR field_2 = "value_2";
//or I can do
SELECT * from table_name WHERE field_1 <> "value_1" AND field_2 <> "value_2";
我可以不构建以下代码:
SELECT * from table_name WHERE field_1 = "value_1" AND field_2 <> "value_2";
//nor can I do
SELECT * from table_name WHERE field_1 = "value_1" AND field_2="value_2" OR field_3 = "value_3
在处理数字和日期时,当我想寻找具有值之间的记录时,我需要用两个单独的值....传递两次相同的文件,这成为一个真正的问题不吗?
SELECT * from table_name WHERE price BETWEEN 10 AND 20;
SELECT * from table_name WHERE date BETWEEN "2016-08-01" AND "2016-08-15";
并且不要忘记带有"IN"或LIKE语句的多个条件,这也不会构建,即:
SELECT * from table_name WHERE field_1 IN("value_1","value_2, "value_3");
SELECT * from table_name WHERE field_1 LIKE "val%";
下面是我当前的代码:
// db contains my DB connection
$db = new DB();
$where = 'WHERE';
$criteria = array();
foreach ($_GET as $key => $value) {
$where = $where.' '.$key.'=? AND';
array_push($criteria,$value);
}
if(count($_GET) > 0){
// $sql will look like: SELECT * FROM table_name WHERE field_1 = ? AND field_2 = ?
// $criteria is an array of values to pair with the above prepared statement.
// Will look like: $criteria("value_1", "value_2")
$sql = 'SELECT * FROM mcl_data_gap '.$where;
$results = $db->query($sql,$criteria);
} else {
$sql = 'SELECT * FROM mcl_data_gap';
$results = $db->query($sql);
}
// .... continue on using above SQL statement
在上面的代码中,我使用了get,但我的假设是post也可以工作。
我唯一的想法是插入更多的键值对,其中包含编码格式所需的操作符,这将允许我然后寻找这些操作符并基于它们构建语句,但我只是觉得有一个更好的方法,这就是我希望你能帮助。
我刚想到的另一个选项是在将SQL传递给服务器之前构建SQL并执行它。
或者我可以发布包含整个WHERE语句段的对象吗?
您正在使用动态值的查询参数(相等比较的右侧)。这很好。
但是不能为动态列名(比较的左侧)使用参数。这就是代码容易受到SQL注入攻击的原因。预处理语句在这方面没有帮助。
解决方案是确保来自$_GET键的每个列名实际上是表中的列之一。换句话说,这称为将输入列入白名单。
$mcl_data_gap_columns = ["field_1", "field_2", "field_3"];
您只希望处理与表中存在的列列表中的列匹配的$_GET参数。任何不在此列表中的内容都应该被忽略。
对于具有多个值的谓词,您可以在PHP中访问它们,方法是将GET参数命名为末尾为"[]
"。
$terms = [];
$parameters = [];
// only look for $_GET keys that match one of the known columns.
// this automatically ignores all other $_GET keys.
foreach ($mc_data_gap_columns as $col) {
// get the single value, or the array of multiple values.
// convert to an array in either case.
if (isset($_GET[$col])) {
$values = (array) $_GET[$col];
$default_op = "=";
} elseif (isset($_GET[$col."[]"])) {
$values = $_GET[$col."[]"];
$default_op = "IN";
} else {
continue;
}
// if your comparison is anything other than equality,
// there should be another request parameter noting that.
if (isset($_GET[$col."_SQLOP"])) {
$op = $_GET[$col."_SQLOP"];
} else {
$op = $default_op;
}
只处理已知的操作。如果$op不是特定支持的操作之一,忽略它,否则抛出错误。
switch ($op) {
case "=":
case ">":
case "<":
case ">=":
case "<=":
case "<>":
// all these are simple comparisons of one column to one value
$terms[] = "$col $op ?";
$parameters[] = $values[0];
break;
case "BETWEEN":
// comparisons of one column between two values
if (count($values) != 2) {
error_log("$col BETWEEN: wrong number of arguments: " . count($values));
die("Sorry, there has been an error in your request.");
}
$terms[] = "$col BETWEEN ? AND ?";
$parameters[] = $values[0];
$parameters[] = $values[1];
break;
case "IN":
// comparisons of one column IN a list of any number of values
$placeholders = implode(",", array_fill(1, count($values), "?"));
$terms[] = "$col IN ($placeholders)";
$parameters = array_merge($parameters, $values);
break;
default:
error_log("Unknown operation for $col: $op");
die("Sorry, there has been an error in your request.");
}
}
最后,你会知道$terms要么是一个空数组,要么是一个搜索条件数组。
if ($terms) {
$sql .= " WHERE " . join(" AND ", $terms);
}
$db->query($sql, $parameters);
我没有测试上面的代码,但它应该说明了这个想法:
- 不要在SQL查询中逐字使用$_GET输入
- 始终根据固定的安全值列表过滤输入
- 或使用
switch
对一组固定的安全case
s进行测试
我刚想到的另一个选项是在将SQL传递给服务器之前构建SQL并执行它。
不,不,不!这只会让你被黑。千万不要这样做!
如果你认为你的HTML页面是别人向你的服务器提交请求的唯一途径,那你就错了。任何人都可以形成他们想要的任何URL,并将其提交到您的站点,即使它包含您不期望的GET参数和值。