PDO 准备好的语句足以防止 SQL 注入吗?

发布于 2024-07-06 20:33:10 字数 459 浏览 10 评论 0原文

假设我有这样的代码:

$dbh = new PDO("blahblah");

$stmt = $dbh->prepare('SELECT * FROM users where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );

PDO 文档说:

准备好的语句的参数不需要加引号; 司机会为您处理。

这真的是我需要做的一切来避免 SQL 注入吗? 真的那么容易吗?

如果 MySQL 有影响的话,你可以假设它。 另外,我真的只是对使用准备好的语句来对抗 SQL 注入感到好奇。 在这种情况下,我不关心 XSS 或其他可能的漏洞。

Let's say I have code like this:

$dbh = new PDO("blahblah");

$stmt = $dbh->prepare('SELECT * FROM users where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );

The PDO documentation says:

The parameters to prepared statements don't need to be quoted; the driver handles it for you.

Is that truly all I need to do to avoid SQL injections? Is it really that easy?

You can assume MySQL if it makes a difference. Also, I'm really only curious about the use of prepared statements against SQL injection. In this context, I don't care about XSS or other possible vulnerabilities.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

写下不归期 2024-07-13 20:33:10

简短的回答是,如果使用得当,PDO 准备是足够安全的。


我正在改编 这个答案来讨论 PDO...

长答案并不那么容易。 它基于此处演示的攻击。

攻击

那么,让我们首先展示攻击......

$pdo->query('SET NAMES gbk');
$var = "\xbf\x27 OR 1=1 /*";
$query = 'SELECT * FROM test WHERE name = ? LIMIT 1';
$stmt = $pdo->prepare($query);
$stmt->execute(array($var));

在某些情况下,这将返回超过 1 行。 让我们剖析一下这里发生的事情:

  1. 选择字符集

    $pdo->query('设置名称 gbk'); 
      

    要使此攻击发挥作用,我们需要服务器期望连接上的编码将 ' 编码为 ASCII,即 0x27 得到一些最终字节为 ASCII \ 的字符,即 0x5c。 事实证明,MySQL 5.6 默认支持 5 种这样的编码:big5cp932gb2312gbk和<代码>sjis。 我们将在这里选择gbk

    现在,请注意此处 SET NAMES 的使用,这一点非常重要。 这将字符集设置在服务器上。 还有另一种方法可以做到这一点,但我们很快就会实现。

  2. 有效负载

    我们将用于此注入的有效负载以字节序列 0xbf27 开头。 在 gbk 中,这是一个无效的多字节字符; 在 latin1 中,它是字符串 ¿'。 请注意,在 latin1 gbk 中,0x27 本身就是一个文字 '性格。

    我们选择此有效负载是因为,如果我们对其调用 addslashes(),我们将插入一个 ASCII \0x5c,在 ' 字符之前。 因此,我们最终会得到 0xbf5c27,它在 gbk 中是一个两个字符序列:0xbf5c 后跟 0x27 。 或者换句话说,一个有效字符后跟一个未转义的'。 但我们没有使用 addslashes()。 那么继续下一步...

  3. $stmt->execute()

    这里要认识到的重要一点是,PDO 默认情况下执行真正的准备好的语句。 它模拟它们(对于 MySQL)。 因此,PDO 在内部构建查询字符串,对每个绑定字符串值调用 mysql_real_escape_string() (MySQL C API 函数)。

    mysql_real_escape_string() 的 C API 调用与 addslashes() 的不同之处在于它知道连接字符集。 因此它可以对服务器期望的字符集正确执行转义。 然而,到目前为止,客户端认为我们仍在使用 latin1 进行连接,因为我们从未告诉过它。 我们确实告诉服务器我们正在使用gbk,但客户端仍然认为它是latin1

    因此,对 mysql_real_escape_string() 的调用会插入反斜杠,并且我们的“转义”内容中有一个自由悬挂的 ' 字符! 事实上,如果我们查看 gbk 字符集中的 $var,我们会看到:

    缞' OR 1=1 /*

    这正是攻击所需要的。

  4. 查询

    这部分只是形式,但这是呈现的查询:

    SELECT * FROM test WHERE name = '缞' OR 1=1 /*' LIMIT 1 
      

恭喜,您刚刚使用 PDO 准备语句成功攻击了一个程序...

简单的修复

现在,值得注意的是您可以防止这种情况发生通过禁用模拟准备语句:

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

这通常会产生真正的准备语句(即数据在与查询不同的数据包中发送)。 但是,请注意,PDO 会默默后备 模拟 MySQL 本身无法准备的语句:它可以的语句是 手册中列出,但要注意选择合适的服务器版本)。

正确的修复

这里的问题是我们使用了SET NAMES而不是C API的mysql_set_charset()。 否则的话,攻击就不会成功。 但最糟糕的是,PDO 直到 5.3.6 才公开 mysql_set_charset() 的 C API,因此在之前的版本中,它无法针对每个可能的命令阻止这种攻击!
它现在公开为 DSN 参数,应该是使用而不是 SET NAMES...

前提是我们使用 2006 年以来的 MySQL 版本。如果您使用的是早期的 MySQL 版本,则 mysql_real_escape_string() 中的“http://bugs.mysql.com/bug.php?id=8378” rel="noreferrer">bug 意味着无效的多字节字符,例如我们的有效负载被视为用于转义目的的单个字节即使客户端已被正确告知连接编码,因此这种攻击仍然会成功。 该错误已在 MySQL 4.1.20,5.0.225.1.11

正如

我们在一开始所说的,要使这种攻击起作用,数据库连接必须使用易受攻击的字符集进行编码。 utf8mb4 不是容易受到攻击,但可以支持每个 Unicode 字符:因此您可以选择使用它,但它从 MySQL 5.5.3 起才可用。 另一种选择是 utf8,也不易受攻击,并且可以支持整个 Unicode Basic多语言飞机

或者,您可以启用 NO_BACKSLASH_ESCAPES SQL 模式,它(除其他外)改变 mysql_real_escape_string() 的操作。 启用此模式后,0x27 将被替换为 0x2727 而不是 0x5c27,因此转义进程无法创建有效的以前不存在的任何易受攻击的编码中的字符(即 0xbf27 仍然是 0xbf27 等),因此服务器仍会拒绝该字符串,因为该字符串无效。 但是,请参阅 @eggyal 的回答,了解使用此 SQL 模式(尽管不是使用 PDO)可能产生的不同漏洞。

安全示例

以下示例是安全的:

mysql_query('SET NAMES utf8');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

因为服务器期望 utf8...

mysql_set_charset('gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

因为我们已经正确设置了字符集,以便客户端和服务器匹配。

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

因为我们已经关闭了模拟准备语句。

$pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=gbk', $user, $password);
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

因为我们已经正确设置了字符集。

$mysqli->query('SET NAMES gbk');
$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "\xbf\x27 OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();

因为 MySQLi 始终执行真正的准备好的语句。

总结

如果您:

  • 使用现代版本的 MySQL(5.1 晚期、所有 5.5、5.6 等) PDO 的 DSN 字符集参数(在 PHP ≥ 5.3.6 中)

OR

  • Don不使用易受攻击的字符集进行连接编码(仅使用 utf8 / latin1 / ascii / 等)

OR

  • 启用 NO_BACKSLASH_ESCAPES SQL 模式

您 100% 安全。

否则,即使您正在使用 PDO 准备语句,您也很容易受到攻击...

附录

我一直在慢慢地开发一个补丁,以将默认值更改为不模拟为未来版本的 PHP 做准备。 我遇到的问题是,当我这样做时,很多测试都会中断。 一个问题是模拟的准备只会在执行时抛出语法错误,但真正的准备会在准备时抛出错误。 因此这可能会导致问题(这也是测试失败的部分原因)。

The short answer is YES, PDO prepares are secure enough if used properly.


I'm adapting this answer to talk about PDO...

The long answer isn't so easy. It's based off an attack demonstrated here.

The Attack

So, let's start off by showing the attack...

$pdo->query('SET NAMES gbk');
$var = "\xbf\x27 OR 1=1 /*";
$query = 'SELECT * FROM test WHERE name = ? LIMIT 1';
$stmt = $pdo->prepare($query);
$stmt->execute(array($var));

In certain circumstances, that will return more than 1 row. Let's dissect what's going on here:

  1. Selecting a Character Set

    $pdo->query('SET NAMES gbk');
    

    For this attack to work, we need the encoding that the server's expecting on the connection both to encode ' as in ASCII i.e. 0x27 and to have some character whose final byte is an ASCII \ i.e. 0x5c. As it turns out, there are 5 such encodings supported in MySQL 5.6 by default: big5, cp932, gb2312, gbk and sjis. We'll select gbk here.

    Now, it's very important to note the use of SET NAMES here. This sets the character set ON THE SERVER. There is another way of doing it, but we'll get there soon enough.

  2. The Payload

    The payload we're going to use for this injection starts with the byte sequence 0xbf27. In gbk, that's an invalid multibyte character; in latin1, it's the string ¿'. Note that in latin1 and gbk, 0x27 on its own is a literal ' character.

    We have chosen this payload because, if we called addslashes() on it, we'd insert an ASCII \ i.e. 0x5c, before the ' character. So we'd wind up with 0xbf5c27, which in gbk is a two character sequence: 0xbf5c followed by 0x27. Or in other words, a valid character followed by an unescaped '. But we're not using addslashes(). So on to the next step...

  3. $stmt->execute()

    The important thing to realize here is that PDO by default does NOT do true prepared statements. It emulates them (for MySQL). Therefore, PDO internally builds the query string, calling mysql_real_escape_string() (the MySQL C API function) on each bound string value.

    The C API call to mysql_real_escape_string() differs from addslashes() in that it knows the connection character set. So it can perform the escaping properly for the character set that the server is expecting. However, up to this point, the client thinks that we're still using latin1 for the connection, because we never told it otherwise. We did tell the server we're using gbk, but the client still thinks it's latin1.

    Therefore the call to mysql_real_escape_string() inserts the backslash, and we have a free hanging ' character in our "escaped" content! In fact, if we were to look at $var in the gbk character set, we'd see:

    縗' OR 1=1 /*

    Which is exactly what the attack requires.

  4. The Query

    This part is just a formality, but here's the rendered query:

    SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1
    

Congratulations, you just successfully attacked a program using PDO Prepared Statements...

The Simple Fix

Now, it's worth noting that you can prevent this by disabling emulated prepared statements:

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

This will usually result in a true prepared statement (i.e. the data being sent over in a separate packet from the query). However, be aware that PDO will silently fallback to emulating statements that MySQL can't prepare natively: those that it can are listed in the manual, but beware to select the appropriate server version).

The Correct Fix

The problem here is that we used SET NAMES instead of C API's mysql_set_charset(). Otherwise, the attack would not succeed. But the worst part is that PDO didn't expose the C API for mysql_set_charset() until 5.3.6, so in prior versions it cannot prevent this attack for every possible command!
It's now exposed as a DSN parameter, which should be used instead of SET NAMES...

This is provided we are using a MySQL release since 2006. If you're using an earlier MySQL release, then a bug in mysql_real_escape_string() meant that invalid multibyte characters such as those in our payload were treated as single bytes for escaping purposes even if the client had been correctly informed of the connection encoding and so this attack would still succeed. The bug was fixed in MySQL 4.1.20, 5.0.22 and 5.1.11.

The Saving Grace

As we said at the outset, for this attack to work the database connection must be encoded using a vulnerable character set. utf8mb4 is not vulnerable and yet can support every Unicode character: so you could elect to use that instead—but it has only been available since MySQL 5.5.3. An alternative is utf8, which is also not vulnerable and can support the whole of the Unicode Basic Multilingual Plane.

Alternatively, you can enable the NO_BACKSLASH_ESCAPES SQL mode, which (amongst other things) alters the operation of mysql_real_escape_string(). With this mode enabled, 0x27 will be replaced with 0x2727 rather than 0x5c27 and thus the escaping process cannot create valid characters in any of the vulnerable encodings where they did not exist previously (i.e. 0xbf27 is still 0xbf27 etc.)—so the server will still reject the string as invalid. However, see @eggyal's answer for a different vulnerability that can arise from using this SQL mode (albeit not with PDO).

Safe Examples

The following examples are safe:

mysql_query('SET NAMES utf8');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

Because the server's expecting utf8...

mysql_set_charset('gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

Because we've properly set the character set so the client and the server match.

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Because we've turned off emulated prepared statements.

$pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=gbk', $user, $password);
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Because we've set the character set properly.

$mysqli->query('SET NAMES gbk');
$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "\xbf\x27 OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();

Because MySQLi does true prepared statements all the time.

Wrapping Up

If you:

  • Use Modern Versions of MySQL (late 5.1, all 5.5, 5.6, etc) AND PDO's DSN charset parameter (in PHP ≥ 5.3.6)

OR

  • Don't use a vulnerable character set for connection encoding (you only use utf8 / latin1 / ascii / etc)

OR

  • Enable NO_BACKSLASH_ESCAPES SQL mode

You're 100% safe.

Otherwise, you're vulnerable even though you're using PDO Prepared Statements...

Addendum

I've been slowly working on a patch to change the default to not emulate prepares for a future version of PHP. The problem that I'm running into is that a LOT of tests break when I do that. One problem is that emulated prepares will only throw syntax errors on execute, but true prepares will throw errors on prepare. So that can cause issues (and is part of the reason tests are borking).

浅唱ヾ落雨殇 2024-07-13 20:33:10

准备好的语句/参数化查询足以防止 SQL 注入,但仅限于应用程序中的每个查询始终使用时。

如果您在应用程序的其他任何地方使用未经检查的动态 SQL,它仍然容易受到二阶注入的攻击。

二阶注入意味着数据在包含在查询中之前已在数据库中循环一次,并且更难实现。 AFAIK,您几乎从未见过真正设计的二阶攻击,因为攻击者通常更容易通过社交工程进入,但有时会因为额外的良性 ' 字符或而出现二阶错误相似的。

当您可以将一个值存储在数据库中并稍后在查询中用作文字时,您就可以完成二阶注入攻击。 举个例子,假设您在网站上创建帐户时输入以下信息作为新用户名(假设此问题使用 MySQL 数据库):

' + (SELECT UserName + '_' + Password FROM Users LIMIT 1) + '

如果用户名没有其他限制,准备好的语句仍将确保上面的嵌入查询在插入时不会执行,并将值正确存储在数据库中。 但是,想象一下,稍后应用程序从数据库中检索您的用户名,并使用字符串连接将该值包含在新查询中。 您可能会看到其他人的密码。 由于用户表中的前几个名称往往是管理员,因此您可能也刚刚放弃了农场。 (另请注意:这是不以纯文本形式存储密码的又一个原因!)

然后,我们看到,如果预准备语句仅用于单个查询,而忽略其他一些查询,则它们不如果您遵循良好的应用程序设计原则,以便将数据访问与程序的其余部分分开,则可以轻松执行或审核每个查询是否正确使用参数化。 在这种情况下,sql注入(一阶和二阶)都被完全阻止。


*事实证明,MySql/PHP(很久很久以前)在涉及宽字符时处理参数方面很愚蠢,并且有一种罕见情况这里的其他高票答案中概述了它可以允许注入通过参数化查询。

Prepared statements / parameterized queries are sufficient to prevent SQL injections, but only when used all the time, for the every query in the application.

If you use un-checked dynamic SQL anywhere else in an application it is still vulnerable to 2nd order injection.

2nd order injection means data has been cycled through the database once before being included in a query, and is much harder to pull off. AFAIK, you almost never see real engineered 2nd order attacks, as it is usually easier for attackers to social-engineer their way in, but you sometimes have 2nd order bugs crop up because of extra benign ' characters or similar.

You can accomplish a 2nd order injection attack when you can cause a value to be stored in a database that is later used as a literal in a query. As an example, let's say you enter the following information as your new username when creating an account on a web site (assuming MySQL DB for this question):

' + (SELECT UserName + '_' + Password FROM Users LIMIT 1) + '

If there are no other restrictions on the username, a prepared statement would still make sure that the above embedded query doesn't execute at the time of insert, and store the value correctly in the database. However, imagine that later the application retrieves your username from the database, and uses string concatenation to include that value a new query. You might get to see someone else's password. Since the first few names in users table tend to be admins, you may have also just given away the farm. (Also note: this is one more reason not to store passwords in plain text!)

We see, then, that if prepared statements are only used for a single query, but neglected for some other queries, they are not sufficient to protect against sql injection attacks throughout an entire application, because they lack a mechanism to enforce all access to a database within an application uses safe code. However, used as part of good application design — which may include practices such as code review or static analysis, or use of an ORM, data layer, or service layer that limits dynamic sql — prepared statements are the primary tool for solving the Sql Injection problem. If you follow good application design principles, such that your data access is separated from the rest of your program, it becomes easy to enforce or audit that every query correctly uses parameterization. In this case, sql injection (both first and second order) is completely prevented.


*It turns out that MySql/PHP were (long, long time ago) just dumb about handling parameters when wide characters are involved, and there was a rare case outlined in the other highly-voted answer here that can allow injection to slip through a parameterized query.

孤单情人 2024-07-13 20:33:10

不,他们并不总是这样。

这取决于您是否允许将用户输入放置在查询本身中。 例如:

$dbh = new PDO("blahblah");

$tableToUse = $_GET['userTable'];

$stmt = $dbh->prepare('SELECT * FROM ' . $tableToUse . ' where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );

容易受到 SQL 注入攻击,并且在此示例中使用准备好的语句将不起作用,因为用户输入用作标识符,而不是数据。 这里正确的答案是使用某种过滤/验证,例如:

$dbh = new PDO("blahblah");

$tableToUse = $_GET['userTable'];
$allowedTables = array('users','admins','moderators');
if (!in_array($tableToUse,$allowedTables))    
 $tableToUse = 'users';

$stmt = $dbh->prepare('SELECT * FROM ' . $tableToUse . ' where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );

注意:您不能使用 PDO 绑定 DDL(数据定义语言)之外的数据,即这不起作用:

$stmt = $dbh->prepare('SELECT * FROM foo ORDER BY :userSuppliedData');

上述方法的原因不起作用是因为 DESCASC 不是数据。 PDO 只能转义数据。 其次,你甚至不能在它周围加上 ' 引号。 允许用户选择排序的唯一方法是手动过滤并检查它是 DESCASC

No, they are not always.

It depends on whether you allow user input to be placed within the query itself. For example:

$dbh = new PDO("blahblah");

$tableToUse = $_GET['userTable'];

$stmt = $dbh->prepare('SELECT * FROM ' . $tableToUse . ' where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );

would be vulnerable to SQL injections and using prepared statements in this example won't work, because the user input is used as an identifier, not as data. The right answer here would be to use some sort of filtering/validation like:

$dbh = new PDO("blahblah");

$tableToUse = $_GET['userTable'];
$allowedTables = array('users','admins','moderators');
if (!in_array($tableToUse,$allowedTables))    
 $tableToUse = 'users';

$stmt = $dbh->prepare('SELECT * FROM ' . $tableToUse . ' where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );

Note: you can't use PDO to bind data that goes outside of DDL (Data Definition Language), i.e. this does not work:

$stmt = $dbh->prepare('SELECT * FROM foo ORDER BY :userSuppliedData');

The reason why the above does not work is because DESC and ASC are not data. PDO can only escape for data. Secondly, you can't even put ' quotes around it. The only way to allow user chosen sorting is to manually filter and check that it's either DESC or ASC.

初见终念 2024-07-13 20:33:10

不,这还不够(在某些特定情况下)! 默认情况下,当使用 MySQL 作为数据库驱动程序时,PDO 使用模拟的准备语句。 使用 MySQL 和 PDO 时,您应该始终禁用模拟的准备好的语句:

$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

始终应该做的另一件事是设置数据库的正确编码:

$dbh = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass');

另请参阅此相关问题:如何防止 PHP 中的 SQL 注入?

请注意,这只能保护您免受 SQL 注入,但您的应用程序仍然可能容易受到其他类型的攻击。 例如,您可以通过使用正确的编码和引用样式再次使用 htmlspecialchars() 来防止 XSS。

No this is not enough (in some specific cases)! By default PDO uses emulated prepared statements when using MySQL as a database driver. You should always disable emulated prepared statements when using MySQL and PDO:

$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

Another thing that always should be done it set the correct encoding of the database:

$dbh = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass');

Also see this related question: How can I prevent SQL injection in PHP?

Note that this will only protect you against SQL injection, but your application could still be vulnerable to other kinds of attacks. E.g. you can protect against XSS by using htmlspecialchars() again with the correct encoding and quoting style.

蓝眼泪 2024-07-13 20:33:10

是的,这就足够了。 注入类型攻击的工作方式是通过某种方式让解释器(数据库)来评估应该是数据的东西,就好像它是代码一样。 仅当您在同一介质中混合代码和数据时(例如,当您将查询构造为字符串时),这才有可能。

参数化查询通过分别发送代码和数据来工作,因此永远不可能在其中找到漏洞。

不过,您仍然可能容易受到其他注入类型的攻击。 例如,如果您在 HTML 页面中使用数据,您可能会受到 XSS 类型的攻击。

Yes, it is sufficient. The way injection type attacks work, is by somehow getting an interpreter (The database) to evaluate something, that should have been data, as if it was code. This is only possible if you mix code and data in the same medium (Eg. when you construct a query as a string).

Parameterised queries work by sending the code and the data separately, so it would never be possible to find a hole in that.

You can still be vulnerable to other injection-type attacks though. For example, if you use the data in a HTML-page, you could be subject to XSS type attacks.

微凉徒眸意 2024-07-13 20:33:10

就我个人而言,我总是首先对数据进行某种形式的清理,因为您永远不能信任用户输入,但是当使用占位符/参数绑定时,输入的数据将单独发送到服务器的 sql 语句,然后绑定在一起。 这里的关键是,这将提供的数据绑定到特定类型和特定用途,并消除了更改 SQL 语句逻辑的任何机会。

Personally I would always run some form of sanitation on the data first as you can never trust user input, however when using placeholders / parameter binding the inputted data is sent to the server separately to the sql statement and then binded together. The key here is that this binds the provided data to a specific type and a specific use and eliminates any opportunity to change the logic of the SQL statement.

梦旅人picnic 2024-07-13 20:33:10

即使您要使用 html 或 js 检查来防止前端 sql 注入,您也必须考虑前端检查是“可绕过的”。

您可以禁用js或使用前端开发工具(现在由firefox或chrome内置)编辑模式。

因此,为了防止 SQL 注入,清理控制器内的输入日期后端是正确的。

我建议您使用 filter_input() 本机 PHP 函数来清理 GET 和 INPUT 值。

如果您想继续安全性,为了合理的数据库查询,我想建议您使用正则表达式来验证数据格式。
preg_match() 在这种情况下会帮助你!
但要小心! 正则表达式引擎并不那么轻。 仅在必要时使用它,否则您的应用程序性能将会降低。

安全是有成本的,但不要浪费你的性能!

简单的例子:

如果你想仔细检查从 GET 收到的值是否是一个数字,小于 99
if(!preg_match('/[0-9]{1,2}/')){...}
所以

if (isset($value) && intval($value)) <99) {...}

,最终的答案是:“不!PDO 准备语句不能阻止所有类型的 sql 注入”; 它不会阻止意外的值,只是阻止意外的串联

Eaven if you are going to prevent sql injection front-end, using html or js checks, you'd have to consider that front-end checks are "bypassable".

You can disable js or edit a pattern with a front-end development tool (built in with firefox or chrome nowadays).

So, in order to prevent SQL injection, would be right to sanitize input date backend inside your controller.

I would like to suggest to you to use filter_input() native PHP function in order to sanitize GET and INPUT values.

If you want to go ahead with security, for sensible database queries, I'd like to suggest to you to use regular expression to validate data format.
preg_match() will help you in this case!
But take care! Regex engine is not so light. Use it only if necessary, otherwise your application performances will decrease.

Security has a costs, but do not waste your performance!

Easy example:

if you want to double check if a value, received from GET is a number, less then 99
if(!preg_match('/[0-9]{1,2}/')){...}
is heavyer of

if (isset($value) && intval($value)) <99) {...}

So, the final answer is: "No! PDO Prepared Statements does not prevent all kind of sql injection"; It does not prevent unexpected values, just unexpected concatenation

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文