最近渗透测试某站点的时候触发了一个报错,然后发现了站点使用的CMS,百度了一下是一个国产开源的企业级CMS。从官网拉下来审了一下,再此记录一下
下面是index.php入口文件
<?php
if( !file_exists(dirname(__FILE__) . "/include/config.db.php") )
{
header("Location:install/index.php");
exit();
}
require_once( "include/common.inc.php" );
$mod = str_replace( '../', '', $mod );
if( empty( $mod ) )
{
$mod = 'index';
}
$action_file = WEB_INCLUDE . '/action/' . $mod . '.php';
file_exists($action_file) && require_once($action_file);
$cls_tpl = cls_app:: get_template( $mod );
$cls_tpl->display();
?>
很常见的cms入口形式,但是可以注意到第八行将../替换为空,这里怀疑会不会存在目录穿越,可以采用..././这样的形式来穿越到上一层。但是第15行限制了后缀必须为php,且由于前缀也被限制于是不能使用zip伪协议拿shell,如果php版本为5.2可以采用00截断包含任意文件,这里暂时卡住,继续审计,第2行应该为配置文件略过,跟进第7行的common.inc.php
common.inc.php开头先定义了许多常量,然后更改了一些php配置,接着又引入了两个文件,跟进发现配置了一些变量,先不管,继续向下审计common.inc.php
<?php
$req_data = array();
foreach( array('_GET', '_POST', '_COOKIE') as $_request )
{
foreach( $$_request as $_k => $_v )
{
${$_k} = _get_request($_v);
if( '_COOKIE' != $_request )
{
$req_data[$_k] = _get_request($_v);
}
}
}
unset($_GET, $_POST);
?>
上面代码可以很明显的发现,cms把$_GET,$_POST,$_COOKIE注册为了全局变量。所以之后可能存在变量覆盖,之后的代码引入了全局函数和全局类。这时候CMS入口以审计结束,可以开始审计函数和类
由于安装文件一般是漏洞的重灾地,于是这里直接跳到了安装文件,果然找到了漏洞点。
在install_action.php中,安装完成后会把前端文件重命名,但是后端逻辑文件依旧存在,所以如果知道安装文件位置即可重安装
<?php
function install_end()
{
//安装收尾
//把安装文件的名字换了
@rename('index.php', 'index.php_bak');
}
这里只重命名了前端文件
而且在文件280行,存在写文件操作,文件名为php且文件内容可控
<?php
$db_tablepre = $_POST['tablepre'];
write_db_config($db_type, $db_host, $db_name, $db_pass, $db_table, $db_tablepre);
function write_db_config($db_type, $db_host, $db_name, $db_pass, $db_table, $db_tablepre)
{
//写入数据库配置
global $db_code;
$db_config = "";
$db_config .= "<?php\n\n";
$db_config .= "\$db_type = '" . $db_type . "';\n";
$db_config .= "\$db_host = '" . $db_host . "';\n";
$db_config .= "\$db_name = '" . $db_name . "';\n";
$db_config .= "\$db_pass = '" . $db_pass . "';\n";
$db_config .= "\$db_table = '" . $db_table . "';\n";
$db_config .= "\$db_ut = '" . $db_code . "';\n";
$db_config .= "\$db_tablepre = '" . $db_tablepre . "';\n\n";
$db_config .= "?>";
require_once("../include/class/class.file.php");
$cls_file = new cls_file('../include/config.db.php');
$cls_file-> set_text($db_config);
return $cls_file-> write();
}
由于文件内容可控,我们可以通过tablepre=exp来写入一个恶意php文件
tablepre=dcr_qy_';?><?php phpinfo()?>
由于写入的是配置文件,所以访问站点任意文件都会包含此文件,所以还可以当后门来用。
审计过程还发现,如果采用sqlite安装,sqlite数据库文件名会以php结尾,并且我们可以控制数据库名数据库表,但是cms开始会新建一个名为<?php的数据表,sqlite文件的文件头会存在多个<?php,php解析到这里直接报错了。不知道怎么绕过。
在install_action.php中还存在sql注入
<?php
$db_table = $_POST['table'];
$sql_db_exists = "SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME='$db_table'";
很明显的注入,就不细讲了
全局搜索危险函数unlink
一个个跟进审计,查找拿些变量可控,最终找到两个任意文件删除漏洞
在fmanage_action.php中提供了文件删除功能,但是未对文件路径做过滤,于是可以删除任意文件
上面提到cms把所有url变量注册为了全局变量,于是只需访问action=del_file&cpath=../../../../../../../1.txt即可删除任意文件 cls_dir类中也存在一个任意文件删除漏洞
cache_clear.php引用了这个类
<?php
$cls_dir = new cls_dir();
$cls_dir-> delete_dir( WEB_CACHE . "/template/{$tpl_dir}" );
同理,通过控制url参数tpl_dir即可任意文件删除
在db.class.php中的构造方法里,可以进行数据库连接
<?php
class cls_db
{
private $pdo;
private $db_type;
private $host;
private $name;
private $pass;
private $table;
private $ut;
private $conn;
private $result;
private $rs;
private $str_error; //错误信息
/**
* 构造函数
* @param string $db_type 数据库类型
* @param string $host 数据库地址
* @param string $name 数据库用户名
* @param string $pass 数据库密码
* @param string $table 数据库名
* @param string $ut 数据库编码
* @return resource 成功返回一个连接的resource
*/
function __construct( $db_type, $db_host, $db_name, $db_pass, $db_table, $db_ut )
{
$this->db_type = $db_type;
$this->host = $db_host;
$this->name = $db_name;
$this->pass = $db_pass;
$this->table = $db_table;
$this->ut = $db_ut;
if( !$this->conn )
{
$this->connect();
}
}
因为构造方法只有在实例化新类才会被执行,所以理论上,如果我们可以任意实例化任意类,我们可以控制数据库连接的ip和端口,再通过mysql任意文件读取漏洞,即可达到任意文件读取,全局搜索new $
遗憾的是,这三个变量审计后发现我们都不可控,于是这条路没有走通,但是我觉得思路还是很不错的
class.email.php文件中会存在任意ip建立套接字,发送数据可控,于是我们可以通过crlf来SSRF,可以攻击内网的php-fpm,redis等应用,但是在刚开始建立套接字的时候,cms会判断对应ip是否返回2或者3,
<?php
function smtp_ok()
{
$response = str_replace("\r\n", "", fgets($this->sock, 512));
$this->smtp_debug( $response . "\n" );
if (!ereg("^[23]", $response))
{
fputs($this->sock, "QUIT\r\n");
fgets($this->sock, 512);
cls_app::log("Error: Remote host returned \"" . $response . "\"\n");
return false;
}
return true;
}
?>
如果攻击内网,必须要对应内网服务在建立连接时,返回数据中带有2或者3,我们才能发送数据,否者程序会直接退出。
cms官网:http://www.dcrcms.com/
打赏我,让我更有动力~
© 2016 - 2024 掌控者 All Rights Reserved.