世界早已习惯mysqli和PDO。许多人非常积极地推广它们:有准备好的变量,一切都变得安全,等等。
在这里,假设有一个抽象代码:
$dbh = new PDO("test");
$stmt = $dbh->prepare('SELECT * FROM users where username = :username');
$stmt->execute(array(':username' => $_REQUEST['username']));
或者
$dbh = new PDO("test");
$sth = $dbh->prepare('SELECT name, colour, calories FROM fruit WHERE calories < ? AND color = ?');
$sth->execute(array($_POST['number'], $_POST['color']));
和所有...
就这样就够了吗,不需要做更多的事情了吗?没有类型结构mysqli_real_escape_string(对于 mysqli)和其他萨满教?或者仍然不是?
实际上,我想知道如果上面的代码不保护,那么如何正确地处理带有PDOand的请求mysqli(以及为什么他们会谈论安全性)?有哪些关于如何使用PDOand安全地执行查询的明确示例mysqli?如果真相得到保护,那么……我很震惊))
PS 也许这个问题已经被考虑过了,我不知道,提前抱歉。
安全
如果我们在请求中谈论数字和字符串文字,那么是的,它们会保护。
一般来说,没什么。在 PDO 的情况下,也希望在 DSN 中设置连接编码,但无论如何都应该这样做。
Prepared expressions的思想不是将数据和查询分开发送,而是不是由程序员,而是由数据库驱动向查询添加数据。而它是如何在他体内实现的是第十件事。
注意
在受人尊敬的 vp_arth 的敦促下,我应该补充一点,理论上 可以专门调整您的系统,使其在仿真模式下跳过注入。这将需要两件事:
这是 mysql 中的一个错误,最近才得到修复。您可以在 SO 上的这篇文章中阅读更多内容。
限制
这里更有趣的是当准备好的表达式不能使用时该怎么做的问题。如上所述,查询参数只能用于替换字符串或数字文字。但在某些情况下,需要替换的不是数据,而是查询中列的名称。以下是分析不正确决策的这种情况的示例(英文):An SQL injection against which prepared statements won't help。
这种情况很少见,但您需要了解并做好准备。
方便
毕竟,准备好的表达式更方便。比较老派
和 PDO 准备语句
——紧凑、整洁、安全。
屏蔽神话
关于“像 mysqli_real_escape_string 这样的结构”的单独说明。问题是,与准备好的语句不同,这些结构与防止 SQL 注入无关。该结构执行定义明确且非常专业的句法功能。使用它“用于注入保护”肯定会导致这种注入。
上面的链接给出了这种滥用的一个例子,但你也可以举另一个完全愚蠢但更能说明问题的例子:
如果您认为该函数是为“注入保护”服务的,那么这段代码是合乎逻辑的。然而,实际上,它可以通过 UNION 进一步归因于几乎任何查询并获得经典注入。
同时,您不需要达到另一个极端 -用于其预期目的,用于转义字符串中的特殊字符,mysqli_real_escape_string 可以很好地应对注入,只是作为副作用。
简要地
安全上下文中的机制
prepared statements只会确保请求本身及其参数值从代码到 DBMS 的正确传输。不会少,但不会多。射击腿仍然是可能的。但这比您尝试将数据直接替换到请求中要困难得多。要回答安全问题,首先要了解危险是什么。
什么是 sql 注入,它们是如何发生的?
开发人员在这里看到了什么?将数据替换为字符串。从 PHP 的角度来看,这里没有任何危险。从一个没有经验的 PHP 开发人员检查他的代码的角度来看,代码是有效的,因为他没有想到在登录中写一些奇怪的东西。
当有人进入而不是登录时,冒险就开始了
admin' or '1'='1。这里有必要回忆一下,SQL是一个基于文本的东西。DBMS 在 PHP 中连接后会得到什么?这是一行连续的文本。DBMS 应该如何理解这个查询与预期的查询不同?这是一个查询,它在语法上是正确的,它可以被执行——DBMS 执行它。但是请求已经不是开发者想说的了。
SQL注入的危险和流行恰恰在于查询的文本实体。在正确的位置用数据替换变量非常容易 - 但这是出错的途径,您不能这样做。
现在保护。
在 SQL 本身中,最初提供的是,为了在查询中通过文字正确表示字符串,有必要以某种方式对某些字节进行编码。在大多数情况下,转义引号:
现在解析器知道数据在登录行中结束的位置。同时,客户端和服务端编码的一致性很关键,否则在某些情况下是有可能被攻破的。
另一方面,该机制解决了这个问题
prepared statements:我们对应用程序的请求数量有限,但使用不同的数据。因此,prepared statements我们决定将请求结构和数据显式分离。现在,应用程序不再是“你好,在这里运行这个查询”的情况,而是通过一个特殊的协议告诉 DBMS“你好,准备好执行这个查询”,然后:“还记得那个查询已经准备好了吗?现在执行它并使用它作为第一个参数 - 这些是以下 8 个字节,对于第二个参数 - 接下来的 20 个字节”。查询数据在物理上与查询结构分开传输,因此 DBMS 不可能将数据与查询混淆,即使没有发生转义。重要的一点 - 这就是为什么通过机制prepared statemenets改变查询的结构是不可能的,即使改变排序的方向也是老生常谈order by。数据将按原样传输,数据本身或请求结构都不会失真,一切都很好。
但这就够了吗?
首先,让我们谈谈默认启用的准备好的表达式模拟。只要您正确设置连接编码并提供一些奖励,这并不可怕。但是,如果编码不正确,那么通过编码进行的类似于上述攻击的攻击又是真实的。
同理
PDO::ATTR_EMULATE_PREPARES,truePDO本身负责处理prepared statements。一个干净的请求被发送到数据库,其中的数据已经被替换并正确转义(只是dsn不要忘记charset正确指定它)。PDO::ATTR_EMULATE_PREPARES也就是说false,它使用常规的 DBMS 机制来准备请求,然后在单独的调用中传输该请求的数据。那些。正常的,真正准备好的表达。从非显而易见的点:
where user_from = :id or user_to = :id并且只传入一次 id 参数列表。其实这取决于具体驱动的实现,比如mysql这样的请求在关闭仿真时会失败报错Invalid parameter number,但在.h中也会正确执行PostgreSQL。启用仿真 - 因此,无论 DBMS 是什么,它都可以使用。PgBouncer(connection pool forPostgreSQL)就不能处理prepared statements。通过表达式模拟,您可以在项目代码中使用 API 和 prepare 的便利mysql保存请求只在连接范围内。因此,在“准备、执行、关闭连接”的典型用例中,真正的准备并没有带来任何优势。在连接之间缓存计划——在我看来(我自己没有使用过这个 DBMS),会Oracle带来一定程度的麻烦,因为最佳查询计划在很大程度上取决于数据本身。让我来说明在简单队列上缓存查询计划的麻烦。在status一栏,status只有十几条,status
waiting有几百万条done。请求来到数据库:问题是,没有数据本身,数据库如何猜测最优方案?如果她收到 status = waiting 的请求,最好按照状态索引进行。如果一个请求到达完成状态,还有几个选项:有一个小的限制,通过 id 索引并简单地丢弃状态不合适的记录是有意义的。
然后把你的头重新打开
比如有一个请求:
就这样离开它安全吗?不。将其传递给
payment负数,您将毫无问题地收到应计款项,而不是借记。请求逻辑没有被破坏,数据按原样传输,没有被扭曲——但是应用程序的业务逻辑被破坏了。所以,还是要查资料。并且不会有任何灵丹妙药 - 是你自己知道,它在代码中的特定位置 - 是否应该只有一个负数。或登录字符串或其他内容。并且该机制
prepared statements只会确保请求本身和值从代码到 DBMS 的正确传输,仅此而已。这里值得一提的是请求排序的相同变化。机制
prepared statements不会让你这样做,但你事先知道只有两个排序方向:ascordesc,你也事先知道选择可以按哪些字段排序。因此,排序方向只是根据可能值的白名单进行检查,不会出现问题。关于性能的更多信息
动态组装请求结构似乎很危险,一般不可能,而且每次都重新解析请求很慢。这是一个神话。结果是一个像这样的大查询
为了不使用任何搜索条件,只需将其参数指定为 NULL 即可。是的,我们在查询解析器上省了钱,但让优化器的生活变得极其困难。没有真正的查询数据,优化器被迫只使用整个表的顺序读取。但是如果把参数化查询换成值字面量,那么优化器就相当明白他们想从他这里得到什么,并使用更易理解的计划。那些。通过这样的请求,
prepared statements我们并没有提高应用程序的性能,而是从根本上杀死了它。如果数据库支持单独的请求和数据传输,并且数据库驱动程序使用此功能,那么您是安全的。
与请求分开传输的数据不会以任何方式影响原始请求。
但是,存在“模拟准备好的表达式”(
Emulated Prepared Statements) 之类的东西。如果基础不支持真实的
prepared statements,或者如果我们有意启用此“功能”(在某些驱动程序中可能默认启用),则使用它们。在仿真的情况下,数据库不知道数据在哪里,请求在哪里。对于数据转义,我们完全依赖于特定驱动程序 (
pdo_firebird,pdo_mysql) 中此仿真的实现。因此,如果您尚未验证此连接选项是否存在,请确保并明确禁用它:
如果您需要使用用户数据而不是作为数据,而是作为表/字段名称 - 100% 安全的唯一方法是白名单。
永远不要直接在查询中使用用户数据,为此从允许的插入列表中将必要的插入到请求中。
最好不要从用户那里接受字段/表的名称,而是接受此类白名单中的索引。
来自下一个问题的讨论:
我的意思是这个例子:
我的意思是问题中的以下陈述:
在模拟准备好的表达式的情况下,它们也是
"выполняют строго определенную и очень специализированную синтаксическую функцию",在一般情况下可以通过合成示例绕过。
在现实生活中,我发现
emulated prepared statementsin可以根据行中的字节数pdo_firebird下降。Segmentation faultutf-8准备好的查询
因此,我是倡导广泛使用这种方法的人之一。
如果我们谈论无论请求如何发出都必须执行的萨满教,那么这就是从来自用户的数据中删除 HTML 标记 ( http://www.php.net/Strip_tags )。这并不总是真正必要的,但是在某些情况下,例如,保存论坛帖子,忽略此功能可能会破坏整个页面的标记或导致为所有打开该页面的用户执行不需要的 js 代码。但这并不直接适用于查询执行。
我看到他们如何在许多自写引擎上通过 PDO 愚蠢地从 $ _POST 中筛选出来,并将其放入数据库中。是的,一切似乎都还好。但。
但是在这里我们正在等待 XSS!
查看线路
即使通过 PDO 筛选,它仍然会进入数据库,然后遭受你在哪里和如何搞砸了。