PHP-Nuke web中心系统中的用户登录SQL hacking 之所以翻译这个文章,是因为它非常细致完整地描述了发现安全漏洞的过程,包括成功的和不成功的尝试,提供了一些有用的技术和思路。PHP-Nuke本身的这些已被发现的漏洞会很快被修补,但发现问题的思路不会有大的改变,所以关键在于学习他的思路。文中可能有一些理解或翻译上的错误,原文可以在找到:
http://www.wiretrip.net/rfp/p/doc.asp?id=60&iface=2
-----/ RFP2101 /-------------------------------/ rfp.labs / wiretrip/----
RFPlutonium to fuel your PHP-Nuke
SQL hacking user logins in PHP-Nuke web portal
------------------------------------/ rain forest puppy / rfp@wiretrip.net
目录:
-/ 1 / 标准的建议信息
-/ 2 / 总览
-/ 3 / 细解
-/ 4 / 其他手段
-/ 5 / 解决方案
--------------------------------------------------------------------------
声明:没人强迫你读这个,不想看的话你可以不看
--------------------------------------------------------------------------
-/ 1 / 标准的建议信息 /------------------------------------
软件包: PHP-Nuke
厂商主页: www.phpnuke.org
测试过的版本: 4.3
平台:独立于平台(PHP)
联系厂商时间: 12/29/2000
CVE 候选号: CAN-2001-0001
脆弱性类型: 访问验证弱点(普通用户和管理员)
RFPolicy v2:http://www.wiretrip.net/rfp/policy.html
以前存在问题: 绕过管理员认证, Aug 2000
BID: 1592CVE:CVE-2000-0745SAC: 00.35.032
当前版本:4.4(可能还是有问题,未
测试)
-/ 2 / 总览 /------------------------------------------
PHP-Nuke 是一个用PHP实现的网站和新闻中心系统。我对它的外表和提供的功能印象深刻,决定在以后的两个项目中用到它。就象我决定使用的其他代码一样,我会对那些代码做一个快速的审核(开放源码万岁)。我对代码整体上是满意的,它的确消除了一些有关SQL的安全问题。
我觉得把这个脆弱性的如何工作的整个过程揭示出来,从教育的眼光来看,比只写什么“PHP-Nuke是可脆弱的”有意义的多。如果你想了解更多关于SQL hacking,应该看看RFP2K01,在:
http://www.wiretrip.net/rfp/p/doc.asp?id=42
这并不是个非常有用的入侵,它只是允许你冒充其他用户得到他们加密后的口令。它也给攻击者暴力破解用户或管理员的口令提供了可能性。
-/ 3 / 细解 /--------------------------------------
首先,为了更好地辅助SQL hacking,打开SQL查询的记录选项是有帮助的。对MySQL来说只要在(safe_mysqld)启动的时候加上'-l logfile'参数就行了。
其次,让我们看一下代码。因为是用PHP写的而且用MySQL,我们的目标函数当然是mysql_query()了。让我们把所有的mysql_query()都grep出来:
[rfp@cide nuke]# ls
admin/config.php index.php print.php topics.php
admin.php counter.phplanguagescroller.js ultramode.txt
article.php dhtmllib.jslinks.php search.phpupgrades
auth.inc.phpfaq.phpmainfile.phpsections.phpuser.php
backend.php footer.php manual/ stats.php voteinclude.php
banners.php friend.php memberslist.php submit.php
cache/header.php pollBooth.php themes/
comments.phpimages/pollcomments.phptop.php
[rfp@cide nuke]# grep mysql_query *
admin.php:$result = mysql_query("SELECT qid FROM queue");
.... 超过254条SQL queries就不贴在这了 ....
让我们来看看那些带有变量的语句,因为里面可能带有用户的输入。例如一些select语句:
article.php:mysql_query("update users set umode='$mode',
uorder='$order', thold='$thold' where uid='$cookie[0]'");
banners.php:mysql_query("delete from banner where bid=$bid");
comments.php: $something = my
sql_query("$q");
user.php: $result = mysql_query("select email, pass from users where
(uname='$uname')");
index.php:mysql_query("insert into referer values (NULL, '$referer')");
来自 article.php 的查询带有四个变量: $mode, $order, $thold,和 $cookie[0]。 comments.php 很有趣,看起来整个查询放在$q变量中,这意味着我们必须到文件中去看那个变量的值是什么,在文件中,我们可以看到:
$q = "select tid, pid, sid, date, name, email, url, host_name,
subject, comment, score, reason from comments where sid=$sid
and pid=$pid";
if($thold != "") {
$q .= " and score>=$thold";
} else {
$q .= " and score>=0";
}
if ($order==1) $q .= " order by date desc";
if ($order==2) $q .= " order by score desc";
所以我们可以看到$q里用到了变量$sid和$pid,可能还有$thold,如果它被定义了的话。
现在我们该怎么办?让我们来看看那些变量里到底有些什么。我们从article.php中的那个查询开始。去掉注释后,实际的代码是这样的:
<?PHP
if(!isset($mainfile)) { include("mainfile.php"); }
if(!isset($sid) && !isset($tid)) { exit(); }
if($save) {
cookiedecode($user);
mysql_query("update users set umode='$mode', uorder='$order',
thold='$thold' where uid='$cookie[0]'");
getusrinfo($user);
$info = base64_encode("$userinfo[uid]:$userinfo[uname]:".
"$userinfo[pass]:$userinfo[storynum]:$userinfo[umode]:".
"$userinfo[uorder]:$userinfo[thold]:$userinfo[noscore]");
setcookie("user","$info",time()+$cookieusrtime);
}
(注意:为了在这个安全公告中显示,代码格式做了些调整)
我们看到对变量$mode, $order, $thold, 和$cookie[0]并没有明显的处理。然而,mainfile.php被包含进来而且在函数cookiedecode()中可能会有些处理,所以我们也应该看看它们。
我们先得看看mainfile.php里是不是已经定义了变量 $mode, $order,$thold, or $cookie:
[rfp@cide nuke]# grep \$mode mainfile.php
[rfp@cide nuke]# grep \$order mainfile.php
[rfp@cide nuke]# grep \$thold mainfile.php
[rfp@cide nuke]#
嗯, 可以看出mainfile.php 没有对那些变量做任何处理。然而有一个多余的变量$cookie被返回了(在这看不到)。这是因为在mainfile.php中有函数cookiedecode() (和其他相似的函数)。cookiedecode() 的代码是这样的:
function cookiedecode($user) {
global $cookie;
$user = base64_decode($user);
$cookie = explode(":", $user);
return $cookie;
}
cookiedecode()调用取到变量$user的值,用base64方式解码,以’:’为定界符分成几个部分,放入$cookie[]数组。这是有意义的,因为上面的SQL查询用到了$cookie[0],数组的第一个元素。
怪?那个$user 变量是从哪来的呢? grep 一下mainfile.php 可以知道$user 变量只在这个函数中被用到。
好啊。这意味着作者对变量$user(他被解码并拆分成$cookie[0]数组), $mode, $order, $thold什么都没干。对那些不熟悉PHP的人说一声,PHP会为从URL得到的参数各自己分配一个全局变量。例如,下面的查询:
/somefile.php?varb1=rain&value2=forest¶m3=puppy
会在脚本中定义出三个全局变量$varb1, $value2, 和$param3,它们的值分别为'rain', 'forest', and 'puppy'。这意味着如果我们以如下的URL向article.php提交,我们可以为变量$mode, $order,和$thold赋上任意的值:
>
/article.php?mode=rain&order=forest&thold=puppy
在我们做这些之前,别忘了以下的程序片断:
if($save) {
...
这意味着变量$save必须被设置。grep一下mainfile.php看不到变量$save,所以我们应该在URL中设置它的值:
/article.php?mode=rain&order=forest&thold=puppy&save=1
让我们试试吧。对这个页面发出请求,没有东西返回,因为我忘掉了下面的这行:
if(!isset($sid) && !isset($tid)) { exit(); }
我们需要把$sid 和$tid变量加到URL行,现在是这样了:
/article.php?mode=rain&order=forest&thold=puppy&save=1&sid=0&tid=0
这次返回了一个错误页面。看看我们的mysql日志记录,