简体   繁体   English

如何防止PHP中的SQL注入?

[英]How can I prevent SQL injection in PHP?

If user input is inserted without modification into an SQL query, then the application becomes vulnerable to SQL injection , like in the following example:如果用户输入未经修改就插入到 SQL 查询中,那么应用程序将容易受到SQL 注入的攻击,如下例所示:

$unsafe_variable = $_POST['user_input']; 

mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");

That's because the user can input something like value'); DROP TABLE table;--那是因为用户可以输入类似value'); DROP TABLE table;-- value'); DROP TABLE table;-- , and the query becomes: value'); DROP TABLE table;-- ,查询变为:

INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')

What can be done to prevent this from happening?可以做些什么来防止这种情况发生?

The correct way to avoid SQL injection attacks, no matter which database you use, is to separate the data from SQL , so that data stays data and will never be interpreted as commands by the SQL parser.无论您使用哪个数据库,避免 SQL 注入攻击的正确方法是将数据从 SQL 中分离出来,以便数据保持数据,并且永远不会被 Z9778840A0740CB30C9828B76 解析器解释为命令。 It is possible to create an SQL statement with correctly formatted data parts, but if you don't fully understand the details, you should always use prepared statements and parameterized queries.可以使用格式正确的数据部分创建 SQL 语句,但如果您不完全了解详细信息,则应始终使用准备好的语句和参数化查询。 These are SQL statements that are sent to and parsed by the database server separately from any parameters.这些是 SQL 语句,它们与任何参数分开发送到数据库服务器并由其解析。 This way it is impossible for an attacker to inject malicious SQL.这样攻击者就不可能注入恶意 SQL。

You basically have two options to achieve this:你基本上有两种选择来实现这一点:

  1. Using PDO (for any supported database driver):使用PDO (适用于任何支持的数据库驱动程序):

     $stmt = $pdo->prepare('SELECT * FROM employees WHERE name =:name'); $stmt->execute([ 'name' => $name ]); foreach ($stmt as $row) { // Do something with $row }
  2. Using MySQLi (for MySQL):使用MySQLi (用于 MySQL):

     $stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name =?'); $stmt->bind_param('s', $name); // 's' specifies the variable type => 'string' $stmt->execute(); $result = $stmt->get_result(); while ($row = $result->fetch_assoc()) { // Do something with $row }

If you're connecting to a database other than MySQL, there is a driver-specific second option that you can refer to (for example, pg_prepare() and pg_execute() for PostgreSQL).如果您要连接到 MySQL 以外的数据库,则可以参考驱动程序特定的第二个选项(例如,PostgreSQL 的pg_prepare()pg_execute() )。 PDO is the universal option. PDO 是通用选项。


Correctly setting up the connection正确设置连接

PDO PDO

Note that when using PDO to access a MySQL database real prepared statements are not used by default .注意,当使用PDO访问 MySQL 数据库时,默认情况下不使用真实的prepared statements。 To fix this you have to disable the emulation of prepared statements.要解决此问题,您必须禁用准备好的语句的模拟。 An example of creating a connection using PDO is:使用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);

In the above example, the error mode isn't strictly necessary, but it is advised to add it .在上面的例子中,错误模式不是绝对必要的,但建议添加它 This way PDO will inform you of all MySQL errors by means of throwing the PDOException .这样 PDO 将通过抛出PDOException通知您所有 MySQL 错误。

What is mandatory , however, is the first setAttribute() line, which tells PDO to disable emulated prepared statements and use real prepared statements.然而,强制性的是第一个setAttribute()行,它告诉 PDO 禁用模拟的准备好的语句并使用真正的准备好的语句。 This makes sure the statement and the values aren't parsed by PHP before sending it to the MySQL server (giving a possible attacker no chance to inject malicious SQL).这确保了语句和值在发送到 MySQL 服务器之前不会被 PHP 解析(使可能的攻击者没有机会注入恶意 SQL)。

Although you can set the charset in the options of the constructor, it's important to note that 'older' versions of PHP (before 5.3.6) silently ignored the charset parameter in the DSN.尽管您可以在构造函数的选项中设置charset ,但重要的是要注意 PHP(5.3.6 之前)的“旧”版本会默默地忽略 DSN 中的字符集参数

Mysqli Mysqli

For mysqli we have to follow the same routine:对于 mysqli,我们必须遵循相同的例程:

 mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); // error reporting $dbConnection = new mysqli('127.0.0.1', 'username', 'password', 'test'); $dbConnection->set_charset('utf8mb4'); // charset

Explanation解释

The SQL statement you pass to prepare is parsed and compiled by the database server.您传递给prepare的 SQL 语句由数据库服务器解析和编译。 By specifying parameters (either a ? or a named parameter like :name in the example above) you tell the database engine where you want to filter on.通过指定参数(在上面的示例中可以是?或命名参数,如:name ),您可以告诉数据库引擎您要过滤的位置。 Then when you call execute , the prepared statement is combined with the parameter values you specify.然后,当您调用execute时,准备好的语句将与您指定的参数值相结合。

The important thing here is that the parameter values are combined with the compiled statement, not an SQL string.这里重要的是参数值与编译语句结合,而不是 SQL string。 SQL injection works by tricking the script into including malicious strings when it creates SQL to send to the database. SQL 注入通过欺骗脚本在创建 SQL 以发送到数据库时包含恶意字符串来工作。 So by sending the actual SQL separately from the parameters, you limit the risk of ending up with something you didn't intend.因此,通过将实际的 SQL 与参数分开发送,您可以限制以您不想要的东西结束的风险。

Any parameters you send when using a prepared statement will just be treated as strings (although the database engine may do some optimization so parameters may end up as numbers too, of course).您在使用准备好的语句时发送的任何参数都将被视为字符串(尽管数据库引擎可能会进行一些优化,因此参数也可能最终以数字形式结束)。 In the example above, if the $name variable contains 'Sarah'; DELETE FROM employees在上面的例子中,如果$name变量包含'Sarah'; DELETE FROM employees 'Sarah'; DELETE FROM employees the result would simply be a search for the string "'Sarah'; DELETE FROM employees" , and you will not end up with an empty table . 'Sarah'; DELETE FROM employees结果只是搜索 string "'Sarah'; DELETE FROM employees" ,你不会得到一个空表

Another benefit of using prepared statements is that if you execute the same statement many times in the same session it will only be parsed and compiled once, giving you some speed gains.使用准备好的语句的另一个好处是,如果您在同一个 session 中多次执行同一个语句,它只会被解析和编译一次,从而为您带来一些速度提升。

Oh, and since you asked about how to do it for an insert, here's an example (using PDO):哦,既然你问过如何插入,这里有一个例子(使用 PDO):

 $preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)'); $preparedStatement->execute([ 'column' => $unsafeValue ]);

Can prepared statements be used for dynamic queries?准备好的语句可以用于动态查询吗?

While you can still use prepared statements for the query parameters, the structure of the dynamic query itself cannot be parametrized and certain query features cannot be parametrized.虽然您仍然可以为查询参数使用准备好的语句,但动态查询本身的结构无法参数化,某些查询功能也无法参数化。

For these specific scenarios, the best thing to do is use a whitelist filter that restricts the possible values.对于这些特定场景,最好的办法是使用限制可能值的白名单过滤器。

 // Value whitelist // $dir can only be 'DESC', otherwise it will be 'ASC' if (empty($dir) || $dir;== 'DESC') { $dir = 'ASC' }

