我有一个自定义函数可以创建准备好的语句。由于我使用的是准备好的语句,因此 checkmarx 不应突出显示此函数中的二阶 SQL 注入问题。
function insertUsingPreparedStmt($table, $params, $bind_condition)
{
$dbobj = $this->getMysqliObject();
$fields = array_keys($params);
$values = array_values($params);
$question_mark = '';
foreach ($values as $id) $question_mark .= '?, ';
$question_mark = rtrim($question_mark, ', ');
$bind_params[] = &$bind_condition['bind_type'];
$n = count($values);
for ($i = 0; $i < $n; $i++) {
$bind_params[] = &$values[$i];
}
$query = "INSERT INTO $table (" . implode(" , ", $fields) . ") VALUES ( $question_mark )";
$stmt = $dbobj->prepare($query);
call_user_func_array(array($stmt, 'bind_param'), $bind_params);
$stmt->execute();
$stmt->close();
}
Checkmarx 说
方法
在executeStoreProcedure
app/Mysqlim.php
的line 994从元素获取数据库数据。然后,此元素的值在未经过适当清理或验证的情况下流经代码,并最终在fetch_object
的 line 1568 的方法insertUsingPreparedStmt
中用于数据库查询。这可能会启用 二阶 SQL 注入 攻击。app/Mysqlim.php
这类问题大约有 12 个,我确信这些是误报。我怎样才能从 checkmarx 中摆脱这些?
$table
和 keys($params)
都是可能的 SQL 注入向量,因为它们在没有经过验证的情况下被插入到 SQL 查询中。
根据错误,我猜测一个或两个最初来自数据库,因此使其成为二阶SQL注入。如果有人可以通过插入字符串来中毒您的数据库,然后查询该字符串并最终将其用作表名或列名,那么这就是 SQL 注入。
你经常听到人们说“使用准备好的语句!”好像那是关键部分。事实上,如果查询被 SQL 注入破坏,简单地使用
prepare()
并不能神奇地使查询安全。 parameters 使查询安全。但是您只能使用参数代替 SQL 查询中的标量值,而不能使用表标识符或列标识符。那么如何解决呢?
一个好的解决方案是使用 allowlist 来验证标识符。如果您插入的值在您的代码中是一个固定值,并且不是不安全的来源(例如用户输入)或来自外部来源(例如数据库、文件或 Web 请求),则该值是安全的。外部输入可以选择使用哪个值,但值本身不是外部输入
假设您的 PHP 应用程序中有一个名为
$allMyTables
的全局变量。假设它是一个散列,其中表名是键,这个键指向另一个散列,其中该表的列名是键。这是单个表的示例:
$allMyTables = [
'mytable' => [
'id' => true,
'name' => true,
'score' => true,
'created_at' => true,
'updated_at' => true
]
];
你如何填充它现在并不重要。假设您只是将它作为一个静态 PHP 文件添加到您的项目中。如果添加表或列,则需要编辑此 PHP 文件。关键是这些值在您的代码中是固定的,它们不是来自外部来源。
我会这样写你的函数:
function insertUsingPreparedStmt($table, $params)
{
global $allMyTables;
/* first validate that the table exists */
if (!array_key_exists($table, $allMyTables)) {
throw new ErrorException("Table $table is unknown. Contact site administrator.");
}
/* get the subset of $params for columns known to belong to that table */
$params = array_intersect_key($params, $allMyTables[$table]);
$dbobj = $this->getMysqliObject();
$columns = array_keys($params);
$columns_string = implode(',', array_map(func($col) { return "`$col`"; }, $columns));
$values = array_values($params);
$placeholders = implode(',', array_fill(0, count($columns), '?'));
$query = "INSERT INTO `$table` ($columns_string) VALUES ($placeholders)";
$stmt = $dbobj->prepare($query);
$stmt->bind_param(str_repeat('s', count($columns)), ...$values);
$stmt->execute();
$stmt->close();
}
在这个例子中,我还确保将表名和列名放在反引号中,以防它们与 SQL 保留关键字冲突。
在这个函数中,以及在MySQL中的大多数情况下,您可以将所有参数作为字符串类型传递。 MySQL 会根据需要进行类型转换。只有少数例外。
PHP 的
...
语法 passing an array to a variadic function 从 PHP 5.6(大约 2014 年)开始可用。对于这种情况,不再需要使用call_user_func_array()
。
实际上,如果我编写此函数,我会进行另一项更改:我会使用 PDO 而不是 mysqli。它使一些任务更简单。该函数的最后几行是:
$stmt = $dbobj->prepare($query);
$stmt->execute($values);