Articles in the category of 代码审计

开源安全,代码审计

前言

最近渗透测试某站点的时候触发了一个报错,然后发现了站点使用的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
图片.png
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入口以审计结束,可以开始审计函数和类

重安装漏洞(Getshell)

由于安装文件一般是漏洞的重灾地,于是这里直接跳到了安装文件,果然找到了漏洞点。
在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()?>
由于写入的是配置文件,所以访问站点任意文件都会包含此文件,所以还可以当后门来用。

图片.png

审计过程还发现,如果采用sqlite安装,sqlite数据库文件名会以php结尾,并且我们可以控制数据库名数据库表,但是cms开始会新建一个名为<?php的数据表,sqlite文件的文件头会存在多个<?php,php解析到这里直接报错了。不知道怎么绕过。

sql注入

在install_action.php中还存在sql注入

<?php
$db_table = $_POST['table'];
$sql_db_exists = "SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME='$db_table'";

很明显的注入,就不细讲了

任意文件删除漏洞x2

全局搜索危险函数unlink
图片.png
一个个跟进审计,查找拿些变量可控,最终找到两个任意文件删除漏洞

在fmanage_action.php中提供了文件删除功能,但是未对文件路径做过滤,于是可以删除任意文件
图片.png
上面提到cms把所有url变量注册为了全局变量,于是只需访问action=del_file&cpath=../../../../../../../1.txt即可删除任意文件

cls_dir类中也存在一个任意文件删除漏洞
图片.png
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 $
图片.png
遗憾的是,这三个变量审计后发现我们都不可控,于是这条路没有走通,但是我觉得思路还是很不错的

失败的审计*2

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/

- Read More -
This is just a placeholder img.