文件解析漏洞总结-Apache

我最为熟悉的便是Apache了,先来研究它的文件解析漏洞。百度许久,又谷歌一番,最终发觉,Apache关于文件解析,似乎只有三种“漏洞”。之所以打引号是因为我觉得这三种“漏洞”都不是Apache的漏洞,只是其特性,而很多程序员不了解这种特性,故而写出有问题的代码,这才给黑客可趁之机,造成漏洞。但大家都称呼这是Apache的文件解析漏洞,我也只好随大流了。

1.多后缀名

先说第一种特性:多后缀名。这是怎么的一种鲜为人知的特性呢?原来是这样的,Apache认为,一个文件可以有多个后缀,如:werner.txt.png.mp3。这一文件,放在Windows里,毫无疑问,就是个mp3文件,Windows只认最后一个“.”及其后面的字符“mp3”,觉得该文件后缀为“.mp3”,这也是大多数操作系统、应用软件的处理方式、是正常人的习惯。而在Apache中,则可能有所不同,如果有必要,Apache会从后(右)往前(左),一一辨别后缀。何时有必要?当Apache不认识某个后缀时,便有必要。如某文件名为:werner.mp3.html.qwe.arex,Apache在处理时,先读取最后一个后缀,为“.arex”,一看,这啥玩意啊,不认识,继续读取下一个后缀“.qwe”,一看,呀,这又是啥,还是不认识,继续读下一个后缀“.html”,一看,哦,这是个超文本标记语言文件,俗称网页文件,这回认识了,也就不继续读下一个后缀了。若是所有后缀都看完了没有一个认识怎么办?此时就会把该文件当做默认类型进行处理了,一般来说,默认类型是text/plain。据说在Apache的配置文件中搜索“DefaultType”就能看到默认类型的明确定义了,但我却不知为何,没有找到。

哪些后缀Apache认识,哪些不认识?有一个名为mime.types的文件,其中记录着Apache认识的后缀。在Ubuntu下,该文件位于/etc/mime.types,在Windows下,该文件位于C:/apache/conf/mime.types(类似这样的,注意Apache的安装路径)。该文件是一个一对多的映射表,定义了某一种文件类型对应的几种后缀。除了该文件,在Apache的配置文件中,还可以用AddCharset语句添加映射,如:

  AddCharset us-ascii   .ascii .us-ascii
  AddCharset ISO-2022-CN .iso2022-cn .cis

mime.types是个很长的文件,大概看了下,Apache认识的后缀比我多多了。节选部分如下所示:

  application/java-archive          jar
  application/m3g                   m3g
  application/java-vm               class
  application/javascript            js
  application/json                  json
  text/html                         html htm shtml
  text/x-diff                       diff patch
  video/x-flv                       flv
  video/x-la-asf                    lsf lsx
  video/x-mng                       mng
  video/x-ms-asf                    asf asx
  video/x-ms-wm                     wm

这一特性会带来什么问题呢?网站往往有上传文件的功能,但一定不想让用户上传程序,因为这很可能会危害网站安全,故而会检查上传文件的后缀名,若是.php,则拒绝上传(假设这是个php站)。此时用户只需上传文件evildoer.php.qwe,若是程序员不了解Apache的这一特性,编写的程序检查后缀时只看“.qwe”,而认为这不是程序文件,允许上传,则用户成功地绕过了上传时的安全检查,上传了php程序文件。该文件的最后一个后缀“.qwe”是Apache不认识的,故而Apache会以倒数第二个后缀“.php”为准,把该文件当做是php文件,解析执行。

这总是奏效的吗?按理来说,由于这是特性而不是漏洞,所以适用于所有版本的Apache。这一奇怪的特性,说不定正是Apache的自豪之处呢。但是,在我的测试中却发现,类似aaa.php.xxx的文件并不会被作为php程序执行,而是被当成文本文件,返回给浏览器,在浏览器中可以看到php源码,而不是执行结果。测试环境是Ubuntu14.04+Apache2.4.7+php5。

这是怎么回事?难道前面几百字都是废话,说的是错的?我们来做个实验。准备一个文件,内容随意,命名为test.jpg.aaa,放置在Apache中,然后在浏览器中访问它,结果如下图所示:

