返回介绍

4.4 SQL 调用相关的安全隐患

发布于 2024-10-10 22:16:31 字数 23362 浏览 0 评论 0 收藏 0

大多数 Web 应用都使用 SQL 来访问关系型数据库。但如果程序中使用 SQL 语句访问数据库的实现代码不完善,就会产生 SQL 注入漏洞。接下来,本节就将对 SQL 注入漏洞做一介绍。

4.4.1 SQL 注入

概要

SQL 注入漏洞是由于 SQL 语句的调用方法不完善而产生的安全隐患。一旦应用中存在 SQL 注入漏洞,就可能会造成如下影响。值得注意的是,以下影响中攻击者都能够直接对服务器实施主动攻击,而不需要用户的参与。

  • 数据库内的信息全部被外界窃取
  • 数据库中的内容被篡改
  • 登录认证被绕过(应用程序登录不需要用户名和密码)
  • 其他,例如服务器上的文件被读取或修改、服务器上的程序被执行等

可见 SQL 注入漏洞的破坏力极大,因此,作为程序开发人员,在编程时务必要确保不引入 SQL 注入漏洞。其中,使用静态占位符调用 SQL 语句就是一种有效的对策。详情请参考本节的“对策”。

SQL 注入漏洞总览

攻击手段与影响

