CMS代码审计之emlog6.0

149次阅读
没有评论

共计 5707 个字符,预计需要花费 15 分钟才能阅读完成。

一、前言

本小菜也是刚开始玩代码审计,最近发现个比较有趣的 CMS 跟大家分享一波,虽然只找到一些鸡肋漏洞。废话不多说开始进入正题,本次审计的 CMS 是 emlog 6.0.0 版本,官方地址为:http://www.emlog.net。

二、审计思路

在学习前辈们的文章后,自己稍微总结一下代码审计的思路主要可以分为以下四种。

1)查找可控变量,正向追踪变量传递过程,查看变量是否进行进行过滤,是否进行后台交互。

2)查找敏感函数,如 Select、Insert 等,回溯该函数的参数是否进行过滤、是否可控。

3)寻敏感功能点,通读功能点模块,如上传点。

4)直接通读全文代码,这种方式能够更好的理解代码的业务逻辑,能够挖掘出更有价值的漏洞

三、审计过程

1. Sql 注入

废话不多说,像本小菜这种级别的还是比较喜欢第一种思路,简单粗暴。

首先了解一下代码大概逻辑结构,然后用 notepad 大法搜索一下 $_GET 参数看看哪些输入没进行过滤,我们再进行切入。通过搜索发现 /admin/comment.php 中未对 $_GET[‘ip’]进行任何限制,接下来就查看 IP 这个参数是否进行了敏感操作

当 action 等于 delbyip 时,对 token 进行判定,查看是否是管理员,然后将 $_GET[‘ip’]赋值给 $ip,然后再调用 dleCommentByIp($ip)函数,代码如下:

if($action==delbyip) {   LoginAuth::checkToken();if(ROLE != ROLE_ADMIN) {       emMsg(权限不足!,./);
    }
    $ip =isset($_GET[ip]) ? $_GET[ip] :;
    $Comment_Model->delCommentByIp($ip);
    $CACHE->updateCache(array(sta,comment));
    emDirect("./comment.php?active_del=1");
}

接下来进行跟进 class Comment_Model 的 delCommentByIp 函数,继续使用搜索大法,在 /inludes/model/comment_model.php 中发现 delCommentByIp 函数,跟进 delCommentByIp 函数,可以看出这个函数直接将传入的参数 $ip, 代入 SELECT DISTINCT gid FROM .DB_PREFIX.comment WHERE ip=’$ip’ 进行 sql 查询,此过程中未发现任何过滤行为,我们只需通过封闭单引号然后进行报错注入。

functiondelCommentByIp($ip){       $blogids =array();
        $sql ="SELECT DISTINCT gid FROM".DB_PREFIX."comment WHERE ip=$ip";
        $query = $this->db->query($sql);while($row = $this->db->fetch_array($query)) {           $blogids[] = $row[gid];
        }
        $this->db->query("DELETE FROM".DB_PREFIX."comment WHERE ip=$ip");
        $this->updateCommentNum($blogids);
    }

接下来就进行漏洞验证了,访问 http://127.0.0.1/emlog/admin/comment.php,点击根据 ip 删除,用 burpsuite 抓取数据包,构造 payload

/emlog/admin/comment.php?action=delbyip&token=abac6e12c2abe9b29797b64481ef6ed4&ip=127.0.0.1′and (extractvalue(1,concat(0x7e,(select user()),0x7e)))  注入时得进行编码处理才能成功,当然各位师傅的骚操作比我多。

比较尴尬的是该注入点进行了权限校验,也就是说只有管理员才能注入,所以该漏洞十分的鸡肋,当然也只是分享一下自己的学习审计过程。

2.  文件上传漏洞

在 admin/plugin.php 插件上传处存在上传漏洞,通过上传 zip 压缩的文件,即可上传木马文件。在 plugin.php 约 79 行处的上传点当 action 为 upload_zip 进行判断,先判断是否上传文件是否为空..

if($action ==upload_zip) {   LoginAuth::checkToken();
    $zipfile =isset($_FILES[pluzip]) ? $_FILES[pluzip] :;if($zipfile[error] ==4) {       emDirect("./plugin.php?error_d=1");
    }if(!$zipfile || $zipfile[error] >=1||empty($zipfile[tmp_name])) {       emMsg(插件上传失败);
    }if(getFileSuffix($zipfile[name]) !=zip) {// 判断后缀名是否为 zipemDirect("./plugin.php?error_f=1");
    }

    $ret = emUnZip($zipfile[tmp_name],../content/plugins/,plugin);// 解压 zip 文件到../content/plugins/ 目录下switch($ret) {case0:
            emDirect("./plugin.php?activate_install=1tpllib");break;case-1:
            emDirect("./plugin.php?error_e=1");break;case1:case2:
            emDirect("./plugin.php?error_b=1");break;case3:
            emDirect("./plugin.php?error_c=1");break;
    }
}

然后通过 include\lib\function.base.php 中的 getFilesuSuffix 函数获取后缀名,并判断是否为 zip。

functiongetFileSuffix($fileName){returnstrtolower(pathinfo($fileName,  PATHINFO_EXTENSION));
}

之后通过 include\lib\function.base.php 中 emUnzip 函数进行解压 zip 文件,在解压的过程中会对压缩包里的文件名称进行判断,如果你压缩包名称为 test 则压缩包里必须存在 test.php 文件,否者会进行报错。

