Checkmarx二阶SQL注入php



我有一个创建预处理语句的自定义函数。由于我使用的是准备好的语句,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第994行app/Mysqlim.phpfetch_object元素获取数据库数据。然后,该元素的值在代码中流动,而没有经过适当的清理或验证,并最终在第1568行处的方法insertUsingPreparedStmt中的数据库查询中使用。app/Mysqlim.php。这可能会启用二阶SQL注入攻击。

这类问题大概有12个,我敢肯定这些都是假阳性。我怎样才能从checkmarx中去掉这些呢?

$tablekeys($params)都是可能的SQL注入向量,因为它们被插入到SQL查询中而没有经过验证。

基于错误,我猜其中一个或两个最初来自数据库,从而使其成为二级SQL注入。如果有人可以通过插入字符串来毒害您的数据库,然后该字符串被查询并最终用作表名或列名,那么这是SQL注入。

你经常听到人们说"使用准备好的语句!"好像那才是最关键的部分。事实上,如果查询受到SQL注入的威胁,仅仅使用prepare()并不能神奇地使查询变得安全。是参数使查询安全。但是,在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的...语法用于将数组传递给可变函数,自PHP 5.6(大约2014年)以来已经可用。在这种情况下,不再需要使用call_user_func_array()

实际上,如果我写这个函数,我会做另一个改变:我将使用PDO而不是mysqli。它使一些任务更简单。函数的最后一行应该是:

$stmt = $dbobj->prepare($query);
$stmt->execute($values);

最新更新