什么时候应该使用预准备语句?

When should I use prepared statements?

提问人:G.SINGH 提问时间:7/28/2014 最后编辑:DharmanG.SINGH 更新时间:11/13/2023 访问量:18584

问:

本来我用和做事。然后我学习了SQL注入,所以我正在尝试学习如何使用预准备语句。我了解 PDO 类的准备和执行函数如何有助于防止 SQL 注入。mysql_connectmysql_query

是否只有在将用户输入存储到数据库中时才需要准备好的语句?仍然可以使用,因为我真的没有使用此功能被黑客入侵的风险吗?还是使用准备好的语句来执行此操作更安全?对于涉及使用MySQL的所有内容,我是否应该使用预准备语句?为什么?mysql_num_rows

php 安全 准备语句 sql 注入

评论

2赞 Mike 'Pomax' Kamermans 7/28/2014
如果你能保证你的代码库的任何部分都不会使用用户生成的查询数据,那么就没有多大意义了。即使只有一个查询,使用两种不同的查询方式也是没有意义的。首先使用准备好的查询,以确保安全性,其次才是一致性。
0赞 elixenide 7/28/2014
你似乎有点困惑。首先,请不要使用mysql_*;这些函数已过时、已弃用且不安全。请改用 MySQLiPDO。其次,它与准备好的语句无关,无论如何都不是 PDO 功能。在运行查询之前准备语句,而不是在运行查询时准备行数。mysql_*mysql_num_rows

答:

0赞 Rakesh Sharma 7/28/2014 #1

Mysql_*已被弃用,因此最好切换或mysqli_*PDO

为了防止SQL注入(mysql):-如何防止PHP中的SQL注入?.

和预准备语句(这些是与任何参数分开发送到数据库服务器并由数据库服务器解析的 SQL 语句)用于每个用户生成的查询数据。

就像在发布数据时一样,您将记录与查询匹配/获取到数据库。所以意味着当你用表单数据触发查询时。

5赞 elixenide 7/28/2014 #2

TL;DR 如果您的 SQL 使用任何类型的数据或输入,则 100% 的时间使用准备好的语句


你似乎有点困惑。首先,请不要使用mysql_*;这些函数已过时、已弃用且不安全。请改用 MySQLiPDO。其次,它与准备好的语句无关,无论如何都不是 PDO 功能。在运行查询之前准备语句,而不是在运行查询时准备行数。mysql_*mysql_num_rows

至于何时准备发言,@Mike'Pomax'Kamermans在评论中指出了这一点。如果您曾经使用过任何数据,即使是一次,使用用户(甚至是据称受信任的用户)曾经接触过的任何数据,或者由任何类型的第三方或第三方应用程序(包括浏览器)生成的数据,请使用准备好的语句。只有当你的数据100%是硬编码的,你才能信任它。

例如,您不能信任:

  • 用户名
  • 密码
  • 电子邮件地址
  • 用户评论
  • 电话号码
  • 日期
  • 搜索字符串
  • 浏览器客户端字符串
  • 信用卡号码
  • 上传的文件名
  • 以及由用户创建或用户可以操作的任何其他类型的输入。

当然,在将它们放入数据库之前,您应该验证所有这些(例如,检查电子邮件地址是否确实是电子邮件地址)。但即便如此,使用准备好的语句也是安全的方法。

评论

0赞 G.SINGH 7/29/2014
我明白你在说什么。我之所以提到我正在使用mysql_num_rows,是因为我看到这个 stackoverflow.com/questions/11305230/......这似乎有点毫无意义。如果没有处理用户数据,那么mysql_num_rows应该没问题,对吧?
1赞 elixenide 7/29/2014
是的;只要您确定没有用户数据(例如 ),就不必使用预准备语句。不过,就我个人而言,我建议始终使用准备好的语句;当没有用户数据的旧查询被更新并且程序员忘记为新查询使用准备好的语句时,就会引入许多安全漏洞。这只是我的看法。SELECT COUNT(*) FROM mytable
47赞 Darren 7/28/2014 #3

TL/DR