To use the parameterized query, you need to use either Mysqli or PDO.要使用参数化查询,您需要使用 Mysqli 或 PDO。 To rewrite your example with mysqli, we would need something like the following.要使用 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();

The key function you'll want to read up on there would be mysqli::prepare .您要阅读的关键 function 将是mysqli::prepare

Also, as others have suggested, you may find it useful/easier to step up a layer of abstraction with something like PDO .此外,正如其他人所建议的那样,您可能会发现使用PDO之类的东西来加强抽象层很有用/更容易。

Please note that the case you asked about is a fairly simple one and that more complex cases may require more complex approaches.请注意,您询问的案例是一个相当简单的案例,更复杂的案例可能需要更复杂的方法。 In particular:尤其是:

  • If you want to alter the structure of the SQL based on user input, parameterized queries are not going to help, and the escaping required is not covered by mysql_real_escape_string .如果您想根据用户输入更改 SQL 的结构,参数化查询将无济于事,并且所需的 escaping 不包含在mysql_real_escape_string中。 In this kind of case, you would be better off passing the user's input through a whitelist to ensure only 'safe' values are allowed through.在这种情况下,您最好将用户的输入通过白名单传递,以确保只允许“安全”值通过。

Every answer here covers only part of the problem.这里的每个答案都只涵盖了问题的一部分。 In fact, there are four different query parts which we can add to SQL dynamically: -实际上,我们可以动态地将四个不同的查询部分添加到 SQL 中:-

  • a string一个 string
  • a number一个号码
  • an identifier标识符
  • a syntax keyword语法关键字

And prepared statements cover only two of them.准备好的陈述只涵盖其中两个。

But sometimes we have to make our query even more dynamic, adding operators or identifiers as well.但有时我们必须使查询更加动态,同时添加运算符或标识符。 So, we will need different protection techniques.因此,我们将需要不同的保护技术。

In general, such a protection approach is based on whitelisting .一般来说,这种保护方法是基于白名单的。

In this case, every dynamic parameter should be hardcoded in your script and chosen from that set.在这种情况下,每个动态参数都应该在您的脚本中进行硬编码并从该集合中选择。 For example, to do dynamic ordering:例如,要进行动态排序:

 $orders = array("name", "price", "qty"); // Field names $key = array_search($_GET['sort'], $orders)); // if we have such a name $orderby = $orders[$key]; // If not, first one will be set automatically. $query = "SELECT * FROM `table` ORDER BY $orderby"; // Value is safe

To ease the process I wrote a whitelist helper function that does all the job in one line:为了简化这个过程,我编写了一个白名单助手 function ,它可以在一行中完成所有工作:

 $orderby = white_list($_GET['orderby'], "name", ["name","price","qty"], "Invalid field name"); $query = "SELECT * FROM `table` ORDER BY `$orderby`"; // sound and safe

There is another way to secure identifiers - escaping but I rather stick to whitelisting as a more robust and explicit approach.还有另一种保护标识符的方法 - escaping 但我宁愿坚持将白名单作为一种更强大和更明确的方法。 Yet as long as you have an identifier quoted, you can escape the quote character to make it safe.然而,只要您引用了标识符,您就可以转义引号字符以使其安全。 For example, by default for mysql you have to double the quote character to escape it .例如,对于 mysql 默认情况下,您必须将引号字符加倍才能对其进行转义 For other other DBMS escaping rules would be different.对于其他其他 DBMS escaping 规则会有所不同。

Still, there is an issue with SQL syntax keywords (such as AND , DESC and such), but white-listing seems the only approach in this case.尽管如此,SQL 语法关键字(例如ANDDESC等)仍然存在问题,但在这种情况下,白名单似乎是唯一的方法。

So, a general recommendation may be phrased as因此,一般建议可以表述为

  • Any variable that represents an SQL data literal, (or, to put it simply - an SQL string, or a number) must be added through a prepared statement.任何代表 SQL 数据文字的变量,(或者,简单地说 - SQL string 或数字)都必须通过准备好的语句添加。 No Exceptions.没有例外。
  • Any other query part, such as an SQL keyword, a table or a field name, or an operator - must be filtered through a white list.任何其他查询部分,例如 SQL 关键字、表或字段名称或运算符 - 必须通过白名单过滤。

Update更新

Although there is a general agreement on the best practices regarding SQL injection protection, there are still many bad practices as well.尽管关于 SQL 注入保护的最佳实践达成了普遍共识,但仍然存在许多不良实践。 And some of them too deeply rooted in the minds of PHP users.其中一些在 PHP 用户的脑海中根深蒂固。 For instance, on this very page there are (although invisible to most visitors) more than 80 deleted answers - all removed by the community due to bad quality or promoting bad and outdated practices.例如,在这个页面上(尽管大多数访问者看不到)超过 80 个已删除的答案- 由于质量差或宣传不良和过时的做法,所有答案都被社区删除。 Worse yet, some of the bad answers aren't deleted, but rather prospering.更糟糕的是,一些不好的答案并没有被删除,而是蓬勃发展。

For example, there(1) are(2) still(3) many(4) answers(5) , including the second most upvoted answer suggesting you manual string escaping - an outdated approach that is proven to be insecure.例如, there(1) are(2) still(3) many(4) answers(5) ,包括第二个最受好评的答案,建议您手动 string escaping - 一种已被证明不安全的过时方法。

Or there is a slightly better answer that suggests just another method of string formatting and even boasts it as the ultimate panacea.或者有一个稍微好一点的答案,它表明string 格式化的另一种方法,甚至将其作为终极灵丹妙药。 While of course, it is not.当然,事实并非如此。 This method is no better than regular string formatting, yet it keeps all its drawbacks: it is applicable to strings only and, like any other manual formatting, it's essentially optional, non-obligatory measure, prone to human error of any sort.这种方法并不比常规的 string 格式化好,但它保留了所有缺点:它仅适用于字符串,并且与任何其他手动格式化一样,它本质上是可选的、非强制性的措施,容易出现任何类型的人为错误。

I think that all this because of one very old superstition, supported by such authorities like OWASP or the PHP manual , which proclaims equality between whatever "escaping" and protection from SQL injections.我认为这一切都是因为一个非常古老的迷信,得到了像OWASPPHP 手册这样的权威的支持,它宣称任何“逃避”和保护 SQL 注射之间都是平等的。

Regardless of what PHP manual said for ages, *_escape_string by no means makes data safe and never has been intended to.不管 PHP 手册多年来说什么, *_escape_string绝不会确保数据安全,而且从未打算这样做。 Besides being useless for any SQL part other than string, manual escaping is wrong, because it is manual as opposite to automated.除了对除 string 以外的任何 SQL 零件无用外,手动 escaping 是错误的,因为它是手动的,与自动相反。

And OWASP makes it even worse, stressing on escaping user input which is an utter nonsense: there should be no such words in the context of injection protection. OWASP 让情况变得更糟,强调 escaping用户输入,这完全是胡说八道:在注入保护的上下文中不应该有这样的词。 Every variable is potentially dangerous - no matter the source, Or.每个变量都有潜在的危险——无论来源如何,或者。 in other words - every variable has to be properly formatted to be put into a query - no matter the source again.换句话说 - 每个变量都必须正确格式化才能放入查询中 - 无论来源如何。 It's the destination that matters.重要的是目的地。 The moment a developer starts to separate the sheep from the goats (thinking whether some particular variable is "safe" or not) he/she takes his/her first step towards disaster, Not to mention that even the wording suggests bulk escaping at the entry point, resembling the very magic quotes feature - already despised.开发人员开始将绵羊与山羊分开的那一刻(考虑某个特定变量是否“安全”)他/她迈出了走向灾难的第一步,更不用说,即使措辞在入口处暗示大量 escaping点,类似于非常神奇的引号功能 - 已经被鄙视了。 deprecated and removed已弃用和删除

