Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
147 views
in Technique[技术] by (71.8m points)

php - 如何防止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?

(如何防止这种情况的发生?)

  ask by community wiki translate from so

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Use prepared statements and parameterized queries.

(使用准备好的语句和参数化查询。)

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(array('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 (正确设置连接)

Note that when using PDO to access a MySQL database real prepared statements are not used by default .

(请注意,使用PDO访问MySQL数据库时,默认情况下不使用 真实的预处理语句。)

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=utf8', '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 the script will not stop with a Fatal Error when something goes wrong.

(这样,当出现问题时,脚本不会因Fatal Error而停止。)

And it gives the developer the chance to catch any error(s) which are throw n as PDOException s.

(并且它为开发人员提供了catch作为PDOException throw n的任何错误的机会。)

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中默默地忽略了字符集参数 。)

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字符串组合。)

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结果仅是搜索字符串"'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.

(使用准备好的语句的另一个好处是,如果您在同一会话中多次执行同一条语句,它将仅被解析和编译一次,从而使您获得了一些速度上的提高。)

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(array('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';
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...