总是。100%的时间,使用它。总是;即使你不需要使用它。仍然使用它。


mysql_*函数已失效,因此您必须使用 PDOMySQLi。使用预准备语句时,这 2 个中的任何一个都足以作为兼容库。

直接在 SQL 查询中添加变量就像把你的车停在一个糟糕的街区,解锁并按下点火开关的钥匙。你基本上是在说,进来拿走我的好东西enter image description here

永远不应该,我的意思是永远不要,把一个数据变量放到SQL中。 除非你想要这个:

SQL Injection

您还应该避免任何模糊的定义,例如“用户输入”。我们不能严格定义什么是“用户输入”。更不用说任何输入都可能包含会破坏您的查询的愚蠢引用。重要的不是数据来自哪里,而是数据的来源:任何数据都将在 SQL 中使用?通过准备好的语句传递它

现在来看看它是如何工作的。准备好的语句的工作方式很简单,它将查询数据一起发送,但分开(如果这有意义的哈哈) - 我的意思是:

Prepared Statements
Query: SELECT foo FROM bar WHERE foo = ?
Data:  [? = 'a value here']

与其前身相比,您在查询中填充了数据,将其作为一个整体发送 - 反过来,这意味着它是作为单个事务执行的 - 导致 SQL 注入漏洞。

鉴于您需要在查询中使用以下变量,

$name = 'one';
$value = 1;

对于 PDO,可以使用如下代码

$stmt = $dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (?,?)");
$stmt->execute([$name, $value]);

对于 mysqli,

$dbh->execute_query("INSERT INTO REGISTRY (name, value) VALUES (?,?)", [$name, $value]);

更多阅读

评论

0赞 BRoebie 12/3/2015
@Darren我想知道我是否做对了。是否有必要在只有固定值的情况下使用准备好的语句?例如,某人需要批准或拒绝请求,而该人获得的唯一输入是选择 2 个按钮:批准和拒绝,每个按钮都写入不同的值,这是固定的。我需要在那里使用准备好的声明吗?注意:该页面不允许用户输入任何文本。
0赞 Darren 12/4/2015
@BRoebie 安全总比后悔好。用户可以与传递的数据(即表单)交互的任何地方本质上都应该是准备语句。如果您从输入中获取上述值,而这些值很容易从元素检查/等中修改,那么您可能会遇到很多麻烦。
0赞 BRoebie 12/4/2015
@Darren我同意。我想知道如果用户只能与页面上的 2 个按钮交互,我是否需要使用准备好的语句,一个按钮会在数据库中写入请求批准,另一个按钮会在数据库中写入请求被拒绝。(是的,这是我所说的一种形式,但那里的所有字段都是无法修改的只读字段,只有我上面提到的 2 个按钮,代码中具有发送到数据库的固定值)
0赞 user236800 1/2/2021
我知道这已经很晚了(很痛苦),但我只想提一下,在谈论“用户输入”时,这意味着发送到您的页面/应用程序的请求,而不是专门的网络表单。例如,用户可以直接向您的应用程序发出 HTTP 请求,而无需实际请求您网站上的索引(或任何其他)页面。请了解服务器和客户端之间的通信是如何工作的,因为这是关键的 😉
1赞 Sri 7/28/2014 #4

对此有两种解决方案——

01- 使用准备好的语句

为了防止 SQL 注入,我们将不得不使用一种称为预处理语句的东西,它使用绑定参数。预准备语句不会将变量与 SQL 字符串组合在一起,因此攻击者无法修改 SQL 语句。预准备语句将变量与编译的 SQL 语句组合在一起,这意味着 SQL 和变量是分开发送的,变量只是被解释为字符串,而不是 SQL 语句的一部分。

02-使用mySQLi准备语句。

使用以下步骤中的方法,您将不需要使用任何其他 SQL 注入过滤技术,例如 mysql_real_escape_string()。这是因为使用预准备语句无法进行常规的 SQL 注入。

例如 -

$name = $_GET['username'];