接下来,笔者将使用示例脚本来讲解 SQL 注入攻击的方法与影响。

  • 示例脚本解说

    以下 PHP 脚本的作用为检索数据库(PostgreSQL)内的图书库存,该脚本含有 SQL 注入漏洞。

    代码清单 /44/44-001.php

    <?php
      header('Content-Type: text/html; charset=UTF-8');
      $author = $_GET['author'];
      $con = pg_connect("host=localhost dbname=wasbook user=postgres password=wasbook");
      $sqlstm = "SELECT id, title, author, publisher, date, price FROM books WHERE author ='$author' ORDER BM id";
      $rs = pg_query($con, $sqlstm);
    ?>
    <body>
    <table border=1>
    <tr>
    <th> 图书 ID</th><th> 书名 </th><th> 作者名 </th><th> 出版社 </th>
    <th> 出版年份 </th><th> 价格 </th>
    </tr>
    <?php
      $maxrows = pg_num_rows($rs);
      for ($i = 0; $i < $maxrows; $i++) {
        $row = pg_fetch_row($rs, $i); echo "<tr>\n";
        for ($j = 0; $j < 6; $j++) {
          echo "<td>" . $row[$j] . "</td>\n";
        }
        echo "</tr>\n";
      }
      pg_close($con);
    ?>
    </table>
    </body>
    
    

    首先来看一下正常情况下脚本的运行情况,比如,使用如下 URL 来检索作者为 Shakespeare 的图书。

    http://example.jp/44/44-001.php?author=Shakespeare
    
    

    图 4-29 正常调用示例

    接下来就让我们来看一下针对此脚本的攻击方法。

  • 错误消息导致的信息泄漏

    以下 URL 的目的在于攻击 44-001.php 以导致信息泄露。使用该 URL 打开页面,显示结果如图 4-30 所示。

    http://example.jp/44-001.php? author='+and+cast((select+id||':'||pwd+from+users+offset+0+limit+1)+as+integer)>1--
    
    

    图 4-30 错误消息导致的信息泄漏

    错误消息中显示了用户名和密码为 yamada:pass1。这就是利用 SQL 注入攻击致使信息泄露的手段。

    此攻击的核心部分为下面的子查询语句。

    (select id||':'||pwd from users offset 0 limit 1)
    
    

    该子查询查找 users 表中第一条数据的 id 和 pwd(用户名和密码)字段后,返回将两者以冒号相连后的字符串,即上图中的 yamada:pass1。然后,语句中尝试将字符串 yamada:pass1 通过 cast 函数转换为 integer 类型,但由于转换类型时出错,页面上显示了错误消息。

    此处并不需要透彻理解这条 SQL 语句,但一定要知道通过 SQL 注入攻击能够取得数据库中的任意信息。即使 SQL 注入漏洞存在于一些不起眼的地方,也可能会直接导致网站的重要信息泄漏。

    另外,上述 SQL 注入攻击也是恶意利用错误消息的典型案例。因此,开发时应该注意不要将程序内部的错误内容显示在错误消息中。

  • UNION SELECT 致使的信息泄漏

    SQL 注入引发的信息泄漏中,除了利用错误消息的方法外,还有一种手段是使用 UNION SELECT。UNION SELECT 的作用为将两个 SQL 语句的检索结果求和。

    下面我们来看一个利用 UNION SELECT 致使信息泄漏的例子。执行以下 URL 后,页面显示结果如图 4-31 所示。从图中可以看出,本应显示图书信息的页面上显示了用户的个人信息。

    http://example.jp/44/44-001.php?author='+union+select+id,pwd,name,addr,null,null,null+from+users--
    
    

    图 4-31 使用 UNION SELECT 进行攻击的结果

    此处不对攻击的详情进行说明,读者只需记住一旦使用 UNION SELECT 的攻击得逞,仅此一次攻击就能使大量信息泄漏。

  • 使用 SQL 注入绕过认证

    当登录页面存在 SQL 注入漏洞时,认证处理就能被绕过,从而导致在不知道密码的情况下也能成功登录应用。

    以下就是一个含有 SQL 注入漏洞的登录页面。首先是用户名和密码的输入页面,为了方便演示,此处密码输入框的 type 属性使用了 text。

    代码清单 /44/44-002.html

    <html>
    <head><title> 请登录 </title></head>
    <body>
    <form action="44-003.php" method="POST">
    用户名 <input type="text" name="id"><br>
    密码 <input type="text" name="pwd"><br>
    <input type="submit" value=" 登录 ">
    </form>
    </body>
    </html>
    
    

    下面是接收用户名和密码后进行登录处理的脚本。

    代码清单 /44/44-003.php

    <?php
      session_start();
      header('Content-Type: text/html; charset=UTF-8');
      $id = @$_POST['id'];   // 用户名
      $pwd = @$_POST['pwd']; // 密码
      // 连接数据库
      $con = pg_connect("host=localhost dbname=wasbook user=postgres password=wasbook");
      // 拼接 SQL 语句
      $sql = "SELECT * FROM users WHERE id ='$id' and pwd = '$pwd'";
      $rs = pg_query($con, $sql);  // 执行查询
    ?>
    <html>
    <body>
    <?php
      if (pg_num_rows($rs) > 0) { // 如果存在 SELECT 结果则登录成功
        $_SESSION['id'] = $id;
        echo ' 登录成功 ';
      } else {
        echo ' 登录失败 ';
      }
      pg_close($con);
    ?>
    </body>
    </html>
    
    

    正常情况下,登录页面中输入用户名 yamada 和密码 pass1 后就能认证成功。

    图 4-32 认证成功例

    下面我们来看一下对此登录页面进行攻击的例子。假设攻击者在不知道密码的情况下输入以下密码。

    ' or 'a'='a
    
    

    这时登录竟然也成功了。

    图 4-33 认证被绕过

    此时,拼接后的 SQL 语句如下。阴影部分为密码输入框内输入的字符串。

    SELECT * FROM users WHERE id ='yamada' and pwd = '' OR 'a'='a
    
    '
    

    SQL 语句的末尾被添加了 OR 'a' = 'a' ,因此 WHERE 语句始终保持成立状态。

    由此可知,如果登录页面存在 SQL 注入漏洞,就可能使密码输入框形同虚设。

  • 通过 SQL 注入攻击篡改数据

    接下来向大家介绍一下使用 SQL 注入攻击篡改页面数据的例子。首先用以下 URL 打开页面。

    http://example.jp/44/44-001.php?author=';update+books+set+title%3D'<i>cracked!</i>'+where+id%3d'1001
    
    '--
    

    然后再次检索 Shakespeare,页面显示就如下图所示。“仲夏夜之梦”变成了“cracked !”,字体也变成了斜体。

    图 4-34 篡改数据的例子

    第一次打开页面时执行的 SQL 文如下。阴影部分为外界传入的字符串,此处为了方便阅读加入了换行。-- 后面的字符被当成 SQL 文的注释而被忽略。

    SELECT * FROM books WHERE author ='';update books set title='<i>cracked!</i>' where id='1001'--
    
    'ORDER BM id
    

    同时我们看到 HTML 的 i 元素也生效了,由此可以得知插入的 HTML 标签是有效的。而在实际的攻击中,攻击者使用 iframe 或 script 元素等发动攻击,使用户的计算机感染病毒的案例可以说是层出不穷。

    另外,如果要在虚拟机中恢复被篡改的数据库,可以执行以下脚本,或者在 http://example.jp/44 的菜单中点击“12. resetdb :恢复数据库”链接。

    http://example.jp/44/resetdb.php

  • 其他攻击

    根据数据库引擎的不同,通过 SQL 注入攻击还可能会达到下列效果。

    • 执行 OS 命令
    • 读取文件
    • 编辑文件
    • 通过 HTTP 请求攻击其他服务器

    此处举一个读取文件的例子。同样使用 44-001.php 作为范例。

    首先使用如下 URL 打开页面。

    http://example.jp/44/44-001.php?author=';copy+books(title)+from+'/etc/passwd'--

    此时会调用如下 SQL 语句。

    copy books(title) from '/etc/passwd'

    这里的 COPY 语句是 PostgreSQL 数据库的扩展功能,能够将文件内容存入表中。此例中 /etc/passwd 就被存入了 books 表的 title 列。执行 COPY 语句需要 PostgreSQL 的管理员权限以连接数据库。

    为了确认效果,接下来我们用如下 URL 打开页面。

    http://example.jp/44/44-001.php?author='or+author+is+null--

    受 SQL 注入攻击的影响,页面显示 author 列的值为 NULL 的行。结果如下图所示。

    图 4-35 /etc/passwd 被存入数据库

    /etc/passwd 的内容被保存至数据库。

    由此可见,在某些情况下,SQL 注入攻击可能会导致服务器上的文件内容经由数据库泄漏至外界。

    SQL 注入攻击造成的影响因数据库引擎的不同而各异。但不管是什么样的数据库引擎,SQL 注入都会导致数据库内的数据被外界读取。关于各数据库引擎的影响,可以参考金床所著的《Web 应用程序安全》[2]。

    综上所述,SQL 注入攻击能导致数据库内的任意数据被泄漏或篡改,因此,SQL 注入漏洞可谓贻害无穷。

    专栏:数据库中表名与列名的调查方法

    通过 SQL 语句能够调查数据库内存在哪些表和列。SQL 标准规格中规定了名为 INFORMATION_SCHEMA 的数据库,使用其中的 tables 和 columns 等视图(假想表),就可以从中读取表和列的定义。

    图 4-36 展示了通过 SQL 注入攻击来使用 columns 视图使页面显示 user 表定义信息的范例。一般攻击者都会使用这种方法来探索数据库。图中的页面上显示了表名、列名和类型名。**

    http://example.jp/44/44-001.php?author='+union+select+table_name,column_name,data_type,null,null,null,null+from+information_schema.columns+order+by+1--
    

    图 4-36 使用 SQL 注入攻击显示表定义

