博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
然之协同系统6.4.1 SQL注入导致getshell
阅读量:5085 次
发布时间:2019-06-13

本文共 6617 字,大约阅读时间需要 22 分钟。

 前言

先知上一个大佬挖的洞,也有了简单的分析

https://xianzhi.aliyun.com/forum/topic/2135

我自己复现分析过程,漏洞的原理比较简单,但是漏洞的利用方式对我而言则是一种新的利用方式。本文对分析过程做一个记录。

 正文

分析软件运行的流程

拿到一个需要分析的 php 程序,首先看看客户端的 http 请求是如何对应到程序中的代码的。

首先得找一个分析的开始点,就以 触发漏洞 的 请求为示例把。

GET /cash/block-printTradeBlock.html?param=eyJvcmRlckJ5IjoiaWQgbGltaXQgMCwxO3NlbGVjdCBpZigxPTIsMSxzbGVlcCgyKSkjIiB9 HTTP/1.1Host: hack.ranzhi.topPragma: no-cacheCache-Control: no-cacheUpgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9Cookie: PHPSESSID=2c17fafndhnfle6j4r9dskopk3; lang=zh-cn; theme=default; rid=kcvhkos22q574sqdjiha39icl5; keepLogin=false; XDEBUG_SESSION=14822Connection: close

安装系统时我们设置 www 目录为虚拟主机目录, 所以当我们访问 /cash/block-printTradeBlock.html  时,实际上访问的是 www/cash/block-printTradeBlock.html, 但是 cash 目录中并没有相关的文件

006daSSqgy1fyfx9ix07tj30qz0ajdgu.jpg

不过该目录下有 .htaccess 文件,通过该文件可以重写 url, 根据规则我们知道,如果访问 cash 目录下不存在的文件,会把请求交给 index.php 处理。

.htaccess 文件参考

http://t.cn/REFVyCJhttps://www.zybuluo.com/phper/note/73726

那下面就分析 index.php 即可,下好断点,然后发送数据包

006daSSqgy1fyfx9ojw5zj30x80i540d.jpg

f7 跟进 loader.php, 首先加载了一些基础类。

006daSSqgy1fyfx9u0i9lj30jh09o756.jpg

然后做一些初始化的操作,实例化一些基础对象,后面用来加载程序的主体

006daSSqgy1fyfx9zqmfyj30vj0cjjt4.jpg

然后是设置路由方式

$app->parseRequest();

006daSSqgy1fyfxa5k14ij30zd0g0abg.jpg

006daSSqgy1fyfxabpqs1j30nq0evwfs.jpg

会对 处理后 url- 分割,第一项作为 module_name 第二项作为 method_name. 以上面的数据包为例。执行完后的结果如下。

006daSSqgy1fyfxahu1loj30eu03xglo.jpg

此时我们已经设置好了 模块名 和 方法名,  下面回到 loader.php

$common->checkPriv();  # 权限校验$app->loadModule();     #  加载相关模块的方法

进入 loadModule 方法

