如果用户输入未经修改就插入到SQL查询中,则应用程序很容易受到SQL注入的攻击,如下例所示:
$unsafe_variable = $_POST['user_input'];
mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");
这是因为用户可以输入类似值的内容);DROP TABLE表;--,并且查询变为:
INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')
可以采取什么措施防止这种情况发生?
要使用参数化查询,需要使用Mysqli或PDO。要用mysqli重写示例,我们需要以下内容。
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("server", "username", "password", "database_name");
$variable = $_POST["user-input"];
$stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");
// "s" means the database expects a string
$stmt->bind_param("s", $variable);
$stmt->execute();
你想在那里读到的关键函数是mysqli::prepare。
此外,正如其他人所建议的,您可能会发现使用PDO之类的东西来提升抽象层是有用的/更容易的。
请注意,您询问的案例相当简单,更复杂的案例可能需要更复杂的方法。特别地:
如果您希望根据用户输入更改SQL的结构,参数化查询将不会有帮助,并且mysql_real_ascape_string不包含所需的转义。在这种情况下,最好通过白名单传递用户的输入,以确保只允许通过“安全”值。
不推荐的警告:这个答案的示例代码(与问题的示例代码一样)使用了PHP的MySQL扩展,该扩展在PHP 5.5.0中被弃用,在PHP 7.0.0中被完全删除。安全警告:此答案不符合安全最佳实践。转义不足以防止SQL注入,请改用准备好的语句。使用以下概述的策略,风险自负。(此外,在PHP7中删除了mysql_real_ascape_string()。)
使用PDO和MYSQLi是防止SQL注入的好方法,但如果您真的想使用MySQL函数和查询,最好使用
mysql_real_reape_string
$unsafe_variable = mysql_real_escape_string($_POST['user_input']);
还有更多的功能可以防止这种情况:比如识别-如果输入是字符串、数字、字符或数组,那么有很多内置函数可以检测这种情况。此外,最好使用这些函数来检查输入数据。
is_string(is_string)
$unsafe_variable = (is_string($_POST['user_input']) ? $_POST['user_input'] : '');
是数字(_N)
$unsafe_variable = (is_numeric($_POST['user_input']) ? $_POST['user_input'] : '');
使用这些函数来检查mysql_real_aescape_string中的输入数据要好得多。
我几年前就写过这个小函数:
function sqlvprintf($query, $args)
{
global $DB_LINK;
$ctr = 0;
ensureConnection(); // Connect to database if not connected already.
$values = array();
foreach ($args as $value)
{
if (is_string($value))
{
$value = "'" . mysqli_real_escape_string($DB_LINK, $value) . "'";
}
else if (is_null($value))
{
$value = 'NULL';
}
else if (!is_int($value) && !is_float($value))
{
die('Only numeric, string, array and NULL arguments allowed in a query. Argument '.($ctr+1).' is not a basic type, it\'s type is '. gettype($value). '.');
}
$values[] = $value;
$ctr++;
}
$query = preg_replace_callback(
'/{(\\d+)}/',
function($match) use ($values)
{
if (isset($values[$match[1]]))
{
return $values[$match[1]];
}
else
{
return $match[0];
}
},
$query
);
return $query;
}
function runEscapedQuery($preparedQuery /*, ...*/)
{
$params = array_slice(func_get_args(), 1);
$results = runQuery(sqlvprintf($preparedQuery, $params)); // Run query and fetch results.
return $results;
}
这允许在一行C#字符串中运行语句。格式如下:
runEscapedQuery("INSERT INTO Whatever (id, foo, bar) VALUES ({0}, {1}, {2})", $numericVar, $stringVar1, $stringVar2);
考虑到变量类型,它会逃逸。若您试图参数化表、列名称,它将失败,因为它将每个字符串放在引号中,这是无效语法。
安全更新:以前的str_replace版本允许通过向用户数据中添加{#}标记进行注入。如果替换包含这些令牌,则preg_replace_callback版本不会导致问题。
PHP和MySQL有很多答案,但以下是针对PHP和Oracle的代码,用于防止SQL注入以及定期使用oci8驱动程序:
$conn = oci_connect($username, $password, $connection_string);
$stmt = oci_parse($conn, 'UPDATE table SET field = :xx WHERE ID = 123');
oci_bind_by_name($stmt, ':xx', $fieldval);
oci_execute($stmt);