网站地图    收藏   

主页 > 后端 > 网站安全 >

回忆phpcms头像上传漏洞以及后续影响 - 网站安全

来源:自学PHP网    时间:2015-04-16 23:15 作者: 阅读:

[导读] 暑假写的文章了,最近博客没干货,发出来娱乐一下。为了响应爱慕锅(Mramydnei)、撸大师(索马里的海贼)、fd牛( fd)的号召成立的parsec团队,以及各位老师多年来对我的教育,我要...

暑假写的文章了,最近博客没干货,发出来娱乐一下。

为了响应爱慕锅(Mramydnei)、撸大师(索马里的海贼)、fd牛(/fd)的号召成立的parsec团队,以及各位老师多年来对我的教育,我要写篇回忆稿。看标题大家可能觉得,这陈芝麻烂谷子的事你还拿出来说啥。当然,我自己搓一点都无所谓,但怎么能丢了parsec的脸,各位还是且听我娓娓道来~


0×01 最初的phpcms头像上传getshell漏洞
不知道大家还记得phpcms曾经火极一时的头像上传漏洞不,因为这个漏洞,互联网上大量站点被黑,影响极为恶劣。
那件事以后我分析过漏洞才成因以及利用方法( http://www.2cto.com/Article/201312/265705.html ),简单来说phpcms对头像上传是这么处理:上传上去的zip文件,它先解压好,然后删除非图片文件。
关键地方代码:

 

//存储flashpost图片
  $filename = $dir.$this->uid.'.zip';
  file_put_contents($filename, $this->avatardata);
  
//此时写入压缩文件夹内容
  
  //解压缩文件
  pc_base::load_app_class('pclzip', 'phpsso', 0);
  $archive = new PclZip($filename);
  if ($archive->extract(PCLZIP_OPT_PATH, $dir) == 0) {
   die("Error : ".$archive->errorInfo(true));
  }
  
//568 行
  
//判断文件安全,删除压缩包和非jpg图片
  $avatararr = array('180x180.jpg', '30x30.jpg', '45x45.jpg', '90x90.jpg');
  if($handle = opendir($dir)) {
      while(false !== ($file = readdir($handle))) {
    if($file !== '.' && $file !== '..') {
     if(!in_array($file, $avatararr)) {
      @unlink($dir.$file);
     } else {
      $info = @getimagesize($dir.$file);
      if(!$info || $info[2] !=2) {
       @unlink($dir.$file);
      }
     }
    }
}
 
可以看到,它删除的时候没有递归删除,也没有删除文件夹。这样,只要我们的webshell放在压缩包的文件夹中,即可避免被删除了。
所以我就创建了一个包含phi文件夹的压缩包,phi里面放上webshell.php,上传上去。
这就是phpcms最早的头像上传漏洞。这个漏洞影响的不只是phpcms,也包括抄袭其代码的finecms。
finecms是一个很喜感的cms,在phpcms出问题以后,finecms偷偷将漏洞修复了,当然修复方法就是直接拷贝了phpcms的补丁。

0×02 finecms前台getshell(phpcms补丁绕过)
纯属抄袭自己不动脑子的人,最容易出问题,比如finecms的开发者。自以为自己反应速度得当,在迅雷不及掩耳盗铃的时间内将phpcms的补丁抄了过来,然后就安然度过了半年的悠闲时光。
phpcms的补丁被绕过了无数次难道你不知道么?你这么屌你的用户们知道么?
那么,我们来看看finecms(phpcms代码类似)是怎么修补这个漏洞的:


public function upload() {
  
        if (!isset($GLOBALS['HTTP_RAW_POST_DATA'])) {
  
            exit('环境不支持');
  
        }
  
        $dir = FCPATH.'member/uploadfile/member/'.$this->uid.'/'; // 创建图片存储文件夹
  
        if (!file_exists($dir)) {
  
            mkdir($dir, 0777, true);
  
        }
  
        $filename = $dir.'avatar.zip'; // 存储flashpost图片
  
        file_put_contents($filename, $GLOBALS['HTTP_RAW_POST_DATA']);
  
        // 解压缩文件
  
        $this->load->library('Pclzip');
  
        $this->pclzip->PclFile($filename);
  
        if ($this->pclzip->extract(PCLZIP_OPT_PATH, $dir, PCLZIP_OPT_REPLACE_NEWER) == 0) {
  
            exit($this->pclzip->zip(true));
  
        }
  
        // 限制文件名称
  
        $avatararr = array('45x45.jpg', '90x90.jpg');
  
        // 删除多余目录
  
        $files = glob($dir."*");
  
        foreach($files as $_files) {
  
            if (is_dir($_files)) {
  
                dr_dir_delete($_files);
  
            }
  
            if (!in_array(basename($_files), $avatararr)) {
  
                @unlink($_files);
  
            }
  
        }
  
        // 判断文件安全,删除压缩包和非jpg图片
  
        if($handle = opendir($dir)) {
  
            while (false !== ($file = readdir($handle))) {
  
                if ($file !== '.' && $file !== '..') {
  
                    if (!in_array($file, $avatararr)) {
  
                        @unlink($dir . $file);
  
                    } else {
  
                        $info = @getimagesize($dir . $file);
  
                        if (!$info || $info[2] !=2) {
  
                            @unlink($dir . $file);
  
                        }
  
                    }
  
                }
  
            }
  
            closedir($handle);   
  
        }
  
        @unlink($filename);
 
好,我们看到,之前产生漏洞的原因就是因为没有考虑文件在文件夹中的情况,只删除了压缩包根目录下的非法文件,而没有删除其文件夹中的非法文件。
所以补丁就采用了递归删除的方式,将压缩包中所有非法文件删除。就是这个dr_dir_delete函数。
我们就不研究这个函数了,我们考虑一种情况,那么如果我上传包含这样代码的压缩包:
<?php fputs(fopen('../../../../../shell.php','w'),'<?php phpinfo();eval($_POST[a]);?>');?>


在文件上传解压到被删除这个时间差里访问,就能在网站根目录下生成新的php文件,那么新生成的php文件是不会被删除的。
这就是一个竞争性上传漏洞,需要我们抓住这个时间差,在上传的php文件还没被删除前访问到它,就能够暴力getshell了。
这是乌云白帽子@felixk3y 在乌云上提出的绕过方法(http://www.wooyun.org/bugs/wooyun-2014-049794),当然我们应用到这里,也成功绕过finecms的补丁,造成getshell(http://wooyun.org/bugs/wooyun-2010-063369)。

0×03 突破程序员的小聪明,phpcms补丁的继续绕过
于是finecms意识到自己的问题,偷偷修补了这个安全问题。当时的他们是这样修复的:
 


// 创建图片存储的临时文件夹
  
   $temp = FCPATH.'cache/attach/'.md5(uniqid().rand(0, 9999)).'/';
  
   if (!file_exists($temp)) {
  
       mkdir($temp, 0777);
  
   }
  
   $filename = $temp.'avatar.zip'; // 存储flashpost图片
  
   file_put_contents($filename, $GLOBALS['HTTP_RAW_POST_DATA']);
  
   // 解压缩文件
  
   $this->load->library('Pclzip');
  
   $this->pclzip->PclFile($filename);
  
   if ($this->pclzip->extract(PCLZIP_OPT_PATH, $temp, PCLZIP_OPT_REPLACE_NEWER) == 0) {
  
       exit($this->pclzip->zip(true));
  
   }
  
   @unlink($filename);
 
说起来这也是phpcms曾经的修复方法,就是将压缩包放在一个随机命名的文件夹中再解压缩,这样你猜不到访问地址也就没法去暴力getshell了。
但是实质上这也只是解决了一个芝麻小的问题,而真正出现漏洞的点他们并未进行修复。
我们看到这段代码:
 
if ($this->pclzip->extract(PCLZIP_OPT_PATH, $dir, PCLZIP_OPT_REPLACE_NEWER) == 0) {
  
    exit($this->pclzip->zip(true));
  
}
当解压发生失败时,就退出解压缩过程。

这也是一个很平常的思路,失败了肯定要报错并退出,因为后面的代码没法运行了。但是,程序员不会想到,有些压缩包能在解压到一半的时候出错。
什么意思,也就说我可以构造一个“出错”的压缩包,它可以解压出部分文件,但绝对会在解压未完成时出错。这是造成了一个状况:我上传的压缩包被解压了一半,webshell被解压出来了,但因为解压失败这里exit($this->pclzip->zip(true));退出了程序执行,后面一切的删除操作都没有了作用。
首先构造一个解压会出错的压缩包,大家看下图,1-7.php都已经被成功解压了,但6.php解压出错,WinRAR弹出了出错信息:

01.gif

发包的时候,将这个压缩包带上,会发现返回了500,出错信息:

02.gif

但你的webshell已经解压完毕了。这个漏洞造成了finecms官网的沦陷: http://www.2cto.com/Article/201409/332918.html 。

0×04 加了行代码就真的安全了吗?终极手段上!
在官网被我日了以后finecms依旧无耻地说自己已经修复了这个漏洞,真稀奇。
过了半个月我看到了他们最新的代码:



if (!isset($GLOBALS['HTTP_RAW_POST_DATA'])) {
  
    exit('环境不支持');
  
}
  
// 创建图片存储文件夹
  
$dir = FCPATH.'member/uploadfile/member/'.$this->uid.'/';
  
if (!file_exists($dir)) {
  
    mkdir($dir, 0777, true);
  
}
  
// 创建图片存储的临时文件夹
  
$temp = FCPATH.'cache/attach/'.md5(uniqid().rand(0, 9999)).'/';
  
if (!file_exists($temp)) {
  
    mkdir($temp, 0777);
  
}
  
$filename = $temp.'avatar.zip'; // 存储flashpost图片
  
file_put_contents($filename, $GLOBALS['HTTP_RAW_POST_DATA']);
  
// 解压缩文件
  
$this->load->library('Pclzip');
  
$this->pclzip->PclFile($filename);
  
if ($this->pclzip->extract(PCLZIP_OPT_PATH, $temp, PCLZIP_OPT_REPLACE_NEWER) == 0) {
  
    @dr_dir_delete($temp);
  
    exit($this->pclzip->zip(true));
  
}
  
@unlink($filename);
 
加了行代码:@dr_dir_delete($temp);,解压出错后,在exit前将已经解压出来的内容删除了。确实避免了我在0×03中说到的安全问题。
但finecms的开发者依旧是没有能看到真正造成这个漏洞的原因。
原因就出在解压压缩包的这个操作上。这个类你就把别人的代码拿来一抄就觉得完毕了,你知道这个类真正的用法么?大家猜猜我这次怎么绕过上诉补丁的。
压缩包中通常是不含有诸如“../”、“..”这种文件名的,但通常不含有不代表不能含有。我如果把压缩包中某文件名改成../../../../../index.php,是不是就能直接把你首页变成我的webshell呀?
这就是因为抄袭者并没有真正领悟zip这个类的使用方法,导致了这个安全问题。我在本地用notepad++即可修改、构造一个压缩包。
先把自己的shell改名字成aaaaaaaaaaaaaaaaaaaa.php
之所以起这个名字,就是预留一些空间,方便我之后将文件名改成../../../aaaaaaaaaaa.php而不用怕字符串长度不对。
把文件直接打包成zip,用notepad++打开:

03.gif

将我画框的俩文件名的前9个字符改成../../../

04.gif

然后就大功告成。
上传头像时抓包将刚才构造的压缩包贴进去:
05.gif

然后,网站根目录下就会有你的shell了:aaaaaaaaaaa.php

06.gif

通过这个方法,就能无限制地getshell:http://www.2cto.com/Article/201409/332919.html

0×05 未完待续,以及究竟怎么修复这个安全问题
究竟是什么原因造成了这个漏洞,究其根本还是以为你将用户不安全的POST数据写入了文件,并解压到web目录下了。
世界上有无数种方法可以避免这个问题,web目录下随便写文件真的好吗?为何你不把压缩包放进tmp目录里,如果上传、解压缩的操作都能在tmp目录里完成,再把我们需要的头像文件拷贝到web目录中,还会有这么麻烦的安全问题吗?
phpcms已经彻底抛弃了解压缩的方式,直接在前端将图片处理完成后进行上传。但愚昧的finecms开发者还是抱着自己无知的思路,去用近乎“黑名单”的方式去解决这个问题,那就是黑客怎么日,他就怎么补,永远不知道下一步黑客会从哪里进入。这样的人永远只能落后挨打,这样的cms迟早会成为一个打满补丁的破布,每一个补丁都将付出无数速度与效率的代价。
多说一句,新版本依旧存在缺陷。让他做一个永远补不完的坑吧


自学PHP网专注网站建设学习,PHP程序学习,平面设计学习,以及操作系统学习

京ICP备14009008号-1@版权所有www.zixuephp.com

网站声明:本站所有视频,教程都由网友上传,站长收集和分享给大家学习使用,如由牵扯版权问题请联系站长邮箱904561283@qq.com

添加评论