DISCUZ2上传漏洞分析 上传漏洞变换利用DISCUZ论坛以其漂亮的界面,完备的功能受到很多站长青睐,在PHP论坛中占有很大市场,从各方面都有可以和动网论坛相媲美。2.0虽然属于老版本,但还是有一大部分用户正在使用。自从9月DISCUZ爆了一个漏洞以来,随后又相继出来了几个漏洞,但这些漏洞要么影响很少,要么利用起来很困难。我写过一个DISCUZ利用程序,经常有人问我如何利用,我无法回答。为此,我读了一下DISCUZ源代码,发现它的上传存在问题,经过测试,在WIN2000下和Red Hat下都存在该问题,不过部分UNIX系统不受影响。文章是写如何发现上传漏洞,给大家和程序作者一点思路,其中源程序请去网上下载免费版。
一 漏洞分析
我首先说DISCUZ2或者更高版本的一个BUG。它的include/common.php存在物理路径泄露漏洞。
require $discuz_root.'./config.php';
require $discuz_root.'./include/global.php';
require $discuz_root.'./include/db_'.$database.'.php';
这段代码中$discuz_root默认是等于”.”的,就是当前目录的意思,如果在PHP.ini中没有设置INCLUDE选项,那系统就会报告无法找到config.php,因为系统认为common.php是被其他文件包含的,而包含它的文件是在INCLUDE目录上层,所以代码是这样写的,但是系统不可能阻止我们直接访问common.php。所以,如果我们在浏览器提交(我的机子为例)
http://localhost/discuz/include/common.php
就会泄露物理路径。
好,切入正题,开始分析上传漏洞如何形成以及如何利用。
DISCUZ论坛的上传函数是include/post.php下面的attach_upload。我一步一步解释程序的执行过程,涉及关键代码处我会详细说明。
global $discuz_root, $attachsave, $attach, $attach_name, $attach_size, $attach_fname, $attachdir, $maxattachsize, $attachextensions;
//获得全局变量
if(!function_exists('is_uploaded_file')) {
if(!is_uploaded_file($attach)) {
return false;
}
} elseif(!($attach != 'none' && $attach && trim($attach_name))) {
return false;
}
//以上判断$attach变量是不是一个上传文件,不是函数结束,我们上传的肯定是个文件
$attach_name = daddslashes($attach_name);
if($attachextensions && @!eregi(substr(strrchr($attach_name, '.'), 1), $attachextensions)) {
showmessage('post_attachment_ext_notallowed');
}
//关键代码,判断扩展名是否符合要求,默认安装时$attachextentsions为空,那么这个IF语句就跳过去了,这是我们所希望的。但是一般有点安全意识的网站都会设置一下,这个问题少后详细讨论。
if(!$attach_size || ($maxattachsize && $attach_size > $maxattachsize)) {
showmessage('post_attachment_toobig');
}
//判断文件大小,构造一下这句对我们没有效果,跳过不管
$filename = $attach_name;
$extension = strtolower(substr(strrchr($filename, '.'), 1));
if($attachsave) {
switch($attachsave) {
case 1: $attach_subdir = 'forumid_'.$GLOBALS['fid']; break;
case 2: $attach_subdir = 'ext_'.$extension; break;
case 3: $attach_subdir = 'month_'.date('ym'); break;
case 4: $attach_subdir = 'day_'.date('ymd'); break;
}
if(!is_dir($discuz_root.'./'.$attachdir.'/'.$attach_subdir)) {
mkdir($discuz_root.'./'.$attachdir.'/'.$attach_subdir, 0777);
}
$attach_fname = $attach_subdir.'/';
} else {
$attach_fname = '';
}
//如何保存,默认是放在论坛attachments目录下,不过有的论坛根据论坛ID号分类,或者日期分类,根据具体论坛而定,不过对我们影响不大
$filename = substr($filename, 0, strlen($filename) - strlen($extension) - 1);
if(preg_match("/[\x7f-\xff]+/s", $filename)) {
$filename = str_replace('/', '', base64_encode(substr($filename, 0, 20)));
}
//过滤非ASCII字符
if(in_array($extension, array('php', 'php3', 'jsp', 'asp', 'cgi', 'pl'))) {
$extension = '_'.$extension;
}
//关键代码,判断扩展名是不是非法,以防有人恶意上传WEBSHELL。不过我们构造条件要饶过这条语句
$attach_fname .= random(4)."_$filename.$extension";
$attach_saved = false;
$source = stripslashes($discuz_root.'./'.$attachdir.'/'.$attach_fname);
//生成路径
剩下代码是上传文件的就省略了,因为要是能执行到这里,和文件任何属性都已无关,所以我也就不解释了(解释好累,呵呵)。
好我们开始反向追踪$source变量,可以看到它是三个变量组成:$discuz_root是定义好的,$attachdir也是定义好的,唯一能控制的是$attach_fname变量。好,再追踪$attach_fname变量,由4个随机字符串,一个下划线和两个变量组成,
$attach_fname .= random(4)."_$filename.$extension";
废话少说,我把这个式子展开,用最原始的变量(我们可以构造的变量)替换,就变成了如下格式(别和我说没学过代数),随机串用abcd表示。
$extension=strtolower(substr(strrchr($attach_name, '.'), 1));
$filename=substr($attach_name, 0, strlen($attach_name) - strlen($extension) - 1);
$attach_fname=”abcd_$filename.$extentsion”;
其中$attach_name是我们提交的,我们按正常思维提交一个图片文件,得到如下结果。
$attach_name=”test.jpg”
$extension=”jpg”
$filename=”test”
$attach_fname=”abcd_test.jpg”
看到结果是如何生成的了吧?我们按非正常思维就要得到SHELL了,看如何利用漏洞。
二 漏洞变换利用
通过上面的分析,大家已经知道程序如何运行。我直接给出一个得到WEBSHELL的方法,并分析是如何跳过程序检查的,其他的利用方法大家可以仁者见仁,智者见智。
我们把$attach_name的值设为”/../../test.php.”(注意最后有个点)。至于为什么$attach_name我们可以构造,而且是任意构造。这就需要点PHP脚本知识和HTTP协议的基础,篇幅有限,不再介绍。再有因为本文文章较长,就不给数据包提交过程,附有PERL源码,大家可以查看文件名构造部分。下面给出如何绕过验证,分析关键代码。
if($attachextensions && @!eregi(substr(strrchr($attach_name, '.'), 1), $attachextensions)) {
showmessage('post_attachment_ext_notallowed');
}
很显然,如果$attachextensions为空,这句就无条件跳过,如果设置了$attachextensions类似为”jpg,gif,txt,zip,rar”形式,那我们刚才的构造就无效了,系统会报错,这种情况如何利用等会再说,这里我们假设这句能跳过执行。
$extension = strtolower(substr(strrchr($filename, '.'), 1));
这里的$filename值是我们提交的为”/../../test.php.”,执行这句后,$extension就为空了。
$filename = substr($filename, 0, strlen($filename) - strlen($extension) - 1);
同上,执行完这句后$filename就等于”/../../test.php”,注意最后没有点。
if(in_array($extension, array('php', 'php3', 'jsp', 'asp', 'cgi', 'pl'))) {
$extension = '_'.$extension;
}
因为$extension为空,所以这句跳过去了。
到此$attach_fname=”abcd_/../../test.php.” (随机串用abcd代替)
我们假设$discuz_root=”e:/www/discuz”,那么
$source=”e:/www/discuz/attachments/abcd_/../../test.php.”
看到了吧,经过我们构造后文件名就变成了上面的样子,保存到哪里不用再说了吧?
因为WINDOWS系统会忽略文件最后的点号,而LINUX系统虽然保留点号,但依然阻挡不了我们获得SHELL。这样我们就把自己的SHELL传到了论坛根目录,如果是LINUX系统,在执行时要带上最后一个点,WINDOWSj就不必了,连接地址如下。
http://localhost/discuz/test.php
到此,如何绕过程序验证的以及如何获得SHELL的都写完了。回到刚才的疑问,如果系统设置了$attachextensions怎么办呢?假设值为”gif,jpg,swf,txt,zip,rar”,大部分系统都是这样的。
大家一开始可能想不到如何利用,我也是偶然想到的,这种方法虽然价值不大,不过作为一种经验我还是有必要和大家分享一下,要发扬共享精神嘛。有了上面的分析基础,我就不长篇赘述,怕大家看着不耐烦。我们把$attach_name的值构造为”/../../images/default/logo.gif”,大家应该明白什么意思了吧?就是覆盖系统的图片文件,或者任何符合格式的文件,包括FLASH文件等。有什么用呢?覆盖了程序的logo图标,确实没什么用,不过搞恶作剧绰绰有余了。假如你自己做一个图片文件,上面写着”该网站存在漏洞“等字样,那个管理员肯定吓一跳。这让我想起来前一个月国内几个安全网站进行了以改写别人网站主页为目的的攻击,我不希望大家这样用这个漏洞,损人不利己。覆盖图片文件没有意义,但覆盖SWF文件就有意义了,我们很容易判断网站SWF的路径和文件名,而且一般网站或多或少有SWF文件,我们覆盖了这些文件,就可以做跨站脚本攻击,可别小看XSS,它比SQL注入的功能弱不了多少,可以得到用户COOKIE等敏感信息。由于篇幅限制,也不再赘述。
漏洞利用小结:
1.版本必须是2.0(2.x.x可能也受影响)以下,因为3.x以上的那个文件名的随机串是放在后面,我想了很长时间都不知怎么用,如果有高手想出来,可以一起讨论。
2.必须可以上传文件,当然要先注册个用户,不过大部分论坛受动网论坛的上传漏洞影响都关闭了上传
3.DISCUZ论坛经过了很多人修改,我读的是最原始的论坛源码,难免有人在修改过程中修改了上传部分,这样肯定会过滤”/,\,..”等敏感字符,所以也不受影响。
4.有的商业论坛虽然显示的”Power by DISCUZ2.0 COML”,但实际上早就打了高版本的补丁,所以它实践不能算是2.0版本,也不会受漏洞影响的。
5.在实际使用过程中,要具体问题具体分析,可以先上传几个普通图片分析一下,通过分析正常图片的路径,可以判断出很多信息。
三 漏洞防范
其实漏洞防范很简单,DISCUZ3论坛已经就没这个漏洞了,但也肯定不是万无一失的,真希望高人能想出利用方法。
其实它完全可以效法动网论坛,文件名指定,扩展名指定。文件名根据年月日和时间信息生成,扩展名手工指定可以采用下面方法。
先判断扩展名合法性,然后
switch($ext)
{
case “jpg”:
$saveExt=”jpg”;
case “gif”:
$saveExt=”gif”;
…
default:
exit(“扩展名非法”);
}
四 总结
因为文章较长,未能图文并茂,希望见谅。文章没什么技术性可言,只是一种经验,可以为大家提供一点思路。文章写的比较匆忙,有什么没表达清楚的地方可以一起探讨(本人笔名小花,网名小华),我的联系方式在程序上有,附带的程序只能用做测试。希望大家不要利用此漏斗做破坏,一起后果自己负责