if ($stmt = $mysqli->prepare("SELECT password FROM tbl_users WHERE name=?")) {

    // Bind a variable to the parameter as a string. 
    $stmt->bind_param("s", $name);

    // Execute the statement.
    $stmt->execute();

    // Get the variables from the query.
    $stmt->bind_result($pass);

    // Fetch the data.
    $stmt->fetch();

    // Display the data.
    printf("Password for user %s is %s\n", $name, $pass);

    // Close the prepared statement.
    $stmt->close();

}

您可以找到有关此表格的更多信息 - http://www.wikihow.com/Prevent-SQL-Injection-in-PHP

-1赞 Manngo 11/13/2023 #5

使用真正准备好的语句意味着在添加任何数据之前将对 SQL 进行解释。

使用预准备语句有两个原因:

  • 安全性:如果语句已经被解释过,那么数据被误解为 SQL 为时已晚。误解 SQL 数据可能会导致 SQL 注入攻击。
  • 效率:如果你有一个语句,你需要用不同的数据多次运行,那么简单地将新数据注入到同一个SQL语句中并重新解释是低效的。如果先准备语句,然后使用不同的数据运行准备好的语句,则更有意义。

关于SQL注入,如果数据来自外部,例如来自表单,则应始终不信任数据。这不一定是您担心的攻击。键入错误或用作撇号的单引号可能会导致 SQL 错误。

但是,仅当数据为字符串时,才会发生 SQL 注入。如果通过诸如 之类的函数运行数据,则会清除任何可能被误解的字符串数据,因此在毫无准备的情况下使用是安全的。intval()

如果你的SQL纯粹来自你自己的代码,那么就没有必要变得偏执。如果你想破坏自己的数据库,有比形成SQL注入攻击更简单的方法。

但是,不要忘记效率方面:如果您多次运行相同的语句,那么,无论数据源如何,准备好的语句都可能更有效率。

还有一点。准备好的语句并不总是由数据库软件真正准备的。PHP 能够伪造准备好的语句。

使用 PDO 时,我总是建议以下几点:

$pdo = new PDO(…);
$pdo -> setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

特别是对于MySQL,这会导致PDO不会伪造准备好的语句。

评论

0赞 Your Common Sense 11/13/2023
这个答案弊大于利。臭名昭著的“如果它来自外部”——鉴于不可能严格定义“外部”——使这个建议变得开放。因此,与公认的答案不同,您的答案基本上是“有时,我无法确切地告诉你何时”。雪上加霜的是,你添加了一个未调用的“然而”,建议在没有准备好的情况下运行一些查询,这只会让读者感到困惑,为什么任何人应该在使用更简单且更不容易出错时使用不同的方法。
0赞 Manngo 11/13/2023
@YourCommonSense乞求不同。 无害且方便。所以是.所以是.PDO 具有 和 方法是有原因的。正如我所提到的,来自表单的数据可以定义为“外部”。您自己的代码中的数据并非如此。我不清楚你为什么不把类似的评论与其他答案和评论联系起来,这些答案和评论也有区别。无论如何,感谢您的评论。$rows = $pdo -> query('SELECT count(*) FROM data') -> fetchColumn();foreach($pdo -> query('SELECT id, name FROM data') AS [$id,$name]) … $pdo -> exec('TRUNCATE TABLE data');exec()query()
0赞 Your Common Sense 11/13/2023
当查询中没有使用任何数据时,您可以自由使用 exec() 或 query()。在添加你的“外部”时,你是在呼唤灾难。因为你无法定义它。至少你的读者可以。从本质上讲,你的回答是“任何不是来自表单的数据都是安全的,不要使用准备好的语句”。我很欣赏你的善意,但你必须注意你的措辞。它会造成巨大的伤害。确实如此。
0赞 Manngo 11/13/2023
@YourCommonSense 对不起,像这样的东西有什么问题?哪句话让你如此焦虑?我建议使用准备好的语句。$id = intval($_POST['id']); $pdo -> query("SELECT * FROM data WHERE id=$id");
0赞 Your Common Sense 11/13/2023
这有很多错误。而且已经解释过太多次了。此外,它与您使用准备好的语句的建议相矛盾。你应该下定决心。