20-PHP代码审计——damicms5.3-5.4漏洞分析

Posted songly_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了20-PHP代码审计——damicms5.3-5.4漏洞分析相关的知识,希望对你有一定的参考价值。

    本次分析damicms的两个漏洞,一个是5.3版本的任意文件操作漏洞,另一个是5.4版本的逻辑支付漏洞。

 

漏洞一:支付逻辑漏洞

    damicms5.4版本存在订单支付逻辑漏洞,提交订单后可将商品的数量修改成-1,然后选择站内支付,可以成功提交订单。

影响版本:

damicms5.4

漏洞环境:

damicms5.4

php5.6.27

 

在首页的产品展示中随便选择一个商品点击购买,然后填完地址信息后选择站内扣款,开启burpsuite抓包然后点击提交订单,如下所示

 

然后将qty字段的值修改成-1,然后点击Go放行数据包,可以看到响应消息返回提交订单成功,漏洞复现成功。

 

接下来我们分析后台订单的业务逻辑,找到lib/Action/MemberAction的dobuy方法

//订单处理
    function dobuy(){
        self::is_login();
        if (!$_POST) {
            exit();
        }
        if (!is_array($_POST['id'])) {
            $this->error('您的购物为空!');
            exit();
        }
        if ($_POST['realname'] == '' || $_POST['tel'] == '') {
            $this->error('收货人信息为空!');
            exit();
        }
        $trade_type = (int)$_POST['trade_type'];
        $iscart = (int)$_POST['iscart'];
        $group_trade_no = "GB" . time() . "-" . $_SESSION['dami_uid'];
        if ($iscart == 1) {
            import('@.ORG.Cart');
            $cart = new Cart();
            $cart->destroy();
        }
    //过滤可能出现的xss
        $_POST = array_map('remove_xss', $_POST);
        $trade = M('member_trade');
        if (C('TOKEN_ON') && !$trade->autoCheckToken($_POST)) {
            $this->error(L('_TOKEN_ERROR_'));
        }//防止乱提交表单

//循环出购物车 写进数据库
        if ($trade_type == 1) {

    //......

        } else if ($trade_type == 2) {

         //......

        //接着会执行这段逻辑
        } else if ($trade_type == 3) {
            $title = '';
            $total_fee = 0;
            $total_num = 0;
            for ($i = 0; $i < count($_POST['id']); $i++) {
                $price = (float)M('article')->where('aid=' . intval($_POST['id'][$i]))->getField('price');
                //过滤非数字字符串
                if (!is_numeric($_POST['id'][$i]) || !is_numeric($_POST['price'][$i]) || !is_numeric($_POST['qty'][$i])) {
                    continue;
                }
                //这行代码是漏洞的触发点
                $total_fee += (intval($_POST['qty'][$i]) * $price) * 1;
            }
            $have_money = M('member')->where('id=' . $_SESSION['dami_uid'])->getField('money');
            if ($have_money < $total_fee) {
                $this->assign('jumpUrl', U('Member/chongzhi'));
                $this->error('您的余额不足,请充值!');
                exit();
            }
            for ($i = 0; $i < count($_POST['id']); $i++) {
                //这里还过滤了一次非数字字符串
                if (!is_numeric($_POST['id'][$i]) || !is_numeric($_POST['price'][$i]) || !is_numeric($_POST['qty'][$i])) {
                    continue;
                }
                $data['gid'] = $_POST['id'][$i];
                $data['uid'] = $_SESSION['dami_uid'];
                $data['price'] = (float)M('article')->where('aid=' . $data['gid'])->getField('price');//必须
                $data['province'] = $_POST['province'];
                $data['city'] = $_POST['city'];
                $data['area'] = $_POST['area'];
                $data['sh_name'] = $_POST['realname'];
                $data['sh_tel'] = $_POST['tel'];
                $data['address'] = $_POST['address'];
                $data['group_trade_no'] = $group_trade_no;
                $data['out_trade_no'] = "DB" . time() . "-" . $_SESSION['dami_uid'];
                $data['servial'] = $_POST['gtype'][$i];
                $data['status'] = 1;//已付款等待发货
                $data['trade_type'] = 3;
                $data['addtime'] = time();
                $data['num'] = (int)$_POST['qty'][$i];
                $total_num += $data['num'];
                $trade->add($data);
                if (strlen($title) < 400) {
                    $title .= $_POST['name'][$i] . "&nbsp;&nbsp;数量:" . $data['num'] . ' 单价:' . $data['price'] . '<br>';
                }
            }
//扣款
            M('member')->setDec('money', 'id=' . $_SESSION['dami_uid'], $total_fee);
            if (intval(C('MAIL_TRADE')) == 1) {
                $config = F('basic', '', './Web/Conf/');
                $user_name = $config[sitetitle] . '管理员';
                $subject = $config[sitetitle] . '订单提醒';
                $bodyurl = '下单时间:' . date('Y-m-d H:i:s', time()) . '<br>会员编号:' . $_SESSION['dami_uid'] . '<br>姓名:' . $_POST['realname'] . '<br>订单号:' . $group_trade_no . '<br>付款方式:站内扣款<br>订购物件:<br>' . $title . '<br>总数量:' . $total_num . '<br>总金额:' . $total_fee . '元';
                $sendto_email = C('MAIL_TOADMIN');
                $email_port = C('MAIL_PORT');
                send_mail($sendto_email, $user_name, $subject, $bodyurl, $email_port);
            }
            $this->assign('group_trade_no', $group_trade_no);
            $this->display('buysuccess');
        } else {
            $this->error('交易方式不确定!');
            exit();
        }
    }

 

