php - 如何防止在PHP中SQL注入?

  显示原文与译文双语对照的内容
0 0

如果将用户输入插入到SQL查询中而不进行修改,那么应用程序就会受到 SQL注入的影响,如以下示例所示:

$unsafe_variable = $_POST['user_input']; 
mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");

那是因为用户可以输入一些东西 value'); DROP TABLE table;-- ,查询变为:

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

可以做什么来防止这种情况发生?

时间:原作者:33个回答

0 0

预先准备好的语句和参数化查询。 这些是与数据库服务器分开发送和解析的SQL语句。 这样攻击者就无法注入恶意的SQL 。

你基本上有两个选项可以实现:

  1. 使用 PDO:

    $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    $stmt->execute(array('name' => $name));
    foreach ($stmt as $row) {
    //do something with $row
    }
  2. 使用反病毒程序:

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

正确设置连接

注意,当使用 PDO 访问mysql数据库真正预备语句被默认不习惯。 要解决这个问题,你必须禁用对prepared语句的模拟。 使用PDO创建连接的示例如下:

$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass');
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

在上面的例子中错误模式不是严格必需的,建议添加它。 这样,当出现错误时,脚本不会用 Fatal Error 停止。 让开发人员有机会 catchthrow n在 PDOException 年代的任何错误。

强制性不过是第一个 setAttribute() 线,它告诉pdo禁用模拟预处理语句和使用真实准备语句。 这就确保了在将语句发送到MySQL服务器( 给一个可能的攻击者注入恶意 SQL ) 之前,它没有被PHP解析。

虽然可以在构造函数的选项中设置 charset,但是要注意'旧的'( <5.3.6 ) 的版本在DSN中忽略了字符集参数

说明

发生的情况是你传递给 prepare的SQL语句被数据库服务器解析并编译。 通过指定参数( 上例中的? 或者命名参数如 :name ),你可以告诉数据库引擎你想在哪里筛选。 然后调用 execute 时,准备好的语句与指定的参数值组合。

重要的是,参数值与编译语句组合,而不是一个SQL字符串。 SQL注入 通过将脚本创建为将SQL发送到数据库时包含恶意字符串来工作。 所以分开发送实际的sql参数,你限制结束的风险你无意的东西。 使用准备好的语句时发送的任何参数都将被视为字符串( 尽管数据库引擎可能会做一些优化,但参数也可能作为数字结束,当然) 。 在上面的示例中,如果 $name 变量包含 'Sarah'; DELETE FROM employees 结果只是搜索字符串 "'Sarah'; DELETE FROM employees" ,你将不会使用一个空表

使用预准备语句的另一个好处是,如果在同一个会话中多次执行同一个语句,它只会被解析和编译一次,给你一些速度提升。

哦,既然你问了如何做一个插入,下面是一个示例( 使用 PDO ):

$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');
$preparedStatement->execute(array('column' => $unsafeValue));
原作者:
0 0

你有两个选项- 转义 unsafe_variable 中的特殊字符或者使用参数化查询。 两者都将保护你免受 SQL注入 攻击。 参数化查询被认为是更好的实践,但是在变量中转义字符需要更少的修改。

我们先做一个简单的字符串转义。

//Connect
$unsafe_variable = $_POST["user-input"];
$safe_variable = mysql_real_escape_string($unsafe_variable);
mysql_query("INSERT INTO table (column) VALUES ('". $safe_variable."')");
//Disconnect

参见 mysql_real_escape_string 函数的详细信息。


警告:

在php 5.5.0 mysql_real_escape_string弃用mysql 扩展。 请使用 mysqli 扩展和 mysqli::escape_string 函数


要使用参数化查询,你需要使用反序列化而不是 MySQL函数。 要重写你的示例,我们需要如下内容。

<?php
 $mysqli = new mysqli("server","username","password","database_name");
//TODO - Check that connection was successful.
 $unsafe_variable = $_POST["user-input"];
 $stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");
//TODO check that $stmt creation succeeded
//"s" means the database expects a string
 $stmt->bind_param("s", $unsafe_variable);
 $stmt->execute();
 $stmt->close();
 $mysqli->close();
?>

要在上面读取的键函数是 mysqli::prepare

也像其他人说的,你可能会发现它有用的/更容易加强与类似的一个抽象层 PDO

请注意,你所询问的案例是一个相当简单的案例,而且更复杂的案例可能需要更复杂的方法。 特别是:

  • 如果你想更改基于用户输入的SQL结构,参数化查询将不会得到帮助,并且所需的转义不会被 mysql_real_escape_string 覆盖。 在这种情况下,你最好通过白名单传递用户的输入,以确保仅允许'安全'值通过。
  • 如果在一个条件中使用来自用户输入的整数,并且采用了 mysql_real_escape_string 方法,那么在下面的注释中,你会遇到多项式描述的问题。 这种情况比较棘手,因为整数不会被引号包围,所以你可以通过验证用户输入是否只包含数字来处理。
  • 可能还有其他情况我不知道。 你可能会发现 http://webappsec.org/projects/articles/091007.txt 在一些更细微的问题上提供了有用的资源。
原作者:
0 0

我建议使用 PDO ( PHP数据对象) 来运行参数化的SQL查询。

