如果用户输入未经修改就插入到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;--')

可以采取什么措施防止这种情况发生?


当前回答

有许多方法可以防止SQL注入和其他SQL攻击。你可以很容易地在互联网上找到它(谷歌搜索)。当然,PDO是一个很好的解决方案。但我想建议您一些防止SQL注入的良好链接。

什么是SQL注入以及如何防止

SQL注入PHP手册

Microsoft对PHP中SQL注入和预防的解释

还有一些类似于防止MySQL和PHP的SQL注入。

现在,为什么需要防止SQL注入查询?

我想告诉您:为什么我们要通过下面的一个简短示例来防止SQL注入:

查询登录身份验证匹配:

$query="select * from users where email='".$_POST['email']."' and password='".$_POST['password']."' ";

现在,如果有人(黑客)

$_POST['email']= admin@emali.com' OR '1=1

和密码。。。。

查询将被解析到系统中,直到:

$query="select * from users where email='admin@emali.com' OR '1=1';

另一部分将被丢弃。那么,会发生什么?未经授权的用户(黑客)可以以管理员身份登录,而无需密码。现在,他/她可以做管理员/电子邮件管理员可以做的任何事情。看,如果不阻止SQL注入,这是非常危险的。

其他回答

要使用参数化查询,需要使用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不包含所需的转义。在这种情况下,最好通过白名单传递用户的输入,以确保只允许通过“安全”值。

使用PDO和准备好的查询。

($conn是PDO对象)

$stmt = $conn->prepare("INSERT INTO tbl VALUES(:id, :name)");
$stmt->bindValue(':id', $id);
$stmt->bindValue(':name', $name);
$stmt->execute();

我认为,在PHP应用程序(或任何web应用程序)中防止SQL注入的最佳方法是考虑应用程序的架构。如果防止SQL注入的唯一方法是记住使用一个特殊的方法或函数,它在每次与数据库对话时都会做正确的事情,那么这是错误的。这样,在代码的某个时刻忘记正确格式化查询只是时间问题。

采用MVC模式和CakePHP或CodeIgniter这样的框架可能是正确的方法:创建安全数据库查询等常见任务已在这些框架中得到解决并集中实现。它们可以帮助您以合理的方式组织web应用程序,并让您更多地考虑加载和保存对象,而不是安全地构造单个SQL查询。

如果可能,请转换参数的类型。但它只适用于像int、bool和float这样的简单类型。

$unsafe_variable = $_POST['user_id'];

$safe_variable = (int)$unsafe_variable ;

mysqli_query($conn, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

正如你所看到的,人们建议你最多使用准备好的陈述。这并没有错,但如果每个进程只执行一次查询,则会有轻微的性能损失。

我正面临这个问题,但我认为我以非常复杂的方式解决了它——黑客避免使用引号的方式。我将其与模拟的准备语句结合使用。我使用它来防止各种可能的SQL注入攻击。

我的方法:

如果您希望输入是整数,请确保它真的是整数。在像PHP这样的变量类型语言中,这一点非常重要。例如,您可以使用这个非常简单但功能强大的解决方案:sprintf(“SELECT 1,2,3 FROM table WHERE 4=%u”,$input);如果你对整数十六进制有任何期望,如果你对它十六进制,你将完全逃避所有输入。在C/C++中有一个名为mysql_hex_string()的函数,在PHP中可以使用bin2hex()。不用担心转义字符串的大小是其原始长度的2倍,因为即使使用mysql_real_ascape_string,PHP也必须分配相同的容量((2*input_length)+1),这是相同的。这种十六进制方法通常在传输二进制数据时使用,但我认为没有理由不对所有数据使用它来防止SQL注入攻击。请注意,您必须在数据前面加上0x,或者改用MySQL函数UNHEX。

例如,查询:

SELECT password FROM users WHERE name = 'root';

将变成:

SELECT password FROM users WHERE name = 0x726f6f74;

or

SELECT password FROM users WHERE name = UNHEX('726f6f74');

Hex是完美的逃脱。无法注射。

UNHEX函数与0x前缀之间的差异

评论中有一些讨论,所以我最后想说清楚。这两种方法非常相似,但在某些方面略有不同:

0x前缀只能用于char、varchar、text、block、binary等数据列。此外,如果要插入空字符串,则其使用有点复杂。您必须将其完全替换为“”,否则会出现错误。

UNHEX()适用于任何列;你不必担心空字符串。


十六进制方法通常用作攻击

注意,这种十六进制方法通常被用作SQL注入攻击,其中整数就像字符串一样,并且只使用mysql_real_aescape_string进行转义。然后可以避免使用引号。

例如,如果你只是这样做:

"SELECT title FROM article WHERE id = " . mysql_real_escape_string($_GET["id"])

攻击很容易给你注射。考虑从脚本返回的以下注入代码:

SELECT ... WHERE id = -1 UNION ALL SELECT table_name FROM information_schema.tables;

现在只提取表结构:

SELECT ... WHERE id = -1 UNION ALL SELECT column_name FROM information_schema.column WHERE table_name = __0x61727469636c65__;

然后只需选择所需的数据。是不是很酷?

但如果可注入站点的编码器将其十六进制,则不可能进行注入,因为查询将如下所示:

SELECT ... WHERE id = UNHEX('2d312075...3635');