通过$ _POST构建动态SQL语句

时间:2016-10-24 12:51:03

标签: php mysql sql

所以这更多的是关于就此采取最佳方法的意见。

我认为这是一种非常优雅的方法,可以使用简单的WHERE子句构建简单的动态SQL语句。 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";

并且不要忘记&#34; IN&#34;的多个标准。或者也不构建的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语句的整个片段的对象吗?

1 个答案:

答案 0 :(得分:0)

您正在使用查询参数作为动态值(相等比较的右侧)。这很好。

但是您不能将参数用于动态列名称(比较的左侧)。这就是您的代码易受SQL注入攻击的方式。准备好的陈述对此没有帮助。

解决方案是确保来自$ _GET键的每个列名实际上都是表中的一列。换句话说,这称为输入白名单。

$mcl_data_gap_columns = ["field_1", "field_2", "field_3"];

您只想处理与表中存在的列列中的列匹配的$ _GET参数。应该忽略此列表中没有的任何内容。

对于具有多个值的谓词,您可以通过在末尾用“[]”命名GET参数来在PHP中访问它们。

$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页面是某人可以向您的服务器提交请求的唯一方式,则表示您的错误。 任何人可以形成他们想要的任何网址,并将其提交到您的网站,即使它包含您不期望的GET参数和值。