可见浏览器是将该文件作为图片处理的。浏览器为何认为test.jpg.aaa是图片呢?aaa可不是图片文件的后缀。这是因为服务器的响应HTTP头中的Content-Type字段值为image/jpeg,浏览器看到image/jpeg,便知这是图片文件。这说明服务器(此处即Apache)是把test.jpg.aaa当做图片的,也说明,前面分析的Apache的多后缀处理是没有错的。

那么aaa.php.xxx为何没有被作为php代码执行呢?我猜是这样的,当然只是我的猜测,实在是找不到相关资料,只好猜了。Apache看到文件aaa.php.xxx,按照多后缀名的解析规则,认为该文件是php程序文件,把该文件作为php程序文件处理。怎么处理呢?交给php解释器,Apache本身并不懂php。而php解释器却有着和Apache不同的后缀解析规则,可能只认最后一个后缀,故而认为aaa.php.xxx不是php程序文件,拒绝执行。在我的测试环境中,php以模块(module)的模式工作于Apache的领导下。这种模式下php接受到领导Apache分配的任务——aaa.php.xxx,一看,不是php程序文件,没法执行,但也没有报错,而是返回了文件内容本身。php还可以以FASTCGI的模式工作于Apache中,此种模式下php遇到类似aaa.php.xxx这种不是php程序的文件,会触发500错误。

