这种动态 SQL 查询生成对注入安全吗?
Posted
技术标签:
【中文标题】这种动态 SQL 查询生成对注入安全吗?【英文标题】:Is this dynamic SQL query generation safe from injections? 【发布时间】:2016-11-11 08:38:51 【问题描述】:在我的脚本中是否有一些东西可能会逃逸,或者它对大多数 SQL 注入安全吗?按照我的理解,如果您将查询作为准备好的参数传递,那么查询的构建方式并不重要,对吧?
Edit2:我编辑了代码以反映绑定 $_POST 值的建议
$q = $pdo->prepare('SHOW COLUMNS FROM my_table');
$q->execute();
$data = $q->fetchAll(PDO::FETCH_ASSOC);
$key = array();
foreach ($data as $word)
array_push($key,$word['Field']);
$sqlSub= "INSERT INTO other_table(";
$n = 0;
foreach ($key as $index)
$sqlSub = $sqlSub.$index.", ";
$n = $n + 1;
$sqlSub = $sqlSub.") VALUES (";
for ($i=1; $i<$n;$i++)
$sqlSub = $sqlSub."?, ";
$sqlSub = $sqlSub.."?)";
$keyValues = array();
for($i=0;i<n;$i++)
array_push($keyValues,$_POST[$key[$i]]);
$q->$pdo->prepare($sqlSub);
q->execute($keyValues);
编辑:这是建议编辑后最终查询的样子
INSERT INTO other_table($key[0],...,$key[n]) VALUES (?,...,nth-?);
【问题讨论】:
if you pass query as prepared argument, it does not matter how the query was build
...错误。是的,这仍然是脆弱的。事实上,您就像没有准备查询一样容易受到攻击。使这种 sql 注入安全的方法不仅仅是准备查询,而是使用占位符准备查询并将值绑定到这些占位符。
不!如果你不使用绑定你的值,你就不安全。
不,如果你想让它安全地使用“描述”或“解释”数组键获取表中的列列表,如果它们是表单输入的名称,例如,使用列列表,您可以交叉检查它们。然后对值使用准备好的语句,对键使用白名单。
@aynber 但他们正在使用准备好的语句。他们只是使用连接,而不是与之绑定。如果不绑定,预处理语句并不比标准 sql 更安全。
@JonathanKuhn 我修改了我的评论。因为对我来说,准备好的陈述意味着具有约束力,但这只是我训练大脑的方式。
【参考方案1】:
没有。显示的示例代码对于大多数 SQL 注入来说是不安全的。
你的理解是完全错误的。
重要的是 SQL 文本。如果这是使用可能不安全的值动态生成的,那么 SQL 文本很容易受到攻击。
代码在多个地方容易受到攻击。甚至列的名称也可能不安全。
CREATE TABLE foo
( `Robert'; DROP TABLE Students; --` VARCHAR(2)
, `O``Reilly` VARCHAR(2)
);
SHOW COLUMNS FROM foo
FIELD TYPE NULL
-------------------------------- ---------- ----
Robert'; DROP TABLE Students; -- varchar(2) YES
O`Reilly varchar(2) YES
在使用另一个反引号转义列标识符中的任何反引号之后,您需要将列标识符括在反引号中。
【讨论】:
列的名称是如何不安全的,因为它们是直接从不可变的表服务器端获取的,无需用户输入?另外我编辑了我的帖子 我展示了一个示例,说明列标识符如何不安全地包含在 SQL 文本中,并且需要特殊处理以使其安全。这就是为什么您会看到 mysqldump 和各种 MySQL 工具将标识符包装在反引号中,即使不需要反引号。 (总是这样做比检查是否需要更快。) 现在我明白了。如果我还允许用户创建他们自己的表模式,然后允许他们运行这个脚本,他们就可以完全按照你通过列名所说的去做。有道理,说得好。 列名不能作为绑定参数传递。要处理这些,您需要正确转义它们,例如...'`' . str_replace( $colname, '`','``') . '`'
,然后将其作为标识符包含在 SQL 文本中。我认为您的其余代码遵循最佳实践,使用静态 ?
绑定占位符作为值,并将实际值作为绑定参数提供,而不是作为 SQL 文本的一部分。
当然,列名应该始终是固定的。永远不要让用户输入来直接确定要使用的表和/或列被操纵。不要让用户“创建自己的表”。【参考方案2】:
正如其他人所指出的,请确保您的列名是安全的。
SQL 注入可以从任何 外部输入发生,而不仅仅是 http 请求输入。如果您使用从文件、Web 服务、从其他代码的函数参数、其他代码的返回值,甚至是您自己的数据库中读取的内容,您可能会面临风险... trust什么都没有! :-)
您可以确保列名本身被转义。不幸的是,在大多数 API 或框架中没有内置函数可以做到这一点。所以你必须自己用正则表达式来做。
我还建议您了解 php 的内置数组函数 (http://php.net/manual/en/ref.array.php)。您的许多代码可以更快地开发代码,并且可能也会更好的运行时性能。
这是一个例子:
function quoteId($id)
return '`' . str_replace($id, '`', '``') . '`';
$q = $pdo->query("SHOW COLUMNS FROM my_table");
while ($field = $q->fetchColumn())
$fields[] = $field;
$params = array_intersect_key($_POST, array_flip($fields));
$fieldList = implode(",", array_map("quoteId", array_keys($params)));
$placeholderList = implode(",", array_fill(1, count($params), "?"));
$sqlSub = "INSERT INTO other_table ($fieldList) VALUES ($placeholderList)";
$q = $pdo->prepare($sqlSub);
$q->execute($params);
在此示例中,我将表中的列与发布请求参数相交。这样,我只使用列集中的那些帖子参数。它最终可能会在 SQL 中生成一个包含少于所有列的 INSERT 语句,但如果缺少的列具有默认值或允许 NULL,那没关系。
【讨论】:
【参考方案3】:确实有一种方法可以防止 SQL 注入:确保您的查询字符串的 text从不包含用户提供的内容,无论您如何尝试“清理”它。
当您按照建议使用“占位符”时,SQL 字符串的 文本 包含 (可能 ...) 个问号 ...VALUES (?, ?, ?)
表示每个要插入参数的地方。每次执行查询时,单独提供相应的参数值列表。
因此,即使为last_name
提供的值是"tables; DROP TABLE STUDENTS;"
,SQL 将永远将其视为“SQL 字符串的一部分”。它只会将“最不寻常的last_name
”插入到数据库中。
如果您正在执行bulk 操作,您只需一次 准备语句这一事实可以节省大量时间。然后,您可以根据需要多次执行该语句,每次都将一组不同(或相同)的参数值传递给它。
【讨论】:
以上是关于这种动态 SQL 查询生成对注入安全吗?的主要内容,如果未能解决你的问题,请参考以下文章
2018-2019-2 20165333 《网络对抗技术》 Exp 9 Web安全基础