prepare('SELECT * FROM users where username = :-6ren">
gpt4 book ai didi

php - PDO准备好的语句是否足以防止SQL注入(inject)?

转载 作者:行者123 更新时间:2023-11-30 22:41:06 31 4
gpt4 key购买 nike

假设我有这样的代码:

$dbh = new PDO("blahblah");

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


PDO文档说:


  准备好的语句的参数不需要用引号引起来。司机为您处理。


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

您可以假设MySQL会有所作为。另外,我真的只是对针对SQL注入使用准备好的语句感到好奇。在这种情况下,我不在乎XSS或其他可能的漏洞。

最佳答案

简短的回答是“否”,PDO准备将不会为您防御所有可能的SQL注入攻击。对于某些晦涩的边缘情况。

我正在修改this answer来谈论PDO ...

长答案不是那么容易。它基于攻击demonstrated here

攻击

因此,让我们开始展示攻击...

$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行以上。让我们剖析这里发生的事情:


选择字符集

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


为了使这种攻击起作用,我们需要服务器在连接时期望的编码既可以像ASCII中那样编码 ',即 0x27,又要具有某些字符,其最终字节是ASCII \0x5c。事实证明,默认情况下,MySQL 5.6默认支持5种此类编码: big5cp932gb2312gbksjis。我们将在此处选择 gbk

现在,在这里注意 SET NAMES的使用非常重要。这将在服务器上设置字符集。还有另一种方法,但是我们会尽快到达那里。
有效载荷

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

之所以选择此有效负载,是因为如果在其上调用 addslashes(),则会在 \字符之前插入一个ASCII 0x5c'。因此,我们将以 0xbf5c27结尾,在 gbk中它是两个字符的序列: 0xbf5c后跟 0x27。换句话说,就是一个有效字符,后跟一个未转义的 '。但是我们没有使用 addslashes()。继续下一步...
$ 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()的调用将插入反斜杠,并且我们的“转义”内容中有一个自由悬挂的 '字符!实际上,如果我们要查看 $var字符集中的 gbk,我们会看到:

縗' OR 1=1 /*

Which is exactly what the attack requires.

  • The Query

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

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



    恭喜,您刚刚使用PDO Prepared Statements成功攻击了一个程序...

    简单修复

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

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


    这通常会导致产生真正准备好的语句(即,将数据从与查询分开的数据包中发送出去)。但是,请注意,PDO会默默地fallback模拟MySQL本身无法准备的语句:手册中的listed都是可以的,但请注意选择合适的服务器版本。

    正确的解决方法

    这里的问题是我们没有调用C API的mysql_set_charset()而不是SET NAMES。如果这样做的话,如果我们从2006年开始使用MySQL版本,我们会很好的。

    如果您使用的是较早的MySQL版本,则mysql_real_escape_string()中的bug表示无效的多字节字符(例如我们的有效负载中的字符)出于转义目的被视为单个字节,即使已正确告知客户端连接编码因此这种攻击仍然会成功。该错误已在MySQL 4.1.205.0.225.1.11中修复。

    但是最糟糕的是,PDO直到5.3.6才公开mysql_set_charset()的C API,因此在以前的版本中,它无法针对所有可能的命令阻止这种攻击!
     现在显示为DSN parameter,应代替SET NAMES使用...

    拯救恩典

    正如我们在一开始所说的那样,要使这种攻击起作用,必须使用易受攻击的字符集对数据库连接进行编码。 utf8mb4并非易受攻击,但可以支持每个Unicode字符:因此您可以选择使用它,但是它仅自MySQL 5.5.3起可用。另一种选择是utf8,它也不易受到攻击,并且可以支持整个Unicode Basic Multilingual Plane

    或者,您可以启用NO_BACKSLASH_ESCAPES SQL模式,该模式(除其他外)会更改mysql_real_escape_string()的操作。启用此模式后,0x27将替换为0x2727而不是0x5c27,因此转义过程无法以以前不存在的任何易受攻击的编码创建有效字符(即0xbf27仍为 等),因此服务器仍会拒绝该字符串为无效字符串。但是,请参阅 @eggyal's answer,以了解使用此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");


    因为服务器期望 0xbf27 ...

    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中)


    要么


    不要使用易受攻击的字符集进行连接编码(您只能使用 utf8 / utf8 / latin1等)


    要么


    启用 ascii SQL模式


    您是100%安全的。

    否则,即使您使用的是PDO预准备语句,也容易受到攻击。

    附录

    我一直在缓慢地开发一个补丁程序,以更改默认值,以不模仿将来的PHP版本。我遇到的问题是,当我这样做时,很多测试都失败了。一个问题是,模拟的Prepare只会在执行时抛出语法错误,而真正的Prepare则会在Prepare上抛出错误。因此,这可能会导致问题(这是测试很乏味的部分原因)。

  • 关于php - PDO准备好的语句是否足以防止SQL注入(inject)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31136032/

    31 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com