如何将Unicode编码的字符串(JSON)存储到数据库中?
如何将Unicode编码的字符串(JSON)存储到数据库中?
如果用户输入被直接插入SQL查询中而没有进行修改,那么应用程序就会变得容易受到SQL注入攻击,就像下面的例子一样:
$unsafe_variable = $_POST['user_input']; mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");
这是因为用户可以输入像value'); DROP TABLE table;--
这样的内容,使得查询变成:
INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')
为了防止这种情况发生,可以采取以下措施:
在将Unicode编码的字符串(JSON)存储到数据库中时,出现以下问题和解决方法:
问题原因:
为了避免SQL注入攻击,我们需要将数据与SQL语句分离,以确保数据不会被SQL解析器解释为命令。虽然我们可以创建具有正确格式化数据部分的SQL语句,但是如果不完全理解细节,建议始终使用预编译语句和参数化查询。这些是将SQL语句与参数分开发送到数据库服务器并由其解析的SQL语句。这样一来,攻击者无法注入恶意SQL。
解决方法:
我们有两种选项来实现这一点:
1. 使用PDO(适用于任何支持的数据库驱动程序):
$stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name'); $stmt->execute([ 'name' => $name ]); foreach ($stmt as $row) { // Do something with $row }
2. 使用MySQLi(适用于MySQL):
$result = $db->execute_query('SELECT * FROM employees WHERE name = ?', [$name]); while ($row = $result->fetch_assoc()) { // Do something with $row }
或者(PHP8.1之前的版本):
$stmt = $db->prepare('SELECT * FROM employees WHERE name = ?'); $stmt->bind_param('s', $name); $stmt->execute(); $result = $stmt->get_result(); while ($row = $result->fetch_assoc()) { // Do something with $row }
如果连接的是MySQL以外的数据库,可以参考特定驱动程序的第二个选项(例如,PostgreSQL的pg_prepare()和pg_execute())。PDO是通用选项。
正确设置连接:
对于PDO,需要禁用模拟预编译语句,以确保使用真正的预编译语句。示例如下:
$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8mb4', 'user', 'password'); $dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
对于MySQLi,也需要设置字符集:
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); // 错误报告 $dbConnection = new mysqli('127.0.0.1', 'username', 'password', 'test'); $dbConnection->set_charset('utf8mb4'); // 字符集
解释:
通过将参数与编译过的语句组合在一起,而不是与SQL字符串一起发送,可以限制意外出现的问题。使用预编译语句的另一个好处是,如果在同一会话中多次执行相同的语句,它只会被解析和编译一次,从而提高性能。
对于插入操作的示例(使用PDO):
$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)'); $preparedStatement->execute([ 'column' => $unsafeValue ]);
对于动态查询,可以使用参数化查询限制可能的值。
为了避免SQL注入攻击,应始终将数据与SQL语句分离,并使用预编译语句和参数化查询。这样可以保证数据不会被解析为命令,并提高性能。使用PDO或MySQLi等工具可以轻松实现这一目标,并确保正确设置数据库连接。
问题的原因是在动态生成的SQL查询中,有多个部分需要进行保护,包括字符串、数字、标识符和语法关键字。然而,目前的解决方法——准备好的语句——只能覆盖其中的两个部分。因此,需要不同的保护技术,而这种保护方法基于白名单的方式。
解决方法是将每个动态参数硬编码到脚本中,并从预先定义的集合中选择。例如,可以通过以下方式动态排序:
$orders = array("name", "price", "qty"); //字段名称 $key = array_search($_GET['sort'], $orders); //如果存在这样的名称 $orderby = $orders[$key]; //如果不存在,则自动设置为第一个 $query = "SELECT * FROM `table` ORDER BY $orderby"; //值是安全的
为了简化这个过程,可以使用一个白名单辅助函数,它可以一行代码完成所有工作:
$orderby = white_list($_GET['orderby'], "name", ["name","price","qty"], "Invalid field name"); $query = "SELECT * FROM `table` ORDER BY `$orderby`"; // sound and safe
还有一种保护标识符的方法是进行转义,但是白名单似乎是这种情况下唯一的方法。总的建议是:
- 任何表示SQL数据字面值的变量(或者简单说是SQL字符串或数字)必须通过准备好的语句添加。没有例外。
- 任何其他查询部分,如SQL关键字、表名或字段名,或运算符,必须通过白名单进行过滤。
此外,文章指出了关于SQL注入保护的最佳实践存在一些错误的做法,例如手动字符串转义和字符串格式化方法,并强调了准备好的语句是确保SQL注入安全的措施。
如何在数据库中存储Unicode编码的字符串(JSON)?
要使用参数化查询,您需要使用Mysqli或PDO。为了重写您的示例,我们需要类似以下的代码:
prepare("INSERT INTO table (column) VALUES (?)"); // “s”表示数据库期望一个字符串 $stmt->bind_param("s", $variable); $stmt->execute();
您需要了解的关键函数是`mysqli::prepare`。
另外,正如其他人建议的那样,您可能会发现使用像PDO这样的抽象层会更有用/更容易。
请注意,您提到的情况相当简单,更复杂的情况可能需要更复杂的方法。特别是:
- 如果您想根据用户输入更改SQL结构,参数化查询将无法帮助,并且所需的转义不包括`mysql_real_escape_string`。在这种情况下,最好通过白名单传递用户的输入,以确保只有“安全”值被允许通过。
使用`mysql_real_escape_string`是否足够还是需要使用参数化查询?
- 保持良好的习惯,使用参数化查询,即使在本地项目中也是如此。通过参数化查询,您可以确保不会发生SQL注入。但请记住,您应该对数据进行清理,以避免虚假检索(即XSS注入,例如在文本中放置HTML代码),例如使用`htmlentities`。
- 使用参数化查询和绑定值是一个好的做法,但`mysqli_real_escape_string`现在也是可以的。
我理解将`mysql_real_escape_string()`包含在内是为了完整性,但不喜欢首先列出最容易出错的方法。读者可能只是快速选择第一个示例。好在它现在已被弃用 🙂
- 不要同时使用两者;这将使事情变得更复杂!选择参数化查询(首选)或者`mysqli_real_escape_string`。
- 所有的`mysql_*`函数都已被弃用。它们被类似的`mysqli_*`函数所取代,例如`mysqli_real_escape_string`。
我想说的是,您使用旧的手动引用参数的方法更容易出错。我知道旧的MySQL函数已被弃用/移除,而mysqli扩展程序执行了类似的操作,但我们应该首先教授预编译语句,然后再介绍“旧方法”以便完整性。