RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 637185
Accepted
Алексей Шиманский
Алексей Шиманский
Asked:2020-03-09 16:57:26 +0000 UTC2020-03-09 16:57:26 +0000 UTC 2020-03-09 16:57:26 +0000 UTC

准备好的语句/变量是否可以完全防止 SQL 注入?

  • 772

世界早已习惯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 也许这个问题已经被考虑过了,我不知道,提前抱歉。

php
  • 5 5 个回答
  • 10 Views

5 个回答

  • Voted
  1. Best Answer
    Ипатьев
    2020-03-14T22:09:23Z2020-03-14T22:09:23Z

    安全

    如果我们在请求中谈论数字和字符串文字,那么是的,它们会保护。

    就这样就够了吗,不需要做更多的事情了吗?

    一般来说,没什么。在 PDO 的情况下,也希望在 DSN 中设置连接编码,但无论如何都应该这样做。

    Prepared expressions的思想不是将数据和查询分开发送,而是不是由程序员,而是由数据库驱动向查询添加数据。而它是如何在他体内实现的是第十件事。

    注意
    在受人尊敬的 vp_arth 的敦促下,我应该补充一点,理论上 可以专门调整您的系统,使其在仿真模式下跳过注入。这将需要两件事:

    • 通过设置NO_BACKSLASH_ESCAPES模式以特殊方式配置 mysql
    • 使用双引号而不是单引号作为字符串终止符

    这是 mysql 中的一个错误,最近才得到修复。您可以在 SO 上的这篇文章中阅读更多内容。

    限制

    这里更有趣的是当准备好的表达式不能使用时该怎么做的问题。如上所述,查询参数只能用于替换字符串或数字文字。但在某些情况下,需要替换的不是数据,而是查询中列的名称。以下是分析不正确决策的这种情况的示例(英文):An SQL injection against which prepared statements won't help。
    这种情况很少见,但您需要了解并做好准备。

    方便

    毕竟,准备好的表达式更方便。比较老派

    $name = $mysqli->real_escape_sring($_GET['name']);
    $price = $mysqli->real_escape_sring($_GET['price']);
    $color = $mysqli->real_escape_sring($_GET['color']);
    
    $sql = "SELECT * FROM goods WHERE name='$name' and color='$color' and price > '$price'";
    $res = $mysqli->query($sql);
    

    和 PDO 准备语句

    $stmt = $pdo->prepare("SELECT * FROM goods WHERE name = ? and color = ? and price > ?");
    $stmt->execute([$_GET['name'],$_GET['price'],$_GET['color']]);
    

    ——紧凑、整洁、安全。

    屏蔽神话

    关于“像 mysqli_real_escape_string 这样的结构”的单独说明。问题是,与准备好的语句不同,这些结构与防止 SQL 注入无关。该结构执行定义明确且非常专业的句法功能。使用它“用于注入保护”肯定会导致这种注入。

    上面的链接给出了这种滥用的一个例子,但你也可以举另一个完全愚蠢但更能说明问题的例子:

    $id = $mysqli->real_escape_string($_GET['id']);
    $sql = "SELECT * FROM table WHERE id=$id";
    

    如果您认为该函数是为“注入保护”服务的,那么这段代码是合乎逻辑的。然而,实际上,它可以通过 UNION 进一步归因于几乎任何查询并获得经典注入。

    同时,您不需要达到另一个极端 -用于其预期目的,用于转义字符串中的特殊字符,mysqli_real_escape_string 可以很好地应对注入,只是作为副作用。

    • 29
  2. Мелкий
    2020-03-16T17:33:29Z2020-03-16T17:33:29Z

    简要地

    安全上下文中的机制prepared statements只会确保请求本身及其参数值从代码到 DBMS 的正确传输。不会少,但不会多。射击腿仍然是可能的。但这比您尝试将数据直接替换到请求中要困难得多。


    要回答安全问题,首先要了解危险是什么。

    什么是 sql 注入,它们是如何发生的?

    $pdo->query("select * from users where login = '" . $login ."' 
        and pass_md5 = '" . md5($pass) . "'");
    

    开发人员在这里看到了什么?将数据替换为字符串。从 PHP 的角度来看,这里没有任何危险。从一个没有经验的 PHP 开发人员检查他的代码的角度来看,代码是有效的,因为他没有想到在登录中写一些奇怪的东西。

    当有人进入而不是登录时,冒险就开始了admin' or '1'='1。这里有必要回忆一下,SQL是一个基于文本的东西。DBMS 在 PHP 中连接后会得到什么?

    select * from users where login = 'admin' or '1'='1' 
        and pass_md5 = 'какой-тоmd5'
    

    这是一行连续的文本。DBMS 应该如何理解这个查询与预期的查询不同?这是一个查询,它在语法上是正确的,它可以被执行——DBMS 执行它。但是请求已经不是开发者想说的了。

    SQL注入的危险和流行恰恰在于查询的文本实体。在正确的位置用数据替换变量非常容易 - 但这是出错的途径,您不能这样做。


    现在保护。

    在 SQL 本身中,最初提供的是,为了在查询中通过文字正确表示字符串,有必要以某种方式对某些字节进行编码。在大多数情况下,转义引号:

    select * from users where login = 'admin\' or \'1\'=\'1' 
        and pass_md5 = 'какой-тоmd5'
    

    现在解析器知道数据在登录行中结束的位置。同时,客户端和服务端编码的一致性很关键,否则在某些情况下是有可能被攻破的。

    另一方面,该机制解决了这个问题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 机制来准备请求,然后在单独的调用中传输该请求的数据。那些。正常的,真正准备好的表达。

    从非显而易见的点:

    • 对于复杂的查询,在查询中多次使用同一个命名参数非常方便。Anywhere user_from = :id or user_to = :id并且只传入一次 id 参数列表。其实这取决于具体驱动的实现,比如mysql这样的请求在关闭仿真时会失败报错Invalid parameter number,但在.h中也会正确执行PostgreSQL。启用仿真 - 因此,无论 DBMS 是什么,它都可以使用。
    • 特定 DBMS 的特殊性。PDO 可以做的一些 DBMS 可能无法准备语句。比如一个很流行的PgBouncer(connection pool for PostgreSQL)就不能处理prepared statements。通过表达式模拟,您可以在项目代码中使用 API 和 prepare 的便利
    • 准备好的查询被解析并构建一次计划然后仅执行这一事实的问题实际上要复杂得多。同样mysql保存请求只在连接范围内。因此,在“准备、执行、关闭连接”的典型用例中,真正的准备并没有带来任何优势。在连接之间缓存计划——在我看来(我自己没有使用过这个 DBMS),会Oracle带来一定程度的麻烦,因为最佳查询计划在很大程度上取决于数据本身。

    让我来说明在简单队列上缓存查询计划的麻烦。在status一栏,status只有十几条,statuswaiting有几百万条done。请求来到数据库:

    select /**/ from tablename where status = ? order by id limit ?
    

    问题是,没有数据本身,数据库如何猜测最优方案?如果她收到 status = waiting 的请求,最好按照状态索引进行。如果一个请求到达完成状态,还有几个选项:有一个小的限制,通过 id 索引并简单地丢弃状态不合适的记录是有意义的。


    然后把你的头重新打开

    比如有一个请求:

    $stmt = $pdo->prepare('update users_balance 
        set balance = balance - :amount 
        where user_id = :uid and balance > :amount');
    $stmt->execute([
        'uid' => $userId,
        'amount' => $_POST['payment'],
    ]);
    

    就这样离开它安全吗?不。将其传递给payment负数,您将毫无问题地收到应计款项,而不是借记。请求逻辑没有被破坏,数据按原样传输,没有被扭曲——但是应用程序的业务逻辑被破坏了。

    所以,还是要查资料。并且不会有任何灵丹妙药 - 是你自己知道,它在代码中的特定位置 - 是否应该只有一个负数。或登录字符串或其他内容。并且该机制prepared statements只会确保请求本身和值从代码到 DBMS 的正确传输,仅此而已。

    这里值得一提的是请求排序的相同变化。机制prepared statements不会让你这样做,但你事先知道只有两个排序方向:ascor desc,你也事先知道选择可以按哪些字段排序。因此,排序方向只是根据可能值的白名单进行检查,不会出现问题。


    关于性能的更多信息

    动态组装请求结构似乎很危险,一般不可能,而且每次都重新解析请求很慢。这是一个神话。结果是一个像这样的大查询

    SELECT first_name, last_name, subsidiary_id, employee_id
      FROM employees
     WHERE ( subsidiary_id    = :sub_id OR :sub_id IS NULL )
       AND ( employee_id      = :emp_id OR :emp_id IS NULL )
       AND ( UPPER(last_name) = :name   OR :name   IS NULL )
    

    为了不使用任何搜索条件,只需将其参数指定为 NULL 即可。是的,我们在查询解析器上省了钱,但让优化器的生活变得极其困难。没有真正的查询数据,优化器被迫只使用整个表的顺序读取。但是如果把参数化查询换成值字面量,那么优化器就相当明白他们想从他这里得到什么,并使用更易理解的计划。那些。通过这样的请求,prepared statements我们并没有提高应用程序的性能,而是从根本上杀死了它。

    • 21
  3. vp_arth
    2020-03-09T17:19:57Z2020-03-09T17:19:57Z

    如果数据库支持单独的请求和数据传输,并且数据库驱动程序使用此功能,那么您是安全的。

    与请求分开传输的数据不会以任何方式影响原始请求。

    但是,存在“模拟准备好的表达式”( Emulated Prepared Statements) 之类的东西。
    如果基础不支持真实的prepared statements,或者如果我们有意启用此“功能”(在某些驱动程序中可能默认启用),则使用它们。

    在仿真的情况下,数据库不知道数据在哪里,请求在哪里。对于数据转义,我们完全依赖于特定驱动程序 ( pdo_firebird, pdo_mysql) 中此仿真的实现。

    因此,如果您尚未验证此连接选项是否存在,请确保并明确禁用它:

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

    如果您需要使用用户数据而不是作为数据,而是作为表/字段名称 - 100% 安全的唯一方法是白名单。
    永远不要直接在查询中使用用户数据,为此从允许的插入列表中将必要的插入到请求中。

      $i = array_search($_GET['field'], User::$orderFields, true);
      $orderField = $i === false ? User::$defaultOrderField : User::$orderFields[$i];
      $sql = "... ORDER BY $orderField";  
    

    最好不要从用户那里接受字段/表的名称,而是接受此类白名单中的索引。


    来自下一个问题的讨论:

    @vp_arth:实际上,在查询中使用双引号字符串的示例会更有趣,而real_escape_string默认值隐藏为撇号

    我的意思是这个例子:

    /* my.ini
    [mysqld]
    sql-mode="NO_BACKSLASH_ESCAPES";
    
    Или вызов из php:*/
    $db->query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"');
    
    $input = '" OR id = "1';
    $input = $db->real_escape_string($input);
    
    echo 'SELECT * FROM Users WHERE login = "'.$input.'"';
    // SELECT * FROM Users WHERE login = "" OR id = "1"
    

    @vp_arth:没有比上面的更具保护性real_escape_string

    我的意思是问题中的以下陈述:

    @Ipatiev:此构造执行定义明确且非常专业的句法功能

    在模拟准备好的表达式的情况下,它们也是"выполняют строго определенную и очень специализированную синтаксическую функцию",在一般情况下可以通过
    合成示例绕过。
    在现实生活中,我发现emulated prepared statementsin可以根据行中的字节数pdo_firebird下降。Segmentation faultutf-8

    • 12
  4. Aleksei
    2020-03-09T17:10:00Z2020-03-09T17:10:00Z

    准备好的查询

    1. 增加安全性,
    2. 无需转义引号和其他特殊字符,
    3. 完全不需要用引号将插入的字符串值括起来,
    4. 通过在服务器端缓存查询执行计划来提高性能,
    5. 增加代码的可读性。

    因此,我是倡导广泛使用这种方法的人之一。

    如果我们谈论无论请求如何发出都必须执行的萨满教,那么这就是从来自用户的数据中删除 HTML 标记 ( http://www.php.net/Strip_tags )。这并不总是真正必要的,但是在某些情况下,例如,保存论坛帖子,忽略此功能可能会破坏整个页面的标记或导致为所有打开该页面的用户执行不需要的 js 代码。但这并不直接适用于查询执行。

    • 2
  5. user190134
    2020-03-16T16:28:48Z2020-03-16T16:28:48Z

    我看到他们如何在许多自写引擎上通过 PDO 愚蠢地从 $ _POST 中筛选出来,并将其放入数据库中。是的,一切似乎都还好。但。

    但是在这里我们正在等待 XSS!

    查看线路

    <script>console.log( document.cookie ) </script>
    

    即使通过 PDO 筛选,它仍然会进入数据库,然后遭受你在哪里和如何搞砸了。

    • -2

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    Python 3.6 - 安装 MySQL (Windows)

    • 1 个回答
  • Marko Smith

    C++ 编写程序“计算单个岛屿”。填充一个二维数组 12x12 0 和 1

    • 2 个回答
  • Marko Smith

    返回指针的函数

    • 1 个回答
  • Marko Smith

    我使用 django 管理面板添加图像,但它没有显示

    • 1 个回答
  • Marko Smith

    这些条目是什么意思,它们的完整等效项是什么样的

    • 2 个回答
  • Marko Smith

    浏览器仍然缓存文件数据

    • 1 个回答
  • Marko Smith

    在 Excel VBA 中激活工作表的问题

    • 3 个回答
  • Marko Smith

    为什么内置类型中包含复数而小数不包含?

    • 2 个回答
  • Marko Smith

    获得唯一途径

    • 3 个回答
  • Marko Smith

    告诉我一个像幻灯片一样创建滚动的库

    • 1 个回答
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Алексей Шиманский 如何以及通过什么方式来查找 Javascript 代码中的错误? 2020-08-03 00:21:37 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    user207618 Codegolf——组合选择算法的实现 2020-10-23 18:46:29 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5