这不仅保护了 SQL注入,它也加速了查询。

mysql_ than by usingpdo拉, mysqli_, 和 pgsql_ 职务,youmakeyour应用小abstracted from more indatabase,rare废物,你有 switch databaseto提供者。

原作者:
0 0

这里的每个答案只涉及问题的一部分。
事实上,有不同的查询部分我们可以动态地添加到:

  • 字符串
  • 一个数字
  • 标识符
  • 语法关键字。

准备好的语句只覆盖其中的2个

但有时我们必须使查询更加动态,添加操作符或者标识符。
所以,我们需要不同的保护技术。

一般来说,这种保护方法是基于的白名单。 在这种情况下,每个动态参数都应该硬编码到脚本中,并从。
例如要执行动态排序:

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

但是,还有另一种方法来保护标识符- 转义。 只要你有一个标识符引用,你就可以通过双击它们来转义反引号。

作为更进一步的一步,我们可以借用一个在准备语句中使用一些占位符( 代表查询中实际值的代理)的绝妙思想,并创建另一个类型的占位符- 一个标识符占位符。

因此,长话短说: 占位符,不是预备语句可以视为一个银弹。

因此,一般建议可以表述为
只要你使用占位符( 这些占位符理所当然地处理好了) 向查询添加动态部分,你可以确保你的查询是安全的。

( 例如 ANDDESC 等) 语法关键字仍然存在问题,但白名单似乎是这种情况下的唯一方法。

原作者:
0 0

使用PDO和准备好的查询。

( $conn是一个PDO对象)

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

就像你所见,人们建议你在最多的时候使用准备好的语句。 这并没有错,但是当你执行查询每一次过程中,会有轻微的性能损失。

我正面临这个问题,但我想我解决了它在非常复杂的方法- 黑客用来避免使用引号的方式。 我在仿真准备的语句中使用了这个。 我用它来防止类型的所有可能 SQL注入 攻击。

我的方法:

  • 如果你期望输入整数确保真的整数。 在像PHP这样的variable-type语言中,它是非常重要的 你可以使用这个非常简单但功能强大的解决方案: sprintf("SELECT 1,2,3 FROM table WHERE 4 = %u", $input);

  • 如果你期望任何其他的整数十六进制,它 。 如果你诅咒它,你将完全摆脱所有的输入。 在 C/C++ 中有一个名为 mysql_hex_string()的函数,在PHP中,你可以使用 bin2hex()

    不要担心转义字符串的大小为 2 x,因为即使你使用 mysql_real_escape_string,PHP也必须分配相同的容量 ((2*input_length)+1),这是相同的。

  • 当你传输二进制数据时,通常使用这个十六进制方法,但是我没有理由在所有数据上使用它来防止 SQL注入 攻击。 请注意,你必须在 0x 中添加数据或者使用MySQL函数 UNHEX

例如查询:

SELECT password FROM users WHERE name = 'root'

将成为:

SELECT password FROM users WHERE name = 0x726f6f74

或者

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

Hex是完美的逃避。 无法插入。

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

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

0 x 前缀只能用于数据列如char、varchar、文本块,二进制等
如果你要插入一个空字符串,它的使用也有点复杂。 你必须用 '' 完全替换它,否则你会得到一个错误。

UNHEX()任何列工作;你不必担心空字符串。


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

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

例如如果你只执行以下操作:

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

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

选择。在id= -1 unionall从 information_schema.tables 选择table_name

现在只提取表结构:

选择。。处的标识= -1联合所有从 information_schema.column 选择column_name从 table_name = 0x61727469636c65

然后选择任何需要的数据。 酷不是它?

但如果注射站点的编码器将十六进制,没有注射可能因为查询看起来像这样: SELECT.. . WHERE id = UNHEX('2d312075...3635')

原作者:
0 0

注入预防- mysql_real_escape_string()

PHP有一个specially-made函数来防止这些攻击。 你需要做的就是使用一个函数,mysql_real_escape_string

mysql_real_escape_string 接受一个字符串,它将在MySQL查询中使用,并返回相同的字符串,所有 SQL注入 尝试都会安全转义。 基本上,它将替换那些麻烦的quotes('),用户可以用MySQL-safe替换,转义的引号 '.

注意:你必须使用这个函数连接到数据库!

//连接到 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;

你可以在 MySQL - SQL注入 防止 中找到更多细节。

原作者:
0 0

你可以做一些基本的事情:

$safe_variable = mysql_real_escape_string($_POST["user-input"]);
mysql_query("INSERT INTO table (column) VALUES ('". $safe_variable."')");

这不会解决每一个问题,但它是一个很好的踏脚石。 我遗漏了一些明显的项目,比如检查变量的存在,格式化( 数字,字母,等等 ) 。

原作者:
0 0

无论你做什么使用,确保你检查你输入尚未被 magic_quotes 支离破碎或其他well-meaning垃圾,如果有必要,运行它通过 stripslashes sanitise之类的。

原作者:
0 0

参数化查询和输入验证是。 有许多 SQL注入 可能发生的场景,尽管 mysql_real_escape_string() 一直使用。

这些示例易受 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");

或者

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

在这两种情况下,你都不能使用 ' 来保护封装。

。: - 意外的SQL注入 ( 当转义不足时)

原作者:
...