So, unlike whatever "escaping", prepared statements is the measure that indeed protects from SQL injection (when applicable).因此,与任何“转义”不同,准备好的语句确实可以防止 SQL 注入(如果适用)的措施。

I'd recommend using PDO (PHP Data Objects) to run parameterized SQL queries.我建议使用PDO (PHP 数据对象)来运行参数化的 SQL 查询。

Not only does this protect against SQL injection, but it also speeds up queries.这不仅可以防止 SQL 注入,而且还可以加快查询速度。

And by using PDO rather than mysql_ , mysqli_ , and pgsql_ functions, you make your application a little more abstracted from the database, in the rare occurrence that you have to switch database providers.通过使用 PDO 而不是mysql_mysqli_pgsql_函数,您可以使您的应用程序从数据库中更加抽象一点,在极少数情况下您必须切换数据库提供程序。

Use PDO and prepared queries.使用PDO和准备好的查询。

( $conn is a PDO object) $conn是一个PDO对象)

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

As you can see, people suggest you use prepared statements at the most.如您所见,人们建议您最多使用准备好的语句。 It's not wrong, but when your query is executed just once per process, there would be a slight performance penalty.这没有错,但是当您的查询在每个进程中只执行一次时,性能会受到轻微影响。

I was facing this issue, but I think I solved it in very sophisticated way - the way hackers use to avoid using quotes.我遇到了这个问题,但我认为我以非常复杂的方式解决了它——黑客用来避免使用引号的方式。 I used this in conjunction with emulated prepared statements.我将它与模拟的准备好的语句结合使用。 I use it to prevent all kinds of possible SQL injection attacks.我用它来防止各种可能的 SQL 注入攻击。

My approach:我的做法:

  • If you expect input to be integer make sure it's really integer.如果您希望输入是 integer,请确保它确实是integer。 In a variable-type language like PHP it is this very important.在像 PHP 这样的变量类型语言中,这一点非常重要。 You can use for example this very simple but powerful solution: sprintf("SELECT 1,2,3 FROM table WHERE 4 = %u", $input);例如,您可以使用这个非常简单但功能强大的解决方案: sprintf("SELECT 1,2,3 FROM table WHERE 4 = %u", $input);

  • If you expect anything else from integer hex it .如果您期望 integer十六进制它的其他任何内容。 If you hex it, you will perfectly escape all input.如果你对它进行十六进制,你将完美地逃避所有输入。 In C/C++ there's a function called mysql_hex_string() , in PHP you can use bin2hex() .在 C/C++ 中有一个名为mysql_hex_string()的 function ,在 PHP 中你可以使用bin2hex()

    Don't worry about that the escaped string will have a 2x size of its original length because even if you use mysql_real_escape_string , PHP has to allocate same capacity ((2*input_length)+1) , which is the same.不用担心转义的 string 的大小是其原始长度的 2 倍,因为即使使用mysql_real_escape_string , PHP 也必须分配相同的容量((2*input_length)+1) ,这是相同的。

  • This hex method is often used when you transfer binary data, but I see no reason why not use it on all data to prevent SQL injection attacks.传输二进制数据时经常使用这种十六进制方法,但我认为没有理由不对所有数据使用它来防止 SQL 注入攻击。 Note that you have to prepend data with 0x or use the MySQL function UNHEX instead.请注意,您必须在数据前添加0x或使用 MySQL function UNHEX代替。

So, for example, the query:因此,例如,查询:

 SELECT password FROM users WHERE name = 'root';

Will become:会变成:

 SELECT password FROM users WHERE name = 0x726f6f74;

or或者

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

Hex is the perfect escape. Hex是完美的逃生方式。 No way to inject.没办法注入。

Difference between UNHEX function and 0x prefix UNHEX function 和 0x 前缀之间的区别

There was some discussion in comments, so I finally want to make it clear.评论里有一些讨论,所以我最后想说清楚。 These two approaches are very similar, but they are a little different in some ways:这两种方法非常相似,但在某些方面略有不同:

The 0x prefix can only be used for data columns such as char , varchar , text , block , binary , etc. 0x前缀只能用于charvarchartextblockbinary等数据列。
Also, its use is a little complicated if you are about to insert an empty string.此外,如果您要插入一个空的 string,它的使用会有点复杂。 You'll have to entirely replace it with '' , or you'll get an error.你必须用''完全替换它,否则你会得到一个错误。

UNHEX() works on any column; UNHEX()适用于任何列; you do not have to worry about the empty string.您不必担心空的 string。


Hex methods are often used as attacks十六进制方法经常被用作攻击

Note that this hex method is often used as an SQL injection attack where integers are just like strings and escaped just with mysql_real_escape_string .请注意,这种十六进制方法通常用作 SQL 注入攻击,其中整数就像字符串一样,只需使用mysql_real_escape_string进行转义。 Then you can avoid the use of quotes.然后你可以避免使用引号。

For example, if you just do something like this:例如,如果您只是执行以下操作:

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

an attack can inject you very easily .一次攻击可以很容易地注入你。 Consider the following injected code returned from your script:考虑以下从您的脚本返回的注入代码:

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

and now just extract table structure:现在只需提取表结构:

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

And then just select whatever data ones want.然后只是 select 任何数据的人想要的。 Isn't it cool?不是很酷吗?

But if the coder of an injectable site would hex it, no injection would be possible because the query would look like this:但是,如果可注入站点的编码器对其进行十六进制处理,则不可能进行注入,因为查询如下所示:

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

Deprecated Warning: This answer's sample code (like the question's sample code) uses PHP's MySQL extension, which was deprecated in PHP 5.5.0 and removed entirely in PHP 7.0.0.不推荐使用的警告:此答案的示例代码(与问题的示例代码一样)使用 PHP 的MySQL扩展,该扩展在 PHP 5.5.0 中已弃用,并在 Z2FEC392304A5C23AC138DA.727.9B7 中完全删除。

Security Warning : This answer is not in line with security best practices.安全警告:此答案不符合安全最佳实践。 Escaping is inadequate to prevent SQL injection , use prepared statements instead. Escaping 不足以防止 SQL 注入,请改用准备好的语句 Use the strategy outlined below at your own risk.使用下面概述的策略,风险自负。 (Also, mysql_real_escape_string() was removed in PHP 7.) (此外,在 PHP 7 中删除了mysql_real_escape_string() 。)

IMPORTANT重要的

The best way to prevent SQL Injection is to use Prepared Statements instead of escaping , as the accepted answer demonstrates.正如公认的答案所示,防止 SQL 注入的最佳方法是使用Prepared Statements而不是 escaping

There are libraries such as Aura.Sql and EasyDB that allow developers to use prepared statements easier.有诸如Aura.SqlEasyDB 之类的库可以让开发人员更轻松地使用准备好的语句。 To learn more about why prepared statements are better at stopping SQL injection , refer to this mysql_real_escape_string() bypass and recently fixed Unicode SQL Injection vulnerabilities in WordPress . To learn more about why prepared statements are better at stopping SQL injection , refer to this mysql_real_escape_string() bypass and recently fixed Unicode SQL Injection vulnerabilities in WordPress .