php本身是如何识别文件的呢?我在Apache的模块的配置文件中找到了php5.conf,内容如下:

  <FilesMatch ".+\.ph(p[345]?|t|tml)$">
      SetHandler application/x-httpd-php
  </FilesMatch>
  <FilesMatch ".+\.phps$">
      SetHandler application/x-httpd-php-source
      # Deny access to raw php sources by default
      # To re-enable it's recommended to enable access to the files
      # only in specific virtual host or directory
      Order Deny,Allow
      Deny from all
  </FilesMatch>
  # Deny access to files without filename (e.g. '.php')
  <FilesMatch "^\.ph(p[345]?|t|tml|ps)$">
      Order Deny,Allow
      Deny from all
  </FilesMatch>

  # Running PHP scripts in user directories is disabled by default
  #
  # To re-enable PHP in user directories comment the following lines
  # (from <IfModule ...> to </IfModule>.) Do NOT set it to On as it
  # prevents .htaccess files from disabling it.
  <IfModule mod_userdir.c>
      <Directory /home/*/public_html>
          php_admin_flag engine Off
      </Directory>
  </IfModule>

阅读上示配置文件可知,被当做php程序执行的文件名要符合正则表达式:

    ".+\.ph(p[345]?|t|tml)$"

其中“$”符号在正则中匹配结束,故而可知php本身确实是只看最后一个后缀的。就算Apache把某文件当php程序,php自己不认它,也是无用。

进一步试验,把php5.conf文件中刚刚提到的正则表达式的“$”换成“\.”,即:

  ".+\.ph(p[345]?|t|tml)\."

然后重启Apache使配置文件生效,再在浏览器中访问aaa.php.xxx,这次,aaa.php.xxx果然被当做php程序执行了,在浏览器中,看到的是程序执行结果而不是源码。这也从侧面验证了,我的猜测是正确的。测试完之后,一定要记得改回去。

2.罕见后缀

计算机世界自开天辟地以来,便自由多彩。还记得mime.types文件吗?在该文件中搜索“php”这三个字母,结果如下所示:

  werner@Yasser:~$ cat /etc/mime.types | grep php
  #application/x-httpd-php                      phtml pht php
  #application/x-httpd-php-source               phps
  #application/x-httpd-php3                     php3
  #application/x-httpd-php3-preprocessed        php3p
  #application/x-httpd-php4                     php4
  #application/x-httpd-php5                     php5

还记得正则表达式”.+\.ph(p[345]?|t|tml)$”吗,该正则表达式匹配的不仅仅有php,还有php3、php4、php5、pht和phtml。

好吧,原来不仅php,就连phtml、pht、php3、php4和php5都是Apache和php认可的php程序的文件后缀。我原本只知道“.php”的,真是大开眼界。这就好比,不仅py是Python程序文件的后缀,还有pyc和pyo也都是。写上传过滤规则的程序员是否博学多识,也知道这些知识呢?若是不知道,岂不是又生漏洞。利用这些“罕见”的后缀名,也可能绕过安全检查,干些“坏事”。

我在Ubuntu14.04+Apache2.4.7中进行测试,先准备文件text.php,其内容是经典的Hello World:

  <?php echo 'HELLO WORLD'; ?>

然后在浏览器中打开它,成功显示“HELLO WORLD”。再修改该文件后缀为各种后缀,进行测试。测试结果是,以php、phtml、pht、php3、php4和php5为后缀,能成功看到“HELLO WORLD”;以phps为后缀,会报403错误,Forbidden;以php3p为后缀,会在浏览器中看到源码。

3.妙用.htaccess

.htaccess是Apache的又一特色。一般来说,配置文件的作用范围都是全局的,但Apache提供了一种很方便的、可作用于当前目录及其子目录的配置文件——.htaccess(分布式配置文件)。

要想使.htaccess文件生效,需要两个条件,一是在Apache的配置文件中写上:

  AllowOverride All

若这样写则.htaccess不会生效:

  AllowOverride None

二是Apache要加载mod_Rewrite模块。加载该模块,需要在Apache的配置文件中写上:

  LoadModule rewrite_module /usr/lib/apache2/modules/mod_rewrite.so

若是在Ubuntu中,可能还需要执行命令:

  sudo a2enmod rewrite

配置完后需要重启Apache。

需要注意Apache可能有多个配置文件,后加载的配置文件会覆盖先加载的配置文件中的配置。所以在某个配置文件中将AllowOverride设置成All,若是其后加载的某个配置文件中AllowOverride的设置是None,则也是没有用的。一般来说,先加载httpd.conf,再加载conf.d/中的配置文件,最后加载sites-enabled/中的配置文件。

这意味着,.htaccess并不总是有效的。而且不幸的是,在我的测试环境中.htaccess默认无效。好吧,为了测试,我只好将它改为有效。以下讨论均在.htaccess有效的前提下进行。

.htaccess文件可以配置很多事情,如是否开启站点的图片缓存、自定义错误页面、自定义默认文档、设置WWW域名重定向、设置网页重定向、设置图片防盗链和访问权限控制。但我们这里只关心.htaccess文件的一个作用——MIME类型修改。如在.htaccess文件中写入:

  AddType application/x-httpd-php xxx

就成功地使该.htaccess文件所在目录及其子目录中的后缀为.xxx的文件被Apache当做php文件。另一种写法是:

  <FilesMatch "shell.jpg">
    SetHandler application/x-httpd-php
  </FilesMatch>

该语句会让Apache把shell.jpg文件解析为php文件。

下面是一次测试,测试前已经打开Apache对.htaccess文件的支持。在网站根目录中准备如下文件树:

  │
  ├── htaccess_test/
  │   ├── .htaccess
  │   ├── shell.jpg
  │   ├── type.xxx
  │   └── test/
  │       ├── shell.jpg
  │       └── type.xxx
  ├── shell.jpg
  └── type.xxx

其中,文件.htaccess的内容为:

  AddType application/x-httpd-php xxx

  <FilesMatch "shell.jpg">
  SetHandler application/x-httpd-php
  </FilesMatch>

文件shell.jpg和type.xxx的内容相同,均为:

  <?php echo 'HELLO WORLD'; ?>

然后在浏览器中访问各文件,结果如下表所示:

访问路径 访问结果
/type.xxx <?php echo ‘HELLO WORLD’; ?>
/shell.jpg 加载失败的图片
/htaccess_test/type.xxx HELLO WORLD
/htaccess_test/shell.jpg HELLO WORLD
/htaccess_test/test/type.xxx HELLO WORLD
/htaccess_test/test/shell.jpg HELLO WORLD

这一测试结果和预期是相符的。

但是,按照前面的猜测,就算Apache认为type.xxx是php文件,php自己不这么认为,不也是不能执行的吗?这次怎么就能执行了,好生奇怪。但不管怎么说,我们都已经知道,当.htaccess文件有效时,用.htaccess文件可以很好地设置文件后缀的解析方式。

参考资料

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

15 + 8 =