代码审计-minicms
2019-12-23 12:21:57 Author: xz.aliyun.com(查看原文) 阅读量:261 收藏

下载Install.php 安装上 这个cms竟然没有数据库
先去看了一下文件的功能

文件功能列表 https://www.4hou.com/technology/19138.html

│  build.php
│  index.php 整个项目的入口,首先引入核心库mc-core.php,然后进行路由,对路由结果进行相应的渲染,相当于MVC中的C
│  install.txt 复制为php文件后,用来安装MiniCMS
│  README.md

├─mc-admin 管理功能的实现
│      conf.php 用户设置页面,包括接收和保存更改的设置
│      editor.php 编辑器的大小、样式调整的库
│      foot.php html<foot>标签构造
│      head.php token验证,html<head>标签构造;若验证失败,跳转至主页
│      index.php 后台登陆身份验证页面
│      page-edit.php 页面编写处理逻辑,包括显示编辑页面、接收提交的页面、页面序列化储存
│      page.php 管理页面的库,声明加载数据、删除页面、还原页面(从回收站还原)
│      post-edit.php 文章编写处理逻辑,包括显示编辑页面、接收提交的页面、页面序列化储存
│      post.php 管理文章的库,声明加载数据、删除文章、还原文章(从回收站还原)
│      style.css 后台用到的CSS

└─mc-files
    │  markdown.php 一个开源的markdown解析库
    │  mc-conf.php 配置文件,包含用户名和密码等敏感信息
    │  mc-core.php 引入mc-tags、mc-conf,声明404函数
    │  mc-rss.php 订购RSS的链接
    │  mc-tags.php 相当于M,引入markdown、包括一些核心函数,包括了加载各种信息的函数(网站名、文章数、前进后退等,中间有各种过滤,可以重点分析)

    ├─pages
    │  └─index
    │          delete.php 使用数组储存了删除页面的信息(id、标题、标签等)与data文件夹内的文章数据一一对应
    │          draft.php 使用数组储存了草稿页面的信息(id、标题、标签等)与data文件夹内的文章数据一一对应
    │          publish.php 使用数组储存了已发布的页面的信息(id、标题、标签等)与data文件夹内的文章数据一一对应

    ├─posts
    │  ├─data 储存了文章内容的反序列化数据(文章内容等)
    │  └─index
    │          delete.php 使用数组储存了删除的文章的信息(id、标题、标签等)与data文件夹内的文章数据一一对应
    │          draft.php 使用数组储存了草稿文章的信息(id、标题、标签等)与data文件夹内的文章数据一一对应
    │          publish.php 使用数组储存了已发布文章的信息(id、标题、标签等)与data文件夹内的文章数据一一对应

    └─theme
            index.php 主题文件,决定了页面的风格,将C传入的信息显示出来,相当于V
            style.css 主题使用的CSS风格

安装后访问,首页就只显示了三个功能 还包括一个首页

大体看了一下index.php,这里的Index.php就相当于C(控制路由),大体的逻辑
先是根据url后面?后的内容,判断$mc_get_type
再根据$mc_get_type 返回指定的内容

XSS1

登陆界面 这里面貌似有xss

mc-admin/index.php

<form action="<?php echo $_SERVER['REQUEST_URI']; ?>" method="post">

直接输出,有点像xss

试了半天没试出来 我的火狐把<给urlencode postman也不行

看别的师傅说 用IE能弹窗 但是我经过测试也不能弹窗

去看了一下 登陆的验证过程
一种是通过cookie验证,
还有一种是账号密码验证,通过后设置cookie

垂直越权

刚开始以为越权漏洞是 把 普通用户的账号当作admin的账号,后来发现,普通用户执行admin的操作也是越权

post页面有编辑 删除 恢复等操作 这就是了解文件结构的作用 根据文件的作用 就能找到常见漏洞
在Post.php的52行 有一处删除文件

unlink('../mc-files/posts/data/'.$id.'.dat');

回溯变量

if ($state != 'delete') {
    $index_file2 = '../mc-files/posts/index/delete.php';

    require $index_file2;

    $mc_posts[$id] = $post;

    file_put_contents($index_file2, "<?php\n\$mc_posts=".var_export($mc_posts, true)."\n?>");
  } else {
    unlink('../mc-files/posts/data/'.$id.'.dat');
  }

$state=delete就可以绕过if判断

$state变量在load_posts函数处被赋值

else if ($_GET['state'] == 'delete'){
      $state = 'delete';
      $index_file = '../mc-files/posts/index/delete.php';
    }

赋值之后,在就没有变化
再去看$id变量
$id变量是从ids种哪来的,ids是GET传过来的 可控