Injection prevention - mysql_real_escape_string()注入预防 - mysql_real_escape_string()

PHP has a specially-made function to prevent these attacks. PHP 有一个特制的 function 来防止这些攻击。 All you need to do is use the mouthful of a function, mysql_real_escape_string .您需要做的就是使用一口 function, mysql_real_escape_string

mysql_real_escape_string takes a string that is going to be used in a MySQL query and return the same string with all SQL injection attempts safely escaped. mysql_real_escape_string takes a string that is going to be used in a MySQL query and return the same string with all SQL injection attempts safely escaped. Basically, it will replace those troublesome quotes(') a user might enter with a MySQL-safe substitute, an escaped quote \'.基本上,它将用一个 MySQL 安全的替代品,一个转义的引号 \' 替换用户可能输入的那些麻烦的引号(')。

NOTE: you must be connected to the database to use this function注意:您必须连接到数据库才能使用此 function

// Connect to MySQL // 连接到 MySQL

 $name_bad = "' OR 1'"; $name_bad = mysql_real_escape_string($name_bad); $query_bad = "SELECT * FROM customers WHERE username = '$name_bad'"; echo "Escaped Bad Injection: <br />". $query_bad. "<br />"; $name_evil = "'; DELETE FROM customers WHERE 1 or username = '"; $name_evil = mysql_real_escape_string($name_evil); $query_evil = "SELECT * FROM customers WHERE username = '$name_evil'"; echo "Escaped Evil Injection: <br />". $query_evil;

You can find more details in MySQL - SQL Injection Prevention .您可以在MySQL - SQL 注入预防中找到更多详细信息。

Security Warning : This answer is not in line with security best practices.安全警告:此答案不符合安全最佳实践。 Escaping is inadequate to prevent SQL injection , use prepared statements instead. Escaping 不足以防止 SQL 注入,请改用准备好的语句 Use the strategy outlined below at your own risk.使用下面概述的策略,风险自负。

You could do something basic like this:你可以做一些基本的事情是这样的:

 $safe_variable = mysqli_real_escape_string($_POST["user-input"], $dbConnection); mysqli_query($dbConnection, "INSERT INTO table (column) VALUES ('". $safe_variable. "')");

This won't solve every problem, but it's a very good stepping stone.这不会解决所有问题,但它是一个很好的垫脚石。 I left out obvious items such as checking the variable's existence, format (numbers, letters, etc.).我省略了明显的项目,例如检查变量的存在、格式(数字、字母等)。

Whatever you do end up using, make sure that you check your input hasn't already been mangled by magic_quotes or some other well-meaning rubbish, and if necessary, run it through stripslashes or whatever to sanitize it.无论您最终使用什么,请确保您检查您的输入是否已经被magic_quotes或其他一些善意的垃圾所破坏,如果有必要,请通过stripslashes或其他任何方式对其进行清理。

Deprecated Warning: This answer's sample code (like the question's sample code) uses PHP's MySQL extension, which was deprecated in PHP 5.5.0 and removed entirely in PHP 7.0.0.不推荐使用的警告:此答案的示例代码(与问题的示例代码一样)使用 PHP 的MySQL扩展,该扩展在 PHP 5.5.0 中已弃用,并在 Z2FEC392304A5C23AC138DA.727.9B7 中完全删除。

Security Warning : This answer is not in line with security best practices.安全警告:此答案不符合安全最佳实践。 Escaping is inadequate to prevent SQL injection , use prepared statements instead. Escaping 不足以防止 SQL 注入,请改用准备好的语句 Use the strategy outlined below at your own risk.使用下面概述的策略,风险自负。 (Also, mysql_real_escape_string() was removed in PHP 7.) (此外,在 PHP 7 中删除了mysql_real_escape_string() 。)

Parameterized query AND input validation is the way to go.参数化查询 AND 输入验证是 go 的方式。 There are many scenarios under which SQL injection may occur, even though mysql_real_escape_string() has been used.即使使用了mysql_real_escape_string() ,也有很多情况下可能会发生SQL注入。

Those examples are vulnerable to SQL injection:这些示例容易受到 SQL 注入的影响:

 $offset = isset($_GET['o'])? $_GET['o']: 0; $offset = mysql_real_escape_string($offset); RunQuery("SELECT userid, username FROM sql_injection_test LIMIT $offset, 10");

or或者

$order = isset($_GET['o'])? $_GET['o']: 'userid'; $order = mysql_real_escape_string($order); RunQuery("SELECT userid, username FROM sql_injection_test ORDER BY `$order`");

In both cases, you can't use ' to protect the encapsulation.在这两种情况下,您都不能使用'来保护封装。

Source : The Unexpected SQL Injection (When Escaping Is Not Enough)来源意外的 SQL 注入(当 Escaping 不够时)

In my opinion, the best way to generally prevent SQL injection in your PHP application (or any web application, for that matter) is to think about your application's architecture.在我看来,在您的 PHP 应用程序(或任何 web 应用程序)中通常防止 SQL 注入的最佳方法是考虑您的应用程序架构。 If the only way to protect against SQL injection is to remember to use a special method or function that does The Right Thing every time you talk to the database, you are doing it wrong.如果防止 SQL 注入的唯一方法是记住使用特殊方法或 function 每次与数据库交谈时都执行正确的事情,那么您做错了。 That way, it's just a matter of time until you forget to correctly format your query at some point in your code.这样,您忘记在代码中的某个位置正确格式化查询只是时间问题。

Adopting the MVC pattern and a framework like CakePHP or CodeIgniter is probably the right way to go: Common tasks like creating secure database queries have been solved and centrally implemented in such frameworks.采用 MVC 模式和CakePHPCodeIgniter之类的框架可能是 go 等常见任务已在此类框架中解决并集中实施。 They help you to organize your web application in a sensible way and make you think more about loading and saving objects than about securely constructing single SQL queries.它们帮助您以合理的方式组织您的 web 应用程序,并使您更多地考虑加载和保存对象,而不是安全地构建单个 SQL 查询。

There are many ways of preventing SQL injections and other SQL hacks. You can easily find it on the Internet (Google Search). Of course PDO is one of the good solutions. But I would like to suggest you some good links prevention from SQL injection.

What is SQL injection and how to prevent

PHP manual for SQL injection

Microsoft explanation of SQL injection and prevention in PHP

And some other like Preventing SQL injection with MySQL and PHP.

Now, why you do you need to prevent your query from SQL injection?

I would like to let you know: Why do we try for preventing SQL injection with a short example below:

Query for login authentication match:

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

Now, if someone (a hacker) puts

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

and password anything....

The query will be parsed into the system only up to:

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

The other part will be discarded. So, what will happen? A non-authorized user (hacker) will be able to log in as administrator without having his/her password. Now, he/she can do anything that the administrator/email person can do. See, it's very dangerous if SQL injection is not prevented.

