Emlog 6.0 beta版本,这可能是最后一篇关于PHP语言CMS的代码审计文章,此次将详细记录完整的审计过程。

文章基本上完整记录小东的对此CMS审计过程,或许显得繁琐,但代码审计的过程就是这样,发现可能项,然后精心构造去验证,这过程中我们会遇到很多次碰壁,坚持测试,思维活跃一些,基本都会有所收获,诚挚希望后来者能够耐心阅读下去,当然最好也能够有所启发。

大家需要注意的一点是,代码审计是为了学习并在SDL中避免发生类似的错误,同时也是帮助开源系统修复相关问题,并不是去为了获得什么0day~

0×00 Emlog 6.0 beta

官网地址:https://www.emlog.net/

Emlog 6.0 beta下载地址:https://www.emlog.net/download

由于官方限制论坛会员(注册付费)才可下载,这里提供一个原版下载地址:https://www.lanzous.com/i1l5gad

文件校验:

文件: C:\Users\stdy\Desktop\emlog_6.0.0.zip 大小: 607725 字节 修改时间: 2018年8月6日, 20:53:50 MD5: 7844FE6FEAE7AF68052DC878B8811FAC SHA1: E06A050D2A0AA879DB9F5CFCAA4703B6AC7B8352 CRC32: 4963E489 

博主的博客就是基于此套博客系统,其实很多圈内大佬都在使用,对于本款CMS的审计文章却并没有,小东就来以此CMS作为PHP代码审计的封笔之作。

0×01 初步测试

首先,我们得先安装!安装成功后的首页界面:

默认后台登陆地址:./admin/

登陆成功后:

闲话一句,感觉6.05.3.1版本好看太多了~

安装过后,我们应该尽可能全面搜集关于此CMS的信息,这对于我们审计代码有很大的帮助。

所以,分析得到此CMS的大致结构,Emlog是一个 MVC 的设计模式,大致的结构如图:

因此我们主要会分析 admin 和 include 文件夹下的文件。

数据库表:

在根目录的init.php 文件中

报错等级指定为7

<?php
//禁用错误报告
error_reporting(0);//报告运行时错误 error_reporting(E_ERROR | E_WARNING | E_PARSE); //报告所有错误 error_reporting(E_ALL); error_reporting(7); /* 设置php错误检测级别 E_ERROR - 致命性运行时错 (1) E_WARNING - 运行时警告(非致命性错)(2) E_PARSE - 编译时解析错误 (4) 1+2+4 = 7 */ ?>

0×02 使用漏洞扫描器

可能有朋友就会说你为什么要使用“漏扫”呐?不是代码审计吗?

这里要纠正一下这个观点,漏扫其实就是一个自动化黑盒测试,在本地环境下,我们不会影响任何的业务。

通过漏扫出的漏洞能够方便我们快速定位漏洞位置,这样是一种高效的方式,这也是在团队里的成员通过漏扫Get了百度的几个高危漏洞给小东的启示。

这里使用了一款重型扫描器 AWVS ,得到的报告如下:

不过在本地扫描时,使用的是 XAMPP windows10 PHP5.6的环境,所以导致漏洞报告中很多误报,漏扫主要扫描出了几个XSS漏洞CSRF漏洞

所以我们首先验证这两类的漏洞

0×03 文章编辑器储存性XSS

在后台的编辑器处,编辑文章./admin/admin_log.php

成功发布后,来到首页

进入文章页后

都弹窗了,这里大家可能要说没法儿利用,但是emlog设计了 会员/作者 功能,在emlog中的某些模版中可以前台注册会员,会员登录后可以编辑发表文章,评论等等功能。Emlog官方还提供了文章投稿插件,都是调用了官方默认的Kindeditor编辑器,这个编辑器自带HTML编辑模式,就算不带这个模式,攻击者也可以抓包修改达到攻击目的。

为什么前台没过滤呐?为了文章有支持HTML代码输出,所以对于kindeditor的保存输出内容并没有转义。

修复建议:参考其他CMS做好文章内容关键词的检测,并做好过滤或者转义

0×04 Uploadify SWF XSS

Emlog使用了 uploadify.swf 的方式上传文件,文件路径 /include/lib/js/uploadify/uploadify.swf

构造Payload:http://www.test.com//include/lib/js/uploadify/uploadify.swf?uploadifyID=00%22%29%29;}catch%28e%29{alert%281%29;}//%28%22&movieName=%22])}catch(e){if(!window.x){window.x=1;alert(document.cookie)}}//&.swf