functionemUnZip($zipfile, $path, $type =tpl){if(!class_exists(ZipArchive,FALSE)) {return3;//zip 模块问题}
    $zip =newZipArchive();if(@$zip->open($zipfile) !==TRUE) {return2;// 文件权限问题}
    $r = explode(/, $zip->getNameIndex(0),2);
    $dir =isset($r[0]) ? $r[0] ./:;switch($type) {casetpl:
            $re = $zip->getFromName($dir .header.php);if(false=== $re)return-2;break;caseplugin:
            $plugin_name = substr($dir,0,-1);
            $re = $zip->getFromName($dir . $plugin_name ..php);// 判断是否存在与文件夹名称相同的 php 文件if(false=== $re)return-1;break;casebackup:
            $sql_name = substr($dir,0,-1);if(getFileSuffix($sql_name) !=sql)return-3;break;caseupdate:break;
    }if(true=== @$zip->extractTo($path)) {       $zip->close();return0;
    }else{return1;// 文件权限问题}
}

漏洞验证:

将一句话木马以 zip 格式进行压缩

然后在插件上传处上传插件

上传成功后使用菜刀连接 content/plugins/test1/test1.php,获取 webshell。

3. 数据库备份拿 shell

数据库拿 shell 主要有两种方式:

1、select …into outfile  利用需要的条件有:对 web 目录需要有写权限;能够使用单引号;知道绝对路径;没有配置 -secure-file-priv

2、通过 general_log,将日志写入特定目录下,利用条件又:对 web 目录需要要写权限;能够使用单引号;知道绝对路径;能够执行多行 sql 语句。

先尝试第一种方法,先备份 sql 语句,在其基础上插入 select into outfile‘eval.php’语句就 ok 了, 但是由于配置了 secure-file-priv, 所以 GG, 这个方法行不通。

使用第二种方法,设置了 general_loggeneral_log_file之后所有 SQL 记录都会写入指定的文件,所以可以通过这种方法将 php 语句写到 log 中。查询语句如下

setglobalgeneral_log=on;

SETglobalgeneral_log_file=C:/phpStudy/PHPTutorial/WWW/emlog/eval.php;

SELECT;

接下来操作如上导入备份文件即可,在 C:/phpStudy/PHPTutorial/WWW/emlog/ 目录下生成 eval.php 的 log 文件。

访问 http://127.0.0.1/emlog/eval.php,查看获取 shell。

4. 存储型 xss

后台链接添加处 admin/link.php 【http://localhost/admin/link.php】,siteurl 参数输出时未进行实体化处理导致,存储型 xss。在 $action=addlink 时,通过 addslashes 函数进行转义处理,防止进行 sql 注入,但是未进行任何的 html 实体化,或者过滤处理。通过第 44 行代码可发现进行了正则匹配所以我们构造的 payload 需以 http、ftp 开头,才能进型数据插入。

if($action==addlink) {   $taxis =isset($_POST[taxis]) ? intval(trim($_POST[taxis])) :0;
    $sitename =isset($_POST[sitename]) ? addslashes(trim($_POST[sitename])) :;
    $siteurl =isset($_POST[siteurl]) ? addslashes(trim($_POST[siteurl])) :;
    $description =isset($_POST[description]) ? addslashes(trim($_POST[description])) :;if($sitename ==|| $siteurl ==) {       emDirect("./link.php?error_a=1");
    }if(!preg_match("/^http|ftp.+$/i", $siteurl)) {//需以 httpftp 开头,$siteurl =http://.$siteurl;
    }
    $Link_Model->addLink($sitename, $siteurl, $description, $taxis);
    $CACHE->updateCache(link);
    emDirect("./link.php?active_add=1");
}

通过 addLink 函数插入链接,接下来跟进 /include/model/link_model.php 中的 link_Model->addLink 函数,该函数将经过 addslashes 转义完的链接插入数据库 进行存储。

functionaddLink($name,$url,$des,$taxis) {if($taxis> 30000 ||$taxis< 0) {$taxis= 0;
		}$sql="insert into".DB_PREFIX."link (sitename,siteurl,description,taxis) values($name,$url,$des,$taxis)";$this->db->query($sql);
	}

接下来跟进输出情况,当 action 参数为空,通过 /include/model/link_model.php 中的 Link_Model->getLinks()函数从数据库查询 sitename、description、siteurl,并返回查询结果,最后由 View::output()进行输出。getLinks()函数查询数据库获取,获取 sitename、description、siteurl 数据,查看可发现未对 siteurl 参数进行实体化和过滤处理。

查看 /include/lib/view.php 中的 View::output 函数,最后结果通过 echo 输出,在整个过程都未对 siteurl 进行实体化、过滤处理,导致最终造成存储型 xss。

漏洞验证

四、结束语

这个 cms 漏洞当然不止这些,还有很多问题。其实整个过程中审计的漏洞利用价值都不大,堪称鸡肋,本文只是分享自己学习的过程,由简单到复杂,分享一下自己的学习体会而已,望各位师傅多多包涵。

* 本文作者:fishyyh,转载请注明来自 FreeBuf.COM。


正文完
 0
评论(没有评论)