I favor stored procedures ( MySQL has had stored procedures support since 5.0 ) from a security point of view - the advantages are -从安全的角度来看,我更喜欢存储过程MySQL 从 5.0 开始就支持存储过程) - 优点是 -

  1. Most databases (including MySQL ) enable user access to be restricted to executing stored procedures.大多数数据库(包括MySQL )允许用户访问被限制为执行存储过程。 The fine-grained security access control is useful to prevent escalation of privileges attacks.细粒度的安全访问控制有助于防止权限升级攻击。 This prevents compromised applications from being able to run SQL directly against the database.这可以防止受感染的应用程序能够直接针对数据库运行 SQL。
  2. They abstract the raw SQL query from the application so less information of the database structure is available to the application.他们从应用程序中抽象出原始的 SQL 查询,因此应用程序可以使用的数据库结构信息较少。 This makes it harder for people to understand the underlying structure of the database and design suitable attacks.这使得人们更难理解数据库的底层结构和设计合适的攻击。
  3. They accept only parameters, so the advantages of parameterized queries are there.它们只接受参数,因此参数化查询的优势就在那里。 Of course - IMO you still need to sanitize your input - especially if you are using dynamic SQL inside the stored procedure.当然 - IMO 你仍然需要清理你的输入 - 特别是如果你在存储过程中使用动态 SQL 。

The disadvantages are -缺点是——

  1. They (stored procedures) are tough to maintain and tend to multiply very quickly.它们(存储过程)很难维护,并且往往会迅速增加。 This makes managing them an issue.这使得管理它们成为一个问题。
  2. They are not very suitable for dynamic queries - if they are built to accept dynamic code as parameters then a lot of the advantages are negated.它们不太适合动态查询——如果它们被构建为接受动态代码作为参数,那么很多优点都被否定了。

I think if someone wants to use PHP and MySQL or some other dataBase server:我想如果有人想使用 PHP 和 MySQL 或其他一些数据库服务器:

  1. Think about learning PDO (PHP Data Objects) – it is a database access layer providing a uniform method of access to multiple databases.想想学习PDO (PHP 数据对象)——它是一个数据库访问层,提供访问多个数据库的统一方法。
  2. Think about learning MySQLi想学MySQLi

Libraries examples:库示例:

---- PDO ---- PDO

----- No placeholders - ripe for SQL injection It's bad ----- 没有占位符 - SQL注入已经成熟

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values ($name, $addr, $city)");

----- Unnamed placeholders ----- 未命名的占位符

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values (?,?,?);

----- Named placeholders ----- 命名占位符

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) value (:name,:addr,:city)");

--- MySQLi --- MySQLi

 $request = $mysqliConnection->prepare(' SELECT * FROM trainers WHERE name =? AND email =? AND last_login >?'); $query->bind_param('first_param', 'second_param', $mail, time() - 3600); $query->execute();

PS : PS

PDO wins this battle with ease. PDO 轻松赢得这场战斗。 With support for twelve different database drivers and named parameters, we can get used to its API.通过支持十二种不同的数据库驱动程序和命名参数,我们可以习惯它的 API。 From a security standpoint, both of them are safe as long as the developer uses them the way they are supposed to be used从安全的角度来看,只要开发人员以应有的方式使用它们,它们都是安全的

If possible, cast the types of your parameters.如果可能,请转换参数的类型。 But it's only working on simple types like int, bool, and float.但它只适用于简单类型,如 int、bool 和 float。

 $unsafe_variable = $_POST['user_id']; $safe_variable = (int)$unsafe_variable; mysqli_query($conn, "INSERT INTO table (column) VALUES ('". $safe_variable. "')");

If you want to take advantage of cache engines, like Redis or Memcached , maybe DALMP could be a choice.如果您想利用缓存引擎,例如RedisMemcached ,也许 DALMP 可能是一个选择。 It uses pure MySQLi .它使用纯MySQLi Check this: DALMP Database Abstraction Layer for MySQL using PHP.检查这个: 使用 PHP 的 MySQL 的 DALMP 数据库抽象层。

Also, you can 'prepare' your arguments before preparing your query so that you can build dynamic queries and at the end have a fully prepared statements query.此外,您可以在准备查询之前“准备”您的 arguments,以便您可以构建动态查询并最终获得完全准备好的语句查询。DALMP Database Abstraction Layer for MySQL using PHP.使用 PHP 的 MySQL 的 DALMP 数据库抽象层。

For those unsure of how to use PDO (coming from the mysql_ functions), I made a very, very simple PDO wrapper that is a single file.对于那些不确定如何使用 PDO(来自mysql_函数)的人,我制作了一个非常非常简单的 PDO 包装器,它是一个文件。 It exists to show how easy it is to do all the common things applications need to be done.它的存在是为了展示完成应用程序需要完成的所有常见事情是多么容易。 Works with PostgreSQL, MySQL, and SQLite.适用于 PostgreSQL、MySQL 和 SQLite。

Basically, read it while you read the manual to see how to put the PDO functions to use in real life to make it simple to store and retrieve values in the format you want.基本上,在阅读手册的同时阅读它,了解如何将 PDO 函数用于现实生活,以便以所需格式轻松存储和检索值。

I want a single column我想要一个单列

$count = DB::column('SELECT COUNT(*) FROM `user`');

I want an array(key => value) results (ie for making a selectbox)我想要一个数组(键 => 值)结果(即用于制作选择框)

 $pairs = DB::pairs('SELECT `id`, `username` FROM `user`');

I want a single row result我想要一个单行结果

$user = DB::row('SELECT * FROM `user` WHERE `id` =?', array($user_id));

I want an array of results我想要一系列结果

$banned_users = DB::fetch('SELECT * FROM `user` WHERE `banned` =?', array('TRUE'));

Security Warning : This answer is not in line with security best practices.安全警告:此答案不符合安全最佳实践。 Escaping is inadequate to prevent SQL injection , use prepared statements instead. Escaping 不足以防止 SQL 注入,请改用准备好的语句

A few guidelines for escaping special characters in SQL statements. SQL 语句中的 escaping 特殊字符的一些准则。

Don't use MySQL .不要使用MySQL This extension is deprecated.此扩展已弃用。 Use MySQLi or PDO instead.请改用MySQLiPDO

MySQLi MySQLi

For manually escaping special characters in a string you can use the mysqli_real_escape_string function.对于 string 中的手动 escaping 特殊字符,您可以使用mysqli_real_escape_string function。 The function will not work properly unless the correct character set is set with mysqli_set_charset .除非使用mysqli_set_charset设置了正确的字符集,否则 function 将无法正常工作。

Example:例子:

 $mysqli = new mysqli('host', 'user', 'password', 'database'); $mysqli->set_charset('charset'); $string = $mysqli->real_escape_string($string); $mysqli->query("INSERT INTO table (column) VALUES ('$string')");

For automatic escaping of values with prepared statements, use mysqli_prepare , and mysqli_stmt_bind_param where types for the corresponding bind variables must be provided for an appropriate conversion:对于使用准备好的语句的自动 escaping 值,使用mysqli_preparemysqli_stmt_bind_param其中必须提供相应绑定变量的类型以进行适当的转换:

Example:例子:

 $stmt = $mysqli->prepare("INSERT INTO table (column1, column2) VALUES (?,?)"); $stmt->bind_param("is", $integer, $string); $stmt->execute();

No matter if you use prepared statements or mysqli_real_escape_string , you always have to know the type of input data you're working with.无论您使用准备好的语句还是mysqli_real_escape_string ,您始终必须知道您正在使用的输入数据的类型。

So if you use a prepared statement, you must specify the types of the variables for mysqli_stmt_bind_param function.所以如果你使用prepared statement,你必须为mysqli_stmt_bind_param function指定变量的类型。