if (isset($_GET['delete']) || (isset($_GET['apply']) && $_GET['apply'] == 'delete')) {
  if (isset($_GET['apply']) && $_GET['apply'] == 'delete') {
    $ids = explode(',', $_GET['ids']);
    foreach ($ids as $id) {
      if (trim($id) == '')
        continue;
      delete_post($id);
      load_posts();
    }
  } else {
    delete_post($_GET['delete']);
  }

试一下 访问post.php 结果是302
很有可能是没登陆,所以重定向了,但是代码看到这里,没有发现有验证的地方,再往下看看
在188行处

<?php require 'head.php' ?>
<script type="text/javascript">
function check_all(name)
{
  var el  = document.getElementsByTagName('input');
  var len = el.length;

  for(var i=0; i<len; i++) {
    if((el[i].type=="checkbox") && (el[i].name==name)) {
      el[i].checked = true;
    }
  }
}

在head.php中

if (isset($_COOKIE['mc_token'])) {
  $token = $_COOKIE['mc_token'];

  if ($token != md5($mc_config['user_name'].'_'.$mc_config['user_pass'])) {
    Header("Location:index.php");
    exit;
  }
} else {
  Header("Location:index.php");
  exit;
}

这里对cookie进行了检查
因为我没登陆,所以就302了
但是不影响我们删文件,因为验证在删除的后边
测试

data目录下的文件已经没了

XSS2

xss大部分是在输入的地方产生的,根据功能来,不去看代码,找到可以输入的地方
统计一下
发布文章那里有三个 分别是 标题 内容 标签
设置 里面有评论代码

title

在管理页面 也就是post.php页面 同样会出现发布的内容,看一下
这里也被转义了

content

内容这里存在xss

弹窗

tag

也转义了 不能打

设置处

同样设置这里 变量输出时都进行了转义不能弹窗

去源码里看一下后台是怎样处理数据的
编辑页面在post-edit.php

这里开头就包含了head.php,不存在越权

下面对content进行了处理

if (isset($_POST['_IS_POST_BACK_'])) {
  $post_id          = $_POST['id'];
  $post_state       = $_POST['state'];
  $post_title       = trim($_POST['title']);
  $post_content     = get_magic_quotes_gpc() ? stripslashes(trim($_POST['content'])) : trim($_POST['content']);
  $post_tags        = explode(',', trim($_POST['tags']));
  $post_date        = date("Y-m-d");
  $post_time        = date("H:i:s");
  $post_can_comment  = $_POST['can_comment'];

get_magic_quotes_gpc 获得当前magic_quotes_gpc的配置选项设置
stripslashes函数 返回以剥离反斜杠\的字符串
在这里就等同于没处理 也没有过滤函数 对xss没防御措施

标题 在前端的输出哪里进行了一次转义操作 这是在草稿箱哪里进行的转义,但是在存储的过程中并没有进行转义操作

<input name="title" type="text" class="edit_textbox" placeholder="在此输入标题" value="<?php echo htmlspecialchars($page_title); ?>"/>

mc-admin/conf.php
同样这里的转义是在conf界面显示的时候进行的转义,存储时没有进行转义

<?php echo htmlspecialchars($comment_code); ?>

后来发现 数据在存储过程中经过了一步var_export() 这个函数会把单引号转义 但是并不会转义<>
所以我推测,这里的转义可能是浏览器转义的
评论代码这里只在设置界面这里会显示,其他地方不显示,也就没了输出点

既然可以输入内容,那么输入一点代码会是什么样,不要忘了这里的内容被存在数组里, 所以要先逃出数组

$mc_config = array (
  'version' => '1.10',
  'site_link' => 'http://127.0.0.1/minicms',
  'site_name' => '我的网站',
  'site_desc' => '又一个MiniCMS网站',
  'comment_code' => '<script>alert(1)</script>',
)

单引号闭合再加上注释符
不行,前面说了comment_code中的单引号被var_export转义了,
只能利用其他的位置
就用网站标题吧
最后发现从conf.php传过去的mc_config变量,都经过了var_export

后来看了已有的CVE 是使用了install.php 在安装时 通过设置网站标题为

实现RCE 一般网站在安装后都会删除install.php

xss3

mc-admin/post.php
这里有一个tag参数,最后的输出没有经过转义

if (isset($_GET['tag']))
  $filter_tag = $_GET['tag'];

288行有输出点

<a class="link_button" href="?state=<?php echo $state; ?>&date=<?php echo $filter_date;?>&tag=<?php echo $filter_tag; ?>">&laquo;</a>
    <a class="link_button" href="?state=<?php echo $state; ?>&date=<?php echo $filter_date;?>&tag=<?php echo $filter_tag; ?>&page=<?php echo $prev_page; ?>">&lsaquo;</a>

下面还有一个data参数,同样的道理,也存在xss
利用过程和之前的基本一样

xss总结

可能只在一个地方输入,但是会在多个地方显示,也就是说虽然只在编辑页面输入了xss语句,但是会在post.php 以及minicms/?post/4hctsa等多个页面显示
还有浏览器可能也会转义标签等xss
因为这个cms使用了stripslashes来处理输入数据,那么可以全局搜索一下stripslashes来快速找到,有哪些地方可以输入数据,也就找到了xss输入点

RCE

page-edit.php下面还有一处写入操作file_put_content
116行

$data['content'] = $post_content;

    file_put_contents($file_path, serialize($data));

上面还有一处文件包含109行

$index_file = '../mc-files/posts/index/'.$post_state.'.php';
    require $index_file;

回溯变量
$post_content = $_POST['content']
$post_state = $_POST['state']

都可控
在测试的时候出现了两个问题
content写上<?php phpinfo();?>

第一个还好说 content是存放在data目录下 这里的路径是index 可以用../跳转一下
第二个捣鼓了半天 还没弄好 就是写入的文件是有后缀的,dat 而这里自动加上了一个后缀.php 试过%00 # ; ./长度截断 都不行
最后把php的版本改为5.2 使用%00截断 实现了文件包含+RCE

一点收获

%00截断适用与php<5.3.29 并且GPC(magic_quotes_gpc)为off

总结

这个cms是一个搭建博客使用的,普通用户能够执行的东西比较少,像文件上传这种漏洞根本就不存在,主要是一个xss
这里xss还用到了一个在action参数里输出,虽然没复现出来


文章来源: http://xz.aliyun.com/t/6968
如有侵权请联系:admin#unsafe.sh