安全隐患的产生原因

SQL 注入攻击能够以开发者意想不到的方式改变 SQL 语句的构造,其中很大程度上都是因为字面量 20 的缘故。字面量指的是 SQL 语句中的固定值,比如字符串 'Shakespeare' 和数值 -5 都是字面量。SQL 中每种数据类型都有相应的字面量,其中最常用的是字符串字面量和数值字面量 21 。

20 字面量以外的其他原因造成的 SQL 注入攻击,请参考“各种列的排序”小节。

21 除此以外还有布尔型字面量和日期时间字面量等。

  • 字符串字面量的问题

    SQL 标准规格中规定字符串字面量必须用单引号括起来。而若要在字符串字面量内使用单引号,就需要使用连续两个单引号来表示。这被称为单引号转义。因此,将“O'Reilly”用于 SQL 的字符串字面量时就需写成 O''Reilly

    然而,在有 SQL 注入漏洞的程序中,由于没有转义单引号,所以就导致拼接后的 SQL 语句如下。

    SELECT * FROM books WHERE author='O'Reilly
    
    '
    

    将此 SQL 语句的后半部分放大,如下图所示。

    图 4-37 上述 SQL 语句的后半部分

    O'Reilly ”中的单引号 22 使得字符串字面量结束,后面的“ Reilly' ”被排除出了字符串字面量。这部分在 SQL 语句中没有意义,所以就会产生语法错误。

    但是,如果将“ Reilly' ”换成有意义的 SQL 语句会如何呢?其实这正是 SQL 注入攻击的方法。SQL 注入攻击中,被插入的单引号等排除出的字符串是有意义的 SQL 语句,因此就能够被应用程序调用而执行特定操作。

    为了便于理解,我们将 SQL 注入攻击的字符串比喻为笼中的狮子,如下图所示。无论攻击字符串多么危险,只要它被解释为字面量就安然无事。而如果狮子(攻击字符串)被放出了笼子(字面量),它就会执行攻击。

    图 4-38 SQL 注入攻击字符串

  • 针对数值的 SQL 注入攻击

    前面介绍了针对字符串字面量的 SQL 注入攻击,而数值字面量也会遭受 SQL 注入攻击。Web 开发中普遍使用的脚本语言(PHP、Perl、Ruby 等)多为动态类型语言,不限制变量的类型。因此,理应填入数值的地方就有可能会被填入其他类型的字符。比如,假设以下 SQL 语句中 age 列的类型为整数型,存储的是职员的年龄。

    SELECT * FROM employees WHERE age < $age

    这时,如果将以下字符串传入 $age ,就形成了 SQL 注入攻击。

    1;DELETE FROM employees

    拼接后的 SQL 语句如下。

    SELECT * FROM employees WHERE age < 1;DELETE FROM employees
    
    
    

    而一旦执行此 SQL 语句就会删除所有的职员信息。

    由于数值字面量没有用单引号围住,所以,当出现非数值的字符时即被视为数值字面量终止。此例中,分号 ; 不是数值,因此分号以后的值就被排除出了数值字面量,而被解释为 SQL 语句的一部分。