public function loadModule()    {        $appName    = $this->appName;        $moduleName = $this->moduleName;        $methodName = $this->methodName;                /*         * 设置control的类名。         * Set the class name of the control.         **/        $className = class_exists("my$moduleName") ? "my$moduleName" : $moduleName;        /*         * 创建control类的实例。         * 根据 `$app->parseRequest()` 设置好的 模块名 实例化对应的类         * Create a instance of the control.         **/        $module = new $className();        $this->control = $module;        /*          * 使用反射机制获取函数参数的默认值         * 通过反射获取 函数的参数名称,         * 通过 `php` 的反射机制 , 获取参数名, 并且初始化好         *         * */        $defaultParams = array();        $methodReflect = new reflectionMethod($className, $methodName);        foreach($methodReflect->getParameters() as $param)        {            $name = $param->getName();            $default = '_NOT_SET';            if(isset($paramDefaultValue[$appName][$className][$methodName][$name]))            {                $default = $paramDefaultValue[$appName][$className][$methodName][$name];            }            elseif(isset($paramDefaultValue[$className][$methodName][$name]))            {                $default = $paramDefaultValue[$className][$methodName][$name];            }            elseif($param->isDefaultValueAvailable())            {                $default = $param->getDefaultValue();            }            $defaultParams[$name] = $default; # 组成一个由 defaultParams[参数名] = "" 构成的字典        }        /**          * 根据PATH_INFO或者GET方式设置请求的参数。         * 根据请求方式, 从请求数据包中获取需要的参数信息。         */        if($this->config->requestType != 'GET')        {            $this->setParamsByPathInfo($defaultParams);        }        else        {            $this->setParamsByGET($defaultParams);        }                    # 过滤数据        if($this->config->framework->filterParam == 2)        {            $_GET     = validater::filterParam($_GET, 'get');            $_COOKIE  = validater::filterParam($_COOKIE, 'cookie');        }        /* 调用方法,并传入参数 */        call_user_func_array(array($module, $methodName), $this->params);        return $module;    }

该函数的流程为

  • 根据 $app->parseRequest() 设置好的 模块名 实例化对应的类
  • 通过 php 的反射机制 , 获取方法参数名, 并且初始化好
  • 根据请求方式, 从请求包中获取需要的参数信息到 $defaultParams
  • 过滤数据
  • 调用方法,并传入参数
    看一看设置参数的方式

006daSSqgy1fyfxar3w4ij30nr0gtmyh.jpg

按照 -  分割 url格式为

module_name-method_name-param1-param2

所以在该程序中 wwwapp 目录是相互对应的。

006daSSqgy1fyfxb3u54qj30r40fqjtz.jpg

当我们请求

/dir_name/module_name-method_name-param-...-paramN.html

最后会调用 app 目录下 module_name 中的 control.php

module_name->method_name(param1,....,paramN)

比如

/cash/block-printTradeBlock.html

实际就是调用 app 目录下 cash 中的 control.php

block->printTradeBlock()

006daSSqgy1fyfxbd5t99j30pg0cu406.jpg

SQL 注入分析

漏洞出现在 lib/base/dao/dao.class.php

006daSSqgy1fyfxbhdf2yj30vp0httar.jpg

这里只对 $order 部分进行了校验, 而没有对 limit 后面的部分进行校验。

看到 printTradeBlock

006daSSqgy1fyfxbmwdilj30n10cyabw.jpg

$this->processParams() 中设置好 $this->params

006daSSqgy1fyfxbwqo8rj30lv0bjmxu.jpg

可以看到 $this->params 就是 json_decode(base64_decode($_GET['param']))

然后又会调用

orderBy($this->params->orderBy)

所以我们可以控制 limit 后面的部分, SQL注入

这套程序中还会执行 sql 语句,用的是 Pdo用的是 $pdo->query(), 这个函数可以一次执行多条 sql 语句。

/**     * 执行SQL语句,返回PDOStatement结果集。     * Query the sql, return the statement object.     *      * @access public     * @return object   the PDOStatement object.     */    public function query($sql = '')    {        /* 如果有错误,返回一个空的PDOStatement对象,确保后续方法能够执行。*/        /* If any error, return an empty statement object to make sure the remain method to execute. */        if(!empty(dao::$errors)) return new PDOStatement();           if($sql)        {            $sql       = trim($sql);            $sqlMethod = strtolower(substr($sql, 0, strpos($sql, ' ')));            $this->setMethod($sqlMethod);            $this->sqlobj = new sql();            $this->sqlobj->sql = $sql;        }        else        {            $sql = $this->processSQL(); // 大概就是获取 sql 语句        }        $key = md5($sql);        try        {            $method = $this->method;            $this->reset();            if($this->slaveDBH and $method == 'select')            {                if(isset(dao::$cache[$key])) return dao::$cache[$key];                $result = $this->slaveDBH->query($sql);                dao::$cache[$key] = $result;                return $result;            }            else            {                if($this->method == 'select')                {                    if(isset(dao::$cache[$key])) return dao::$cache[$key];                    $result = $this->slaveDBH->query($sql);                    dao::$cache[$key] = $result;                    return $result;                }                return $this->dbh->query($sql);            }        }        catch (PDOException $e)         {            $this->sqlError($e);        }    }

这样我们的利用方式就简单了。

  • 闭合 limit 语句,用 ;  连接多条语句
  • 没有回显, 使用 时间盲注

poc

006daSSqgy1fyfxc8ir74j30nt09j0sp.jpg

任意文件下载 && 任意文件删除

由于可以一次执行多条 sql 语句 ,我们实质上已经可以控制数据库了。

app/sys/file/control.php ,有两个函数  deletedownload, 分别用于删除文件和下载文件。

以  delete 为例

006daSSqgy1fyfxcerm9kj30xb0augmk.jpg

$this->file->getById($fileID)  时间就是在 表前缀sys_file 中查找对应 id 相对应的 路径。

006daSSqgy1fyfxcl3fvtj30w80eh77q.jpg

利用 sql 注入修改为目标路径(用 ../ 进行目录跳转),然后选择相应 id 即可删除指定文件。下载文件也是类似。

getshell

删除 my.php 后 会要求我们重新安装系统。

在重装系统的最后一步,会直接使用 POST 中的值,来设置 my.php 的内容,同时 , 访问 install.php 时,是不会调用参数过滤的函数的

006daSSqgy1fyfxcrb7wjj30oz0bpab9.jpg

所以可以注入 php 代码,getshell

006daSSqgy1fyfxcxk54fj30ti0ckgmk.jpg

006daSSqgy1fyfxd3q0u5j30hh0ae75o.jpg

转载于:https://www.cnblogs.com/hac425/p/9416784.html

你可能感兴趣的文章
添加按钮
查看>>
移动端页面开发适配 rem布局原理
查看>>
Ajax中文乱码问题解决方法(服务器端用servlet)
查看>>
会计电算化常考题目一
查看>>
阿里云服务器CentOS6.9安装Mysql
查看>>
剑指offer系列6:数值的整数次方
查看>>
js 过滤敏感词
查看>>
poj2752 Seek the Name, Seek the Fame
查看>>
软件开发和软件测试,我该如何选择?(蜗牛学院)
查看>>
基本封装方法
查看>>
bcb ole拖拽功能的实现
查看>>
生活大爆炸之何为光速
查看>>
bzoj 2456: mode【瞎搞】
查看>>
[Typescript] Specify Exact Values with TypeScript’s Literal Types
查看>>
[GraphQL] Reuse Query Fields with GraphQL Fragments
查看>>
Illustrated C#学习笔记(一)
查看>>
理解oracle中连接和会话
查看>>
两种最常用的Sticky footer布局方式
查看>>
Scrapy实战篇(三)之爬取豆瓣电影短评
查看>>
HDU 5510 Bazinga KMP
查看>>