效果,可无视浏览器filter

0×05 反射型XSS

此处的XSS主要发生在cookie上,因为某些页面如 admin/admin_log,admin/sort.php,admin/link.php页面需要在表单中添加了hidden属性的token值,而这个token值直接从用户的cookie中取得,导致了一个反射型XSS

拦截抓包修改cookie中的token值如下:

效果:

其次验证了 CSRF 漏洞,这个是前台的搜索框的CSRF根本没什么价值

然后是管理员添加友情链接的XSS,经过验证并不存在,后台函数会限制字数


然后就是我们开始进行原始的代码审计工作了,主要借用了Seay代码审计工具Rips,这种审计工具主要依靠正则匹配可能导致危险的php函数来作为可能存在漏洞的判断,半自动化的方式,在一定程度上缓解了代码审计的压力。

0×06 基本函数

首先看了一下文件操作相关的函数,发现经常用到 View::getView 这一方法,

include/lib/view.php 文件中,源码如下:

<?php
/*** 视图控制* @copyright (c) Emlog All Rights Reserved*/class View { public static function getView($template, $ext = '.php') { if (!is_dir(TEMPLATE_PATH)) { emMsg('当前使用的模板已被删除或损坏,请登录后台更换其他模板。', BLOG_URL . 'admin/template.php'); } return TEMPLATE_PATH . $template . $ext; } public static function output() { $content = ob_get_clean(); ob_start(); echo $content; ob_end_flush(); exit; } } 

同时作为权限控制的 LoginAuth::checkToken(),在 \include\lib\loginauth.php下约209行开始

/**
* 生成token,防御CSRF攻击
*/
public static function genToken() { $token_cookie_name = 'EM_TOKENCOOKIE_' . md5(substr(AUTH_KEY, 16, 32) . UID); if (isset($_COOKIE[$token_cookie_name])) { return $_COOKIE[$token_cookie_name]; } else { $token = md5(getRandStr(16)); setcookie($token_cookie_name, $token, 0, '/'); return $token; } } /** * 检查token,防御CSRF攻击 */ public static function checkToken(){ $token = isset($_REQUEST['token']) ? addslashes($_REQUEST['token']) : ''; if ($token != self::genToken()) { emMsg('权限不足,token error'); } } 

验证了Rips扫描出的文件包含问题(第一次使用Rips),发现无法复现,因为Rips扫描的时候是以文件形式,并没有参照程序的严格逻辑,导致的误报!

来到 \admin\admin_log.php 文件,从第78行开始:

//操作文章
if ($action == 'operate_log') {$operate = isset($_REQUEST['operate']) ? $_REQUEST['operate'] : ''; $pid = isset($_POST['pid']) ? $_POST['pid'] : ''; $logs = isset($_POST['blog']) ? array_map('intval', $_POST['blog']) : array(); $sort = isset($_POST['sort']) ? intval($_POST['sort']) : ''; $author = isset($_POST['author']) ? intval($_POST['author']) : ''; $gid = isset($_GET['gid']) ? intval($_GET['gid']) : ''; LoginAuth::checkToken(); if ($operate == '') { emDirect("./admin_log.php?pid=$pid&error_b=1"); } if (empty($logs) && empty($gid)) { emDirect("./admin_log.php?pid=$pid&error_a=1"); } switch ($operate) { case 'del': foreach ($logs as $val) { doAction('before_del_log', $val); $Log_Model->deleteLog($val); doAction('del_log', $val); } $CACHE->updateCache(); if ($pid == 'draft') { emDirect("./admin_log.php?pid=draft&active_del=1"); } else{ emDirect("./admin_log.php?active_del=1"); } break; case 'top': foreach ($logs as $val) { $Log_Model->updateLog(array('top'=>'y'), $val); } emDirect("./admin_log.php?active_up=1"); break; case 'sortop': foreach ($logs as $val) { $Log_Model->updateLog(array('sortop'=>'y'), $val); } emDirect("./admin_log.php?active_up=1"); break; case 'notop': foreach ($logs as $val) { $Log_Model->updateLog(array('top'=>'n', 'sortop'=>'n'), $val); } emDirect("./admin_log.php?active_down=1"); break; case 'hide': foreach ($logs as $val) { $Log_Model->hideSwitch($val, 'y'); } $CACHE->updateCache(); emDirect("./admin_log.php?active_hide=1"); break; ...//中间的代码要验证管理身份,故省略 case 'uncheck': if (ROLE != ROLE_ADMIN) { emMsg('权限不足!','./'); } $Log_Model->checkSwitch($gid, 'n'); $CACHE->updateCache(); emDirect("./admin_log.php?active_unck=1"); break; } } 

那么我们尝试越权删除文章 http://www.test.com/admin/admin_log.php?action=operate_log&operate=del&blog=29&token=994132a26661c8c244a91063c4701a7e 失败了提示权限不足,来到\include\model\log_model.php 发现

/*** 删除文章** @param int $blogId*/
function deleteLog($blogId) { $author = ROLE == ROLE_ADMIN ? '' : 'and author=' . UID; $this->db->query("DELETE FROM " . DB_PREFIX . "blog where gid=$blogId $author"); //这里和上一句限制了作者只能删除自己的文章 if ($this->db->affected_rows() < 1) { emMsg('权限不足!', './'); } // 评论 $this->db->query("DELETE FROM " . DB_PREFIX . "comment where gid=$blogId"); // 标签 $this->db->query("UPDATE " . DB_PREFIX . "tag SET gid= REPLACE(gid,',$blogId,',',') WHERE gid LIKE '%" . $blogId . "%' "); $this->db->query("DELETE FROM " . DB_PREFIX . "tag WHERE gid=',' "); // 附件 $query = $this->db->query("select filepath from " . DB_PREFIX . "attachment where blogid=$blogId "); while ($attach = $this->db->fetch_array($query)) { if (file_exists($attach['filepath'])) { $fpath = str_replace('thum-', '', $attach['filepath']); if ($fpath != $attach['filepath']) { @unlink($fpath); } @unlink($attach['filepath']); } } $this->db->query("DELETE FROM " . DB_PREFIX . "attachment where blogid=$blogId"); } 

这个越权漏洞不存在,同时看了下面的函数判断也是做了类似的处理

到这里其实我们对于整个 CMS 的架构已经较为熟悉了,基本能根据对应函数功能,直接手动找到对应的函数位置。

令人伤心的是,通过 Rips 代码审计工具得到的结果,一个都没复现成功…

###0×07 Seay辅助审计

相信很多人都知道法师的这款工具,主要还是因为中文,用着方便,但是完全依靠正则的方式去匹配函数,只能发现那些函数直接的控制漏洞,逻辑漏洞有时候可以根据逆推可以发现,但这种情况很少。

使用这款工具扫描出来共120个可能的情况(根据经验98%以上都是没法复现的),然后一个个排查,有的例如SQL语句反单引号这样的,很容易就可以判断给忽律,就不需要考虑。

在 /admin/store.php 看到这样一串代码:

这里我的思考是,如果在emlog官网有URL跳转链接的话,那么就可以构造下载远程任意的文件到网站,但是测试了官网没有跳转链接,那么我们尝试下载别的插件(链接跳转等),或者有黑客精心构造了一个插件或者模版,然后再利用,这也算是一个可行的方案。

此处需要管理员权限,作为代码审计的一个参考思路,不是要发现什么0day,而是希望大家能够在代码审计方面有所收获。

(1). SQL注入

对于SQL注入Seay工具一直都没准过,这里小东推荐方式,使用全局搜索 $_GET[ 或 $_PSOT[,然后看看是否代入了SQL查询,然后一一验证。

然后我发现了这样一个没有过滤IP参数

然后到 admin/comment.php 中查看

再看 delCommentByIp($ip) 函数

由此我们可以确定了SQL注入的存在

验证如下:

(2).一个CSRF+任意文件删除

$_GET[]型分析完以后,就寻找$_POST[]的,然后在admin/data.php文件中找到了如下代码

这里我们发现,并没有验证toknen,那么可以构造csrf页面,这里小东就不演示了,直接BURP验证一下任意文件删除吧,关于CSRF,只要没有调用上面基础函数部分说到的 LoginAuth::checkToken() 方法的,都存在CSRF

这里就成功删除了文件

(3).TAG SQL注入

在POST参数中发现此处并没有过滤,同时在 deleteTag() 函数中,代入了SQL查询,因此又是一个SQL注入

但是此处并没有回显。可以采用时间盲注的方式

至此,利用工具的半自动化审计已经结束,下面准备手工测试

0×08 手工测试

手工测试也不是单纯的翻文件,应当以灰盒测试为主导,从逻辑权限敏感信息等方面入手

(1).后台登陆存在暴力破解风险

在这里,我之前提到过的验证码未及时销毁的历史问题还存在,此处不再详细叙述,请参考https://blog.csdn.net/dyboy2017/article/details/78433748

(2).报错信息导致物理路径泄漏

大家不要以为这是小事情,当sql注入存在的时候,我们有机会是可以直接写shell文件,安全无小事

一个低权限的方式,在游客的条件下测试一下

payload:http://www.test.com/admin/attachment.php?action[]=

原因是:addslashes() expects parameter 1

(3).Cookie可计算

include/lib/loginauth.php134行开始

/*** 写用于登录验证cookie** @param int $user_id User ID* @param bool $remember Whether to remember the user or not*/
public static function setAuthCookie($user_login, $ispersis = false) { if ($ispersis) { $expiration = time() + 3600 * 24 * 30 * 12; } else { $expiration = null; } $auth_cookie_name = AUTH_COOKIE_NAME; $auth_cookie = self::generateAuthCookie($user_login, $expiration); setcookie($auth_cookie_name, $auth_cookie, $expiration,'/'); } /** * 生成登录验证cookie * * @param int $user_id user login * @param int $expiration Cookie expiration in seconds * @return string Authentication cookie contents */ private static function generateAuthCookie($user_login, $expiration) { $key = self::emHash($user_login . '|' . $expiration); $hash = hash_hmac('md5', $user_login . '|' . $expiration, $key); $cookie = $user_login . '|' . $expiration . '|' . $hash; return $cookie; } 

可以看到此处的cookie都可以直接计算得到,只需要知道根目录下config.php中的

//auth key
define('AUTH_KEY','dx1&CH^En86GZnxd9CLO7GwC0Q5eYHKM450f598bbd148b6a62f7d263623e31c3');
//cookie name
define('AUTH_COOKIE_NAME','EM_AUTHCOOKIE_VzfVniPWDqd1LM3BFocnrcjpAGH4lUbz'); 

即可。

(4).侧边栏存储性XSS

为了同样是为了支持HTML代码的输出,没有转义对应的脚本代码标签,导致了存储性的XSS存在

0×09 Getshell

(1).SQL注入拿到shell

如上所讲有SQL注入的存在,同时可以获取到物理路径,那么就可以直接写Shell

(2).后台插件上传zip

因为后台可以直接上传本地zip文件,这里我们去官网下载一个插件,同时把我们的shell文件(比如dyboy.php)加入zip,上传安装这个插件就可以了,然后shell地址为:http://www.test.com/content/plugins/插件名/dyboy.php

(3).后台模版上传zip

和插件同样的原理,这里的shell地址为:http://www.test.com/content/templates/模版名/dyboy.php

(4).备份文件拿shell

后台的数据功能处,先备份一个,然后下载到本地,加入SELECT "<?php @assert($_POST['dyboy'])?>" into outfile 'D:\\Server\\htdocs\\safe\\dyboy.php';

然后导入备份恢复本地数据即可

这样就在网站个目录生成了一个dyboy.phpshell

0×10 总结

EMLOG是一个非常小巧轻快的博客系统,运行占用资源非常低,所以非常适合博主用作博客用途,其实只要不开启会员功能,没有弱口令就没有什么大的威胁。以此文章作为PHP代码审计的终稿,文章所述方法同样适用于其他的CMS代码审计和分析,创作不易,也希望本文章能对大家能有所启示。

转载于:https://www.cnblogs.com/h2zZhou/p/9466373.html

对Emlog 6.0 Beta的完整代码审计过程相关推荐

  1. 使用Mongoose populate实现多表关联存储与查询,内附完整代码

    文章目录 使用Mongoose populate实现多表关联与查询 一. 数据模型创建 1. 创建一个PersonSchema 2. 创建一个StorySchema 3. 使用Schema创建对应的m ...

  2. 吴恩达机器学习python实现(6):SVM支持向量机(文末附完整代码)

    所有的数据来源:链接:https://pan.baidu.com/s/1vTaw1n77xPPfKk23KEKARA 提取码:5gl2 1 Support Vector Machines 1.1 Pr ...

  3. 用python爬荣耀皮肤图片(爬虫最基础题,python爬虫教程,超详细+完整代码)

    文章目录 前言 一.思路分析 网站分析 代码思路 二.环境配置 三.完整代码 四.总结 前言 今天,我们用python语言来实现爬荣耀所有英雄的皮肤图片,下载并保存到本地,文章后面有完整代码. 一.思 ...

  4. PCL提取3D点云模型特征(3.0 FPFH快速点特征直方图)附完整代码

    一.概述 上一篇博客解释了PFH是什么以及如何利用PFH来提取点云的特征,那么讲了PFH(PCL提取3D点云模型特征(2.0 PFH点特征直方图 )附完整代码)之后肯定是要接着说FPFH的.本来想着把 ...

  5. 代码资源网整站完整代码,基于ripro9.0定制开发,含572条精品资源数据

    许多网友都想搭建一个资源网站,有些网友购买网站vip会员,然后再每天搬运代码资源再整理上传到自己的网站上去,实在太麻烦太辛苦. 鉴于此种需求,本人决定把本站到目前为止的数据整体打包,分享给大家,让大家 ...

  6. Python 拼图成心2.0【重新梳理】【附完整代码】

    最新版本,点这里 之前一时兴起,写了个生成心形贴图的程序[链接在这里]但是只是把功能实现了,有很多鸡肋还有逻辑之类混乱的,于是又重新整理了一下. 需要用到的库 import os import os. ...

  7. 赛桨PaddleScience v1.0 Beta:基于飞桨核心框架的科学计算通用求解器

    近年来,关于AI for Science的主题被广泛讨论,重点领域包含使用AI方法加速设计并发现新材料,助力高能物理及天文领域的新问题探索,以及加速智慧工业实时设备数据与模型的"数字孪生&q ...

  8. 黑莓管理器6.0_BlackBerry Java SDK 7.0 Beta发布

    黑莓管理器6.0 BlackBerry Java SDK 7.0 Beta发布 BlackBerry Java SDK 7.0版的测试版现已发布. 该SDK包括用于BlackBerry Bold 99 ...

  9. 【控制系统数字仿真与CAD——实验报告】实验三:离散相似法数字仿真(文末附完整代码 + 实验结果)

    一.实验目的 1. 了解离散相似法的基本原理 2. 掌握离散相似法仿真的基本过程 3. 应用离散相似法仿真非线性系统 4. MATLAB实现离散相似法的非线性系统仿真 5. 掌握SIMULINK仿真方 ...

最新文章

  1. GAN 为什么需要如此多的噪声?
  2. 关于Javacript“原型”和“prototype属性”两者需要澄清的几点
  3. Broadcom NetXtrem II网卡Linux安装指南(转载)
  4. 判断一个变量是不是指针
  5. UML类图与类的关系详解【转】
  6. 《每日一题》48. Rotate Image 旋转图像
  7. [ ArcGIS for Server 10.1 系列 ] - 分布式部署GIS Servers
  8. HALCON示例程序hull.hdev区域提取与凸度筛选
  9. 蓝桥杯java 基础练习 十六进制转十进制
  10. iOS小白之路...iOS中基础控件的使用
  11. 2019-07-18
  12. VC6LineNumber完美破解版
  13. JS获取本机IP地址的方法
  14. 儿童手表语音卡安全吗?
  15. excel两列数据对比找不同_比Vlookup好用10倍,它才是Excel函数中的No.1
  16. wordpress主题模板开发制作教程
  17. antd checkbox 默认选中_antd的CheckBox
  18. 让游戏通过红蓝立体眼镜展现立体效果
  19. java 火车票查询系统_JavaWeb火车票订票系统
  20. 公司股权分配的七大简明实操建议

热门文章

  1. c语言固定长度的字符串,【分享】C语言动态长度字符串
  2. c++ java通信 protocol buffer,google protocol buffer (C++,Java序列化应用实例)
  3. idea @Autowired 注入爆红(无法注入)
  4. 抽象类必须要有抽象方法吗?
  5. 后端技术:MyBatis 批量插入的 3 种写法
  6. 硬件知识:串口通讯的起始、数据、停止位是怎么分配的?
  7. Linux常用的25条命令
  8. c# 正则表达式 html标签,C#匹配HTML标签,正则表达式谁会?
  9. cookie,session的区别和联系(补充token)
  10. 产品原型制作_早期制作原型如何帮助您设计出色的数字产品