And the use of mysqli_real_escape_string is for, as the name says, escaping special characters in a string, so it will not make integers safe.顾名思义, mysqli_real_escape_string的使用是用于 string 中的 escaping 特殊字符,因此它不会使整数安全。 The purpose of this function is to prevent breaking the strings in SQL statements, and the damage to the database that it could cause.此 function 的目的是防止破坏 SQL 语句中的字符串,以及它可能导致的数据库损坏。 mysqli_real_escape_string is a useful function when used properly, especially when combined with sprintf . mysqli_real_escape_string在正确使用时是一个有用的 function,尤其是与sprintf结合使用时。

Example:例子:

 $string = "x' OR name LIKE '%John%"; $integer = '5 OR id;= 0', $query = sprintf( "SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string($string); $integer); echo $query, // SELECT id, email, pass; name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 5 $integer = '99999999999999999999', $query = sprintf("SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string($string); $integer); echo $query, // SELECT id, email, pass name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 2147483647

Security Warning: This answer is not in line with security best practices. Escaping is inadequate to prevent SQL injection, use prepared statements instead. Use the strategy outlined below at your own risk. (Also, mysql_real_escape_string() was removed in PHP 7.)

Warning: The mysql extension is removed at this time. we recommend using the PDO extension

Using this PHP function mysql_escape_string() you can get a good prevention in a fast way.

For example:

SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."'

mysql_escape_string — Escapes a string for use in a mysql_query

For more prevention, you can add at the end ...

wHERE 1=1   or  LIMIT 1

Finally you get:

SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."' LIMIT 1

The simple alternative to this problem could be solved by granting appropriate permissions in the database itself.这个问题的简单替代方案可以通过在数据库本身中授予适当的权限来解决。 For example: if you are using a MySQL database then enter into the database through terminal or the UI provided and just follow this command:例如:如果您使用的是 MySQL 数据库,则通过终端或提供的 UI 进入数据库并执行以下命令:

 GRANT SELECT, INSERT, DELETE ON database TO username@'localhost' IDENTIFIED BY 'password';

This will restrict the user to only get confined with the specified query's only.这将限制用户只能使用指定的查询。 Remove the delete permission and so the data would never get deleted from the query fired from the PHP page.删除删除权限,因此数据永远不会从 PHP 页面触发的查询中删除。 The second thing to do is to flush the privileges so that the MySQL refreshes the permissions and updates.第二件事是刷新权限,以便 MySQL 刷新权限和更新。

 FLUSH PRIVILEGES;

more information about flush .有关冲洗的更多信息。

To see the current privileges for the user fire the following query.要查看用户的当前权限,请触发以下查询。

 select * from mysql.user where User='username';

Learn more about GRANT .了解更多关于GRANT的信息。

Regarding many useful answers, I hope to add some value to this thread.关于许多有用的答案,我希望为这个线程增加一些价值。

SQL injection is an attack that can be done through user inputs (inputs that filled by a user and then used inside queries). SQL 注入是一种可以通过用户输入(由用户填写然后在查询中使用的输入)完成的攻击。 The SQL injection patterns are correct query syntax while we can call it: bad queries for bad reasons, and we assume that there might be a bad person that try to get secret information (bypassing access control) that affect the three principles of security (confidentiality, integrity, and availability). SQL 注入模式是正确的查询语法,我们可以称其为:出于不良原因的不良查询,我们假设可能有坏人试图获取影响安全三原则(机密性)的秘密信息(绕过访问控制) 、完整性和可用性)。

Now, our point is to prevent security threats such as SQL injection attacks, the question asking (how to prevent an SQL injection attack using PHP), be more realistic, data filtering or clearing input data is the case when using user-input data inside such query, using PHP or any other programming language is not the case, or as recommended by more people to use modern technology such as prepared statement or any other tools that currently supporting SQL injection prevention, consider that these tools not available anymore?现在,我们的重点是防止SQL注入攻击等安全威胁,问题是(如何使用PHP防止SQL注入攻击),更现实一点,数据过滤或清除输入数据是使用用户输入数据时的情况这样的查询,使用 PHP 或任何其他编程语言不是这种情况,或者更多人建议使用现代技术,例如准备好的语句或任何其他当前支持 SQL 注入预防的工具,认为这些工具不再可用? How do you secure your application?您如何保护您的应用程序?

My approach against SQL injection is: clearing user-input data before sending it to the database (before using it inside any query).我针对 SQL 注入的方法是:在将用户输入数据发送到数据库之前(在任何查询中使用之前)清除用户输入数据。

Data filtering for (converting unsafe data to safe data)数据过滤(将不安全数据转换为安全数据)

Consider that PDO and MySQLi are not available.考虑到PDOMySQLi不可用。 How can you secure your application?如何保护您的应用程序? Do you force me to use them?你强迫我使用它们吗? What about other languages other than PHP? PHP 以外的其他语言呢? I prefer to provide general ideas as it can be used for wider border, not just for a specific language.我更喜欢提供一般性的想法,因为它可以用于更广泛的边界,而不仅仅是特定的语言。

  1. SQL user (limiting user privilege): most common SQL operations are (SELECT, UPDATE, INSERT), then, why give the UPDATE privilege to a user that does not require it? SQL 用户(限制用户权限):最常见的 SQL 操作是(SELECT,UPDATE,INSERT),那么,为什么要给不需要的用户 UPDATE 权限呢? For example, login, and search pages are only using SELECT, then, why use DB users in these pages with high privileges?比如登录、搜索页面都只使用了SELECT,那么,为什么在这些页面中使用高权限的DB用户呢?

RULE: do not create one database user for all privileges.规则:不要为所有权限创建一个数据库用户。 For all SQL operations, you can create your scheme like (deluser, selectuser, updateuser) as usernames for easy usage.对于所有 SQL 操作,您可以创建您的方案,如(deluser、selectuser、updateuser)作为用户名以便于使用。

See principle of least privilege .参见最小特权原则

  1. Data filtering: before building any query user input, it should be validated and filtered.数据过滤:在构建任何查询用户输入之前,应该对其进行验证和过滤。 For programmers, it's important to define some properties for each user-input variables: data type, data pattern, and data length .对于程序员来说,为每个用户输入变量定义一些属性很重要:数据类型、数据模式和数据长度 A field that is a number between (x and y) must be exactly validated using the exact rule, and for a field that is a string (text): pattern is the case, for example, a username must contain only some characters, let's say [a-zA-Z0-9_-.].必须使用精确规则精确验证 (x 和 y) 之间的数字字段,对于 string(文本)的字段:模式就是这种情况,例如,用户名只能包含一些字符,让我们说[a-zA-Z0-9_-.]。 The length varies between (x and n) where x and n (integers, x <=n).长度在 (x 和 n) 之间变化,其中 x 和 n (整数,x <=n)。 Rule: creating exact filters and validation rules are best practices for me.规则:创建精确的过滤器和验证规则对我来说是最佳实践。

  2. Use other tools: Here, I will also agree with you that a prepared statement (parametrized query) and stored procedures.使用其他工具:在这里,我也同意您的说法,即准备好的语句(参数化查询)和存储过程。 The disadvantages here is these ways require advanced skills which do not exist for most users.这里的缺点是这些方法需要大多数用户不具备的高级技能。 The basic idea here is to distinguish between the SQL query and the data that is used inside.这里的基本思想是区分 SQL 查询和里面使用的数据。 Both approaches can be used even with unsafe data, because the user-input data here does not add anything to the original query, such as (any or x=x).即使是不安全的数据也可以使用这两种方法,因为这里的用户输入数据不会向原始查询添加任何内容,例如 (any 或 x=x)。