22 严格来说是英文中的撇号,但通常情况下和单引号通用。

对策

前面已经提到,产生 SQL 注入漏洞的根本原因为,被指定为参数的字符串的一部分被排除出字面量,导致 SQL 语句发生了变化。因此,要防范 SQL 注入漏洞,就必须防止 SQL 语句在拼接过程中被更改。具体可采取如下两种方法。

(a)使用占位符拼接 SQL 语句

(b)在应用程序中拼接 SQL 语句时,要确保字面量被正确处理,SQL 语句不被更改

由于(b)方法的实施非常困难,因此这里极力推荐采用(a)方法 23 。

23 关于 SQL 中字面量的正确构成方法,请参考独立行政法人信息处理推进机构(IPA)发表的《安全调用 SQL 的方法》[5]。

  • 使用占位符拼接 SQL 语句

    使用了占位符后,之前检索图书库存的 SQL 语句就可以记述如下。

    SELECT * FROM books WHERE author = ? ORDER BM id

    SQL 语句中的问号就是占位符,表示将变量或表达式等可变参数填到此处。占位符(Place Holder)的英文即为“占座”的意思。下面我们就来演示一下如何使用占位符修改上述含有漏洞的范例。这里使用了名为 MDB2 的调用 SQL 语句的程序库。

    代码清单 /44/44-004.php

    <?php
      require_once 'MDB2.php';
      header('Content-Type: text/html; charset=UTF-8');
      $author = $_GET['author'];
      // 连接数据库时指定字符编码为 UTF-8
      $mdb2 = MDB2::connect('pgsql://wasbook:wasbook@localhost/wasbook?charset=utf8');
      $sql = "SELECT * FROM books WHERE author = ? ORDER BM id";
      // 准备调用 SQL。在第 2 个参数数组中指定占位符的类型
      $stmt = $mdb2->prepare($sql, array('text'));
      // 执行 SQL 语句。execute 方法的参数为参数的实际值(绑定值)
      $rs = $stmt->execute(array($author));
      // 省略显示的部分。
      $mdb2->disconnect(); // 切断数据库连接
    ?>
    
    

    在上述脚本中, author = ? 部分使用了占位符。此外,在调用 execute 方法时指定了实际的参数值。而将值分配给占位符这一操作就被称为绑定变量。

    专栏:采用 MDB2 的原因

    PHP 中连接 MySQL 或 PostgreSQL 等数据库引擎的程序库种类繁多,而笔者在试用了很多程序库后发现,PEAR 类库中的 MDB2 的安全性最好。原因如下(调查时间为 2010 年 12 月)。

    • MDB2 连接数据库时能够方便地指定字符编码。PDO 等其他程序库指定文字编码非常不便
    • 提供有占位符和转义等直接关系到安全性的功能
    • 被 PEAR 合并的其他程序库(例如 DB)都已停止后续维护
  • 为什么使用占位符会安全

    占位符依据实现方法可分为静态占位符和动态占位符两类。下面我们就来看一下为什么使用占位符能够安全地调用 SQL 语句。

    • 静态占位符

      静态占位符 24 的绑定变量操作在数据库引擎中执行。含有占位符的 SQL 语句被直接发送至数据库引擎,数据库引擎执行编译等准备工作后确定 SQL 语句。随后绑定值也被发送至数据库引擎,数据库引擎将收到的值填充进 SQL 语句后将其执行(图 4-39)。

      图 4-39 静态占位符

      由于 SQL 语句是在包含占位符的状态下被编译的,因此,从理论上来说,之后 SQL 语句就不可能再被改变。

    • 动态占位符

      动态占位符的方式为,首先在处理 SQL 的程序库中执行绑定变量操作,然后再将 SQL 语句发送给数据库引擎处理。绑定变量时字面量会被妥善处理,因此只要处理中没有 Bug 就不会遭受 SQL 注入攻击(图 4-40)。

      图 4-40 动态占位符

      由此可见,无论使用静态还是动态占位符都能消除 SQL 注入漏洞。但就理论上来说,静态占位符能够完全消除 SQL 注入漏洞出现的可能性,所以应当尽可能地采用静态占位符。

      动态占位符可能会因程序问题而造成 SQL 注入漏洞,比如 JVN#5974872325 。详情可参考笔者的博客文章 [4]。

  • 参考:LIKE 语句与通配符

    使用 LIKE 语句进行模糊查询时由通配符引发的问题,经常容易与 SQL 注入混为一谈。指定 LIKE 语句的查询模式时,_ 匹配任意 1 个字符,% 匹配任意 1 个或多个字符。_ 和 % 就被称为通配符。

    使用 LIKE 语句进行查询时,如果字符中含有 _ 或 %,就必须对这些通配符进行转义。不进行转义的话就会出现很多问题,但这并不是 SQL 注入,而很多人却经常将两者混淆。

    接下来就让我们首先通过示例看一下 LIKE 语句的用法。下面是一条用来查询 name 列中包含“山田”的行的语句(部分匹配)。

    WHERE name LIKE '% 山田 %'

    要在 LIKE 语句中查询 _ 或 %,就需要使它们不再担任通配符的角色,即对其进行转义。转义时使用的字符应该使用 ESCAPE 语句指定 26 。下面的例子中就使用了 # 作为转义字符。

    例如,下面是一条查询 name 列中包含 % 的行的语句。第一个和最后一个 % 为通配符,#% 表示查询对象字符为 %。

    WHERE name LIKE '%#%%' ESCAPE '#'

    虽然转义通配符与 SQL 注入漏洞并无直接关联,但却是正确处理所必需的步骤。

    转义通配符的 PHP 函数示例如下所示。它适用于 PostgreSQL 和 MySQL。前提为 PHP 的内部字符编码设置无误。

    function escape_wildcard($s) {
      return mb_ereg_replace('([_%#])', '#\1', $s);
    }
    
    

    其他数据库引擎中需要转义的字符则略有不同,如下表所示。

    表 4-10 需要转义的通配符

    数据库转义对象字符补充说明
    MySQL\_ % 
    PostgreSQL\_ % 
    Oracle\_ % \_ %全角字符也需转义
    MS SQL Server\_ % [见 ※1
    IBM DB2\_ % \_ %全角字符也需转义

    ※1 MS SQL Server 中能够使用 [a-z] 这种类似于正则表达式的通配符。[a-z] 匹配 1 个小写字母。因此,要查询 [ 本身就必须将 [ 转义。参考: http://msdn.microsoft.com/zh-cn/library/ms179859.aspx

    调查时上述数据库的版本如下。

    表 4-11 调查时使用的数据库版本

    数据库版本参考网页
    MySQL5.5http://dev.mysql.com/doc/refman/5.5/en/string-comparison-functions.html#operator_like
    PostgreSQL9.0.2http://www.postgresql.jp/document/pg902doc/html/functions-matching.html#FUNCTIONS-LIKE
    Oracle ※211ghttp://download.oracle.com/docs/cd/E16338_01/server.112/b56299/conditions007.htm#i1034153
    MS SQL ServerSQL Server 2008 R2http://msdn.microsoft.com/ja-jp/library/ms179859.aspx
    IBM DB29.7http://publib.boulder.ibm.com/infocenter/db2luw/v9r7/index.jsp?topic=/com.ibm.db2.luw.sql.ref.doc/doc/r0000751.html

    ※2 Oracle 11g 的参考网页中没有提及全角通配符,但根据实际操作我们发现,全角通配符也需要进行转义。(使用 Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 验证)

  • 使用占位符的各种处理

    在实际的 Web 应用开发中遇到条件复杂的 SQL 语句时,很多人可能都会萌发用拼接字符串的方式来组装 SQL 语句的想法,因为他们觉得使用占位符无法写出复杂的 SQL 语句。鉴于这种情况,接下来我们就向大家介绍一下各种复杂情况下使用占位符来调用 SQL 语句的例子。

    • 查询条件发生动态变化

      Web 应用的查询页面中有时会提供多个查询条件,在这样的页面中,SQL 语句只在有输入内容的文本框中组装,所以 SQL 语句就会根据用户的输入情况而发生变化。

      这种情况下,可以使用字符串动态拼接含有占位符 ? 的 SQL 语句,等到调用 SQL 语句时才绑定参数。示例脚本如下。在这段脚本中,PHP 变量 $title$price 分别为书名和价格上限的页面输入值。

      // 基底 SQL 语句
      $sql = 'SELECT id, title, author, publisher, date, price FROM books';
      if ($title !== '') { // 添加 title 查询条件(LIKE)
        $conditions[] = "title LIKE ? ESCAPE '#'";
        $ph_type[] = 'text';
        $ph_value[] = escape_wildcard($title);
      }
      if ($price !== '') { // 添加 price 查询条件(大小比较)
        $conditions[] = "price <= ?";
        $ph_type[] = 'integer';
        $ph_value[] = $price;
      }
      if (count($conditions) > 0) { // 存在 WHERE 语句时
        $sql .= ' WHERE ' . implode(' AND ', $conditions);
      }
      $stmt = $mdb2->prepare($sql, $ph_type); // 准备 SQL 语句
      $rs = $stmt->execute($ph_value);  // 执行变量绑定和查询操作
      
      

      虽然本例中指定的查询条件最多只有 2 个,但更复杂的查询条件语句也能够通过同样的方法使用占位符拼接而成。

    • 各种列的排序

      为了方便用户浏览列表,有时需要将 SQL 语句的查询结果根据用户指定的列进行排序。SQL 中能够使用 ORDER BY 语句指定列并进行排序,但编程时稍有疏忽就会引入安全隐患。比如,假设脚本中有如下 SQL 语句,其中,指定列名的 $row 通过查询字符串等外部途径传入。如果指定 row=author ,就会按照作者名进行排序。

      SELECT * FROM books ORDER BM $row
      
      

      而如果 $row 被指定为如下值,就形成了 SQL 注入攻击。

      cast((select id||':'||pwd FROM users limit 1) as integer)
      
      

      此时展开后的 SQL 语句如下所示。

      SELECT * FROM books ORDER BM cast((select id||':'||pwd FROM users limit 1) as integer)
      
      
      

      执行结果

      ERROR: integer 类型的输入语法无效 : "yamada:pass1"
      
      

      另外,还可以在 ORDER BY 语句后插入分号并追加其他 SQL 语句(UPDATE 等)。

      下面就让我们来看一下该问题的防范策略,即检验排序列名的有效性的方法。

      假设在以下脚本中,由查询字符串 sort 来指定进行排序的列。数组 $sort_columns 为允许指定的排序列名。这里使用 array_search 函数检查外界传入的列名是否合法,合法的情况下才能够在 SQL 语句后面加上 ORDER BY 语句。

      $sort_columns = array('id', 'author', 'title', 'price');
      $sort_key = $_GET['sort'];
      if (array_search($sort_key, $sort_columns) !== false) {
        $sql .= ' ORDER BM ' . $sort_key;
      }
      
  • SQL 注入的辅助性对策

    通过上面的讲述我们知道了防范 SQL 注入攻击的根本性对策为使用占位符。而这里我们将向大家介绍一些能够配合占位符一起实施的辅助性对策。所谓辅助性对策,是指当根本性对策的实施有疏漏,或者中间件存在漏洞时,能够减轻攻击造成的损害的对策。

    • 不显示详细的错误消息
    • 检验输入值的有效性
    • 设置数据库权限
    • 不显示详细的错误消息

      之前我们提到过利用错误消息来实施 SQL 注入攻击,从而成功窥探数据库中信息的例子。其中,特别是在显示为 SQL 错误的情况下,SQL 注入漏洞会更容易暴露给外界。因此,通过避免显示详细的错误消息,就能够在存在 SQL 注入漏洞的情况下,使攻击难度加大。

      PHP 中关闭详细的错误消息的显示,只需在 php.ini 中做如下设置。

      display_errors = Off
      
    • 检验输入值的有效性

      正如 4.2 节所述,依据应用程序的规格校验输入值,有时能够达到抵挡外部攻击的效果。例如,邮编输入框仅能输入数字、用户名输入框仅能输入字母和数字等,进行输入校验后,即使忘了利用占位符,也不会使 SQL 注入攻击得逞。

      但是,仅依靠输入校验是无法杜绝 SQL 注入攻击的。因为像地址输入框或评论输入框等地方就不限制输入字符的种类。因此,对抗 SQL 注入攻击还是要使用占位符。

    • 设置数据库权限

      将 Web 应用数据库的用户访问权限设置为所需的最低限度后,万一遭受 SQL 注入攻击,也能将受损降到最低。

      例如,仅显示商品信息的应用就不需要用户对商品数据表进行书写操作。这种情况下,仅开放商品数据表的读取权限给数据库用户,而不授予其书写权限,就能防止商品信息被篡改。

      此外,针对在“其他攻击”中提到的通过 SQL 读取文件这一攻击类型,也需要设置数据库管理员权限。将数据库用户的权限设为所需的最低限度,即使应用中有 SQL 注入漏洞,也能将受害程度降到最低。

24 静态占位符在 ISO 或 JIS 中,也被称为预处理语句(Prepared Statement)。

25 详情可参考 http://jvn.jp/en/jp/JVN59748723/

26 MySQL 中可以不写 ESCAPE 语句而使用 \ 来转义字符。但由于 SQL 标准规格(ISO 及 JIS)中规定没有 ESCAPE 语句时就认为没有定义转义字符,因此,按照这一标准,始终使用 ESCAPE 语句来定义转义字符更为保险。

总结

本节讲解了 SQL 注入漏洞的相关知识。SQL 注入漏洞能导致数据库内的所有信息被泄漏或篡改,从而造成极大的影响。因此,应用开发者在编程时一定要时刻警惕 SQL 注入漏洞。

防范 SQL 注入的最佳方法为使用静态占位符调用 SQL 语句。由于即使是动态的 SQL 语句也能够设法使用静态占位符来实现,因此,建议开发者们在应用中全部使用静态占位符。

继续深入学习

关于以下这些本书未涉及的内容,读者们可以参考独立行政法人信息处理推进机构(IPA)发表的《安全调用 SQL 的方法》[5]。

  • 应进行转义的字符详情
  • 字符编码的影响

而不同数据库引擎的攻击方法的示例,在金床所著的《Web 应用安全》[2] 或 Justin Clarke 所著的《SQL 注入攻击与防御》[1] 中有详细说明。

此外,当攻击者无法利用错误消息或 UNION SELECT 窃取内部信息时,还可以使用 SQL 盲注攻击(Blind SQL Injection)的手段达到窃取信息的目的,详情见佐名木智贵所著的《Web 编程安全性技巧》[3] 或《SQL 注入攻击与防御》[1]。

参考:无法使用占位符时的对策

虽然本书始终推荐使用占位符来应对 SQL 注入,但是,在一个既有的应用中,如果将实现方针全部改为使用占位符的话,修改成本将非常巨大。

这种情况下,为了解决 SQL 注入漏洞,可以沿用字符串拼接 SQL 语句的方法,并将重点注目于字面量的正确处理上。具体来说应实施以下两点。

  • 将字符串字面量中有特殊意义的字符和符号进行转义
  • 确保数值字面量中不被混入数值以外的字符

有些调用 SQL 的程序库中提供了 quote 方法来转义 SQL 中的字符串字面量,它能够根据数据库的种类和设置等自行调整转义的字符。

数值字面量的情况下一般只需将值转换(Cast)为数值型即可,但是,像位数很多的十进制数等在一些编程语言中就没有对应的类型,这时就不能使用类型转换,而应使用正则表达式来检验数值。

详情请参考独立行政法人信息处理推进机构(IPA)发表的《安全调用 SQL 的方法》[5]。

参考:Perl+MySQL 的安全连接方法

Perl 和 MySQL 的组合有着很高的人气。但由于 Perl 的标准库中的 SQL 连接库 DBI/DBD 连接 MySQL 时默认使用动态占位符,因此,要想改为使用静态占位符的话,就需要修改如下设置(阴影部分)。

my $db = DBI->connect('DBI:mysql:books:localhost;
mysql_server_prepare=1

;mysql_enable_utf8=1', 'username', 'password')  || die
$DBI::errstr;

参考:PHP+PDO+MySQL 的安全连接方法

在使用 PHP 的开发中,连接 MySQL 数据库通常会采用 PDO(PHP Data Objects)。PDO 因处理速度快而备受欢迎,但使用时需注意防范 SQL 注入漏洞。

PDO 没有提供指定连接数据库时的字符编码的方法,只能通过指定 MySQL 的配置文件的方式来指定字符编码,如下所示。下面的代码中还设置了使用静态占位符。

$dbh = new PDO('mysql:host=localhost;dbname=wasbook', 'username', 'password', array(

    PDO::MMSQL_ATTR_READ_DEFAULT_FILE => '/etc/mysql/my.cnf',
    PDO::MMSQL_ATTR_READ_DEFAULT_GROUP => 'client',
    PDO::ATTR_EMULATE_PREPARES => false,
  ));

另外,在 /etc/mysql/my.cnf(MySQL 的配置文件)中添加如下设置。

[client]
default-character-set=utf8

参考:Java+MySQL 的安全连接方法

Java 连接 MySQL 时使用 JDBC 驱动的 MySQL Connector/J。由于这一组合默认使用的是动态占位符,因此,建议修改如下设置(阴影部分)以改为使用静态占位符。

Connection con = DriverManager.getConnection(
"jdbc:mysql://localhost/dbname?user=xxx&password=xxxx&
useServerPrepStmts=true

&useUnicode=true&characterEncoding=utf8")

参考文献

[1] Justin Clarke.(2009). SQL Injection Attacks and Defence . Syngress.

[2] 金床 .(2007).《ウェブアプリケーションセキュリティ》(《Web 应用安全》). データ·ハウス .

[3] 佐名木智貴 .(2008).《セキュア Web プログラミング Tips 集》(《Web 编程安全性技巧》). ソフト·リサーチ·センター .

[4] 德丸浩(2008 年 12 月 22 日). Java と MySQL の組み合わせで Unicode の U+00A5 を用いた SQL インジェクションの可能性(通过 Java 和 MySQL 的组合对利用 Uninode 的 U+00A5 进行 SQL 注入的可能性). 参考日期:2010 年 12 月 23 日,参考网址:德丸浩の日記 : http://www.tokumaru.org/d/20081222.html#p01

[5] 独立行政法人信息处理推进机构(IPA).(2010 年 3 月 18 日). 安全な SQL の呼び出し方(安全调用 SQL 的方法). 参考日期:2010 年 12 月 7 日,参考网址:情報処理推進機構 : http://www.ipa.go.jp/security/vuln/websecurity.html

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文