dobuy函数内部对提交的数据其实过滤并不算严格,接着调用了这行代码:

$total_fee += (intval($_POST['qty'][$i]) * $price) * 1;

    intval函数的作用是获取变量的整数值,这里的qty代表数量,qty的值为-1并且没有对负数进行过滤,最后计算得到的金额为负,导致产生漏洞。

 

 

在订单页面中可以看到显示已付款

 

    支付漏洞分析思路可以从负数和整形溢出两方面入手,负数很好理解,因为像支付这种业务逻通常是不允许出现负数情况的,一般都要做好负数的处理。另一个就是整形溢出,首先php是一个弱类型语言,只有当程序在运行期间才会确定变量的数据类型(自动类型转换),因此在编写代码期间我们可以对变量赋予任何数据类型,因此php在进行类型转换时有可能出现整形溢出导致漏洞产生。例如int的数据类型的范围是-2147483648 ~ 2147483647,假设金额的数据类型是int,然后通过抓包把金额改成4294967297时,php在对输入的4294967297数据类型强制转换成int时就会导致整形溢出,最后ph会p把溢出的数据丢弃,此时金额的数目可能就变成了1。

 

 

漏洞二:任意文件操作漏洞

影响版本:

damicms5.5.3

漏洞环境:

damicms5.5.3

php5.6.27

 

任意文件删除漏洞主要出现在后台,在插件工具 ---> 模板管理

 

随便选择一个文件点击删除,开启Burpsuite工具抓包,http请求数据格式如下

 

 

|Web|Tpl|default|footer*html代表要删除的文件路径,Tpl/Del/id/是del函数的路由,找到Admin\\Lib\\Action\\TplAction.class.php文件,定位到del方法

	// 删除模板
    public function del(){
		//提取要删除的文件路径
		$id = dami_url_repalce(str_replace('*','.',trim($_GET['id'])));
		//校验请求的文件路径是否具备删除的权限
		if (!substr(sprintf("%o",fileperms($id)),-3)){
			$this->error('无删除权限!');
		}
		//删除文件
		@unlink($id);
		if (!empty($_SESSION['tpl_jumpurl'])){
			//assign函数中参数1为展示页面中需要的参数
			$this->assign("jumpUrl",$_SESSION['tpl_jumpurl']);
		}
		else{
			$this->assign("jumpUrl",'?s=Tpl/index');
		}
		$this->success('删除文件成功!');
    }

 

del方法内部调用了dami_url_repalce函数提取请求中的文件路径,分析dami_url_repalce函数做了哪些处理

//替换采集等通过url参数传值
function dami_url_repalce($xmlurl,$order='asc'){
	if($order=='asc'){
		return str_replace(array('|','@','#'),array('/','=','&'),$xmlurl);
	}else{
		 return str_replace(array('/','=','&'),array('|','@','#'),$xmlurl);
	}
}

    该函数内部会将xmlurl的文件路径中的竖线“|”符号会被替换成“/”符号,最终替换后的文件路径为./Web/Tpl/default/footer.html ,这里没有对“../或./”这些敏感,危险的文件路径进行任何实质性的安全过滤。接着del函数内部中的fileperms函数判断了请求的文件路径是否具备删除权限,然后调用unlink函数删除文件。

 

     假设是在黑盒测试的环境下,我们不知道该站点的网站文件目录结构,可以通过工具遍历爆破网站的目录结构,如果读取到一些敏感的目录文件时,再构造恶意的文件路径,例如通过任意文件删除漏洞,删除install目录下的install.lck文件,然后再重装网站从而截取数据库的管理员权限。

 

 

在http请求中将参数s的值替换成Tpl/Del/id/.|install*lck  ,如下所示

     响应消息返回200,成功将install.lck文件删除,接着我们就可以直接执行install/index.php文件重装网站截取数据库的管理员权限。

 

add函数同样存在任意文件读取漏洞

// 编辑模板
public function add(){
	$filename = dami_url_repalce(str_replace('*','.',trim($_GET['id'])));
	if (empty($filename)) {
		$this->error('模板名称不能为空!');
	}
	$content = read_file($filename);
	$this->assign('filename',$filename);
	$this->assign('content',htmlspecialchars($content));
	$this->display('add');
}

     add函数内部同样也没有过滤,只判断了传入的文件名是否为空,接着就读取了文件的内容。

 

读取Web/Conf/config.php文件的内容,如下所示

漏洞分析完毕。

以上是关于20-PHP代码审计——damicms5.3-5.4漏洞分析的主要内容,如果未能解决你的问题,请参考以下文章

e语言代码如何审计

代码审计那些代码审计的思路

当前市面上的代码审计工具哪个比较好?

代码审计思路之PHP代码审计

代码审计利器-Seay源代码审计系统

代码审计系列:审计思路学习笔记