For more information, please read OWASP SQL Injection Prevention Cheat Sheet .有关更多信息,请阅读OWASP SQL 注射预防备忘单

Now, if you are an advanced user, start using this defense as you like, but, for beginners, if they can't quickly implement a stored procedure and prepared the statement, it's better to filter input data as much they can.现在,如果你是高级用户,开始使用这个防御就可以了,但是,对于初学者来说,如果他们不能快速实现存储过程并准备好语句,那么最好尽可能过滤输入数据。

Finally, let's consider that a user sends this text below instead of entering his/her username:最后,让我们考虑一个用户在下面发送这个文本而不是输入他/她的用户名:

 [1] UNION SELECT IF(SUBSTRING(Password,1,1)='2',BENCHMARK(100000,SHA1(1)),0) User,Password FROM mysql.user WHERE User = 'root'

This input can be checked early without any prepared statement and stored procedures, but to be on the safe side, using them starts after user-data filtering and validation.无需任何准备好的语句和存储过程即可尽早检查此输入,但为了安全起见,在用户数据过滤和验证之后开始使用它们。

The last point is detecting unexpected behavior which requires more effort and complexity;最后一点是检测需要更多努力和复杂性的意外行为; it's not recommended for normal web applications.不建议将其用于普通 web 应用程序。

Unexpected behavior in the above user input is SELECT, UNION, IF, SUBSTRING, BENCHMARK, SHA, and root.上述用户输入中的意外行为是 SELECT、UNION、IF、SUBSTRING、BENCHMARK、SHA 和 root。 Once these words detected, you can avoid the input.一旦检测到这些词,就可以避免输入。

UPDATE 1:更新 1:

A user commented that this post is useless, OK Here is what OWASP.ORG provided :一位用户评论说这个帖子没用,好的,这是OWASP.ORG 提供的:

Primary defenses:主要防御:

Option #1: Use of Prepared Statements (Parameterized Queries)选项 #1:使用准备好的语句(参数化查询)
Option #2: Use of Stored Procedures选项 #2:使用存储过程
Option #3: Escaping all User Supplied Input选项#3:Escaping 所有用户提供的输入

Additional defenses:附加防御:

Also Enforce: Least Privilege同时执行:最小特权
Also Perform: White List Input Validation同时执行:白名单输入验证

As you may know, claiming an article should be supported by a valid argument, at least by one reference, Otherwise it's considered as an attack and a bad claim你可能知道,声明一篇文章应该有一个有效的论据支持,至少有一个引用,否则它被认为是攻击和错误的声明

Update 2:更新 2:

From the PHP manual, PHP: Prepared Statements - Manual :来自 PHP 手册, PHP:准备好的语句 - 手册

Escaping and SQL injection Escaping 和 SQL 注入

Bound variables will be escaped automatically by the server.绑定变量将被服务器自动转义。 The server inserts their escaped values at the appropriate places into the statement template before execution.服务器在执行之前将它们的转义值插入到语句模板的适当位置。 A hint must be provided to the server for the type of bound variable, to create an appropriate conversion.必须向服务器提供绑定变量类型的提示,以创建适当的转换。 See the mysqli_stmt_bind_param() function for more information.有关详细信息,请参阅 mysqli_stmt_bind_param() function。

The automatic escaping of values within the server is sometimes considered a security feature to prevent SQL injection.服务器内的自动 escaping 值有时被认为是防止 SQL 注入的安全功能。 The same degree of security can be achieved with non-prepared statements if input values are escaped correctly.如果输入值被正确转义,则可以使用非准备语句实现相同程度的安全性。

Update 3:更新 3:

I created test cases for knowing how PDO and MySQLi send the query to the MySQL server when using a prepared statement:我创建了测试用例来了解 PDO 和 MySQLi 在使用准备好的语句时如何将查询发送到 MySQL 服务器:

PDO: PDO:

 $user = "''1''"; // Malicious keyword $sql = 'SELECT * FROM awa_user WHERE userame =:username'; $sth = $dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY)); $sth->execute(array(':username' => $user));

Query Log:查询日志:

 189 Query SELECT * FROM awa_user WHERE userame ='\'\'1\'\'' 189 Quit

MySQLi: MySQLi:

 $stmt = $mysqli->prepare("SELECT * FROM awa_user WHERE username =?")) { $stmt->bind_param("s", $user); $user = "''1''"; $stmt->execute();

Query Log:查询日志:

 188 Prepare SELECT * FROM awa_user WHERE username =? 188 Execute SELECT * FROM awa_user WHERE username ='\'\'1\'\'' 188 Quit

It's clear that a prepared statement is also escaping the data, nothing else.很明显,一个prepared statement也是escaping的数据,仅此而已。

As also mentioned in the above statement,正如上述声明中也提到的,

The automatic escaping of values within the server is sometimes considered a security feature to prevent SQL injection.服务器内的自动 escaping 值有时被认为是防止 SQL 注入的安全功能。 The same degree of security can be achieved with non-prepared statements, if input values are escaped correctly如果输入值被正确转义,则使用非准备语句可以实现相同程度的安全性

Therefore, this proves that data validation such as intval() is a good idea for integer values before sending any query.因此,这证明在发送任何查询之前对 integer 值进行数据验证(如intval()是一个好主意。 In addition, preventing malicious user data before sending the query is a correct and valid approach .此外,在发送查询之前阻止恶意用户数据是一种正确有效的方法

Please see this question for more detail: PDO sends raw query to MySQL while Mysqli sends prepared query, both produce the same result有关更多详细信息,请参阅此问题: PDO 将原始查询发送到 MySQL 而 Mysqli 发送准备好的查询,两者都产生相同的结果

References:参考:

  1. SQL Injection Cheat Sheet SQL 注塑备忘单
  2. SQL Injection SQL 注塑
  3. Information security信息安全
  4. Security Principles 安全原则
  5. Data validation数据验证

Security Warning : This answer is not in line with security best practices.安全警告:此答案不符合安全最佳实践。 Escaping is inadequate to prevent SQL injection , use prepared statements instead. Escaping 不足以防止 SQL 注入,请改用准备好的语句 Use the strategy outlined below at your own risk.使用下面概述的策略,风险自负。 (Also, mysql_real_escape_string() was removed in PHP 7.) (此外,在 PHP 7 中删除了mysql_real_escape_string() 。)

Deprecated Warning : The mysql extension is deprecated at this time.不推荐使用的警告:此时不推荐使用 mysql 扩展。 we recommend using the PDO extension我们建议使用PDO 扩展

I use three different ways to prevent my web application from being vulnerable to SQL injection.我使用三种不同的方法来防止我的 web 应用程序容易受到 SQL 注入的影响。

  1. Use of mysql_real_escape_string() , which is a pre-defined function in PHP , and this code add backslashes to the following characters: \x00 , \n , \r , \ , ' , " and \x1a . Pass the input values as parameters to minimize the chance of SQL injection.使用mysql_real_escape_string() ,这是 PHP 中预定义的function ,并且此代码将反斜杠添加到以下字符: \x00\n\r\' a , "\x1a以尽量减少 SQL 注入的机会。
  2. The most advanced way is to use PDOs.最先进的方法是使用 PDO。

I hope this will help you.我希望这能帮到您。

Consider the following query:考虑以下查询:

$iId = mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";

mysql_real_escape_string() will not protect here. mysql_real_escape_string() 不会在这里保护。 If you use single quotes (' ') around your variables inside your query is what protects you against this.如果您在查询中的变量周围使用单引号 (' ') 可以保护您免受这种情况的影响。 Here is an solution below for this:下面是一个解决方案:

$iId = (int) mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";

This question has some good answers about this.这个问题对此有一些很好的答案。

I suggest, using PDO is the best option.我建议,使用 PDO 是最好的选择。

Edit:编辑:

mysql_real_escape_string() is deprecated as of PHP 5.5.0. mysql_real_escape_string()自 PHP 5.5.0 起已弃用。 Use either mysqli or PDO.使用 mysqli 或 PDO。

An alternative to mysql_real_escape_string() is mysql_real_escape_string() 的替代方法是

string mysqli_real_escape_string ( mysqli $link, string $escapestr )

Example:例子:

 $iId = $mysqli->real_escape_string("1 OR 1=1"); $mysqli->query("SELECT * FROM table WHERE id = $iId");

A simple way would be to use a PHP framework like CodeIgniter or Laravel which have inbuilt features like filtering and active-record so that you don't have to worry about these nuances.一种简单的方法是使用 PHP 框架,例如CodeIgniterLaravel ,它们具有过滤和活动记录等内置功能,因此您不必担心这些。

Warning: the approach described in this answer only applies to very specific scenarios and isn't secure since SQL injection attacks do not only rely on being able to inject X=Y .警告:此答案中描述的方法仅适用于非常特定的场景并且不安全,因为 SQL 注入攻击不仅依赖于能够注入X=Y

If the attackers are trying to hack into the form via PHP's $_GET variable or with the URL's query string, you would be able to catch them if they're not secure.如果攻击者试图通过 PHP 的$_GET变量或 URL 的查询 string 侵入表单,如果它们不安全,您将能够捕获它们。

 RewriteCond %{QUERY_STRING} ([0-9]+)=([0-9]+) RewriteRule ^(.*) ^/track.php

Because 1=1 , 2=2 , 1=2 , 2=1 , 1+1=2 , etc... are the common questions to an SQL database of an attacker.因为1=1 , 2=2 , 1=2 , 2=1 , 1+1=2等...是攻击者的 SQL 数据库的常见问题。 Maybe also it's used by many hacking applications.也许它也被许多黑客应用程序使用。

But you must be careful, that you must not rewrite a safe query from your site.但是您必须小心,您不能从您的站点重写安全查询。 The code above is giving you a tip, to rewrite or redirect (it depends on you) that hacking-specific dynamic query string into a page that will store the attacker's IP address , or EVEN THEIR COOKIES, history, browser, or any other sensitive information, so you can deal with them later by banning their account or contacting authorities.上面的代码给了你一个提示,重写或重定向(这取决于你)特定于黑客的动态查询 string 到一个页面中,该页面将存储攻击者的IP 地址,或者甚至他们的 Z14A48E024A48E026A9EFDAE983信息,以便您以后可以通过禁止他们的帐户或联系当局来处理他们。

A good idea is to use an object-relational mapper like Idiorm :一个好主意是使用像Idiorm这样的对象关系映射器

 $user = ORM::for_table('user') ->where_equal('username', 'j4mie') ->find_one(); $user->first_name = 'Jamie'; $user->save(); $tweets = ORM::for_table('tweet') ->select('tweet.*') ->join('user', array( 'user.id', '=', 'tweet.user_id' )) ->where_equal('user.username', 'j4mie') ->find_many(); foreach ($tweets as $tweet) { echo $tweet->text; }

It not only saves you from SQL injections, but from syntax errors too.它不仅可以使您免于 SQL 注入,还可以避免语法错误。 It also supports collections of models with method chaining to filter or apply actions to multiple results at once and multiple connections它还支持具有方法链接的模型 collections 以一次过滤或将操作应用于多个结果和多个连接

There are so many answers for PHP and MySQL , but here is code for PHP and Oracle for preventing SQL injection as well as regular use of oci8 drivers: There are so many answers for PHP and MySQL , but here is code for PHP and Oracle for preventing SQL injection as well as regular use of oci8 drivers:

 $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);

Deprecated Warning: This answer's sample code (like the question's sample code) uses PHP's MySQL extension, which was deprecated in PHP 5.5.0 and removed entirely in PHP 7.0.0.不推荐使用的警告:此答案的示例代码(与问题的示例代码一样)使用 PHP 的MySQL扩展,该扩展在 PHP 5.5.0 中已弃用,并在 Z2FEC392304A5C23AC138DA.727.9B7 中完全删除。

Security Warning : This answer is not in line with security best practices.安全警告:此答案不符合安全最佳实践。 Escaping is inadequate to prevent SQL injection , use prepared statements instead. Escaping 不足以防止 SQL 注入,请改用准备好的语句 Use the strategy outlined below at your own risk.使用下面概述的策略,风险自负。 (Also, mysql_real_escape_string() was removed in PHP 7.) (此外,在 PHP 7 中删除了mysql_real_escape_string() 。)

Using PDO and MYSQLi is a good practice to prevent SQL injections, but if you really want to work with MySQL functions and queries, it would be better to use Using PDO and MYSQLi is a good practice to prevent SQL injections, but if you really want to work with MySQL functions and queries, it would be better to use

mysql_real_escape_string mysql_real_escape_string

 $unsafe_variable = mysql_real_escape_string($_POST['user_input']);

There are more abilities to prevent this: like identify - if the input is a string, number, char or array, there are so many inbuilt functions to detect this.有更多的功能可以防止这种情况:比如识别 - 如果输入是 string、数字、字符或数组,有很多内置函数可以检测到这一点。 Also, it would be better to use these functions to check input data.此外,最好使用这些函数来检查输入数据。

is_string is_string

 $unsafe_variable = (is_string($_POST['user_input'])? $_POST['user_input']: '');

is_numeric is_numeric

 $unsafe_variable = (is_numeric($_POST['user_input'])? $_POST['user_input']: '');

And it is so much better to use those functions to check input data with mysql_real_escape_string .使用这些函数通过mysql_real_escape_string检查输入数据要好得多。

I've written this little function several years ago:几年前我写过这个小 function:

 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 }

This allows running statements in an one-liner C#-ish String.Format like:这允许在单行 C#-ish String.Format 中运行语句,例如:

 runEscapedQuery("INSERT INTO Whatever (id, foo, bar) VALUES ({0}, {1}, {2})", $numericVar, $stringVar1, $stringVar2);

It escapes considering the variable type.考虑到变量类型,它会逃脱。 If you try to parameterize table, column names, it would fail as it puts every string in quotes which is an invalid syntax.如果您尝试参数化表、列名,它将失败,因为它将每个 string 放在引号中,这是一种无效的语法。

SECURITY UPDATE: The previous str_replace version allowed injections by adding {#} tokens into user data.安全更新:之前的str_replace版本允许通过将 {#} 标记添加到用户数据中来进行注入。 This preg_replace_callback version doesn't cause problems if the replacement contains these tokens.如果替换包含这些标记,则此preg_replace_callback版本不会导致问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM