PHP代码审计

本文假定读者是会编程的

PHPCMS V9.2.0

看看源代码文件,是个MVC的设计架构

漏洞复现

安装

这里随便点一个东西,发现URL是这样的格式:http://localhost:81/phpcms/index.php?m=vote&c=index&siteid=1,一般这样的SQL都很少,先不看SQL注入的
先注册个用户

点一圈之后试试上传,这里上传了个图片作为头像


查看一下上传头像的数据包,能看见调用的URL和发送的数据包

百度谷歌后得知文件头是PK的文件是压缩包,把整个数据复制为文件后打开发现里面有四个图片文件

那就试一试在上传的时候把它给整个替换了,替换成带有一句话木马的zip文件,这里先做好一个zip,里里面包含一个shell.php和一个包含shell.php的shell目录

再次上传,抓包改包,清空原来的数据,从文件粘贴含有一句话木马的压缩包的数据



然后就得到四个地址,每个头像都是一个地址,这是其中之一

根据地址可以看到一个有一个头像,根据刚才的上传构造,在和图片缩略图同目录下应该还有一个shell.php,和一个包含另一个shell.php的shell目录,这里构造路径进行访问:可以发现同目录下并不存在shell.php,但是存shell目录而且目录下的shell.php是存在的

漏洞分析

先分析上传的时候调用的URL,从上面可以得知URL为:/phpsso_server/index.php?m=phpsso&c=index&a=uplaodavatar&auth_data=v=1&appid=1&data=......,通过POST方法发送数据。
猜测m是model,c是class,a是action的缩写,先找到这个/phpsso_server/index.php文件,跟踪看是个什么样的调用过程

文件调用过程追踪

以下文件都位于/phpsso_server/目录下

./index.php

就这么一行代码,不用分析,跟进到base.php

define('PHPCMS_PATH', dirname(__FILE__).'/');
include PHPCMS_PATH.'/phpcms/base.php';

pc_base::creat_app();

./phpcms/base.php


跟踪过程可以发现首先判断一个名为application的类的文件存不存在,如果存在就包含这个文件然后初始化这个类,最后返回这个类的指针或者false,这个类的文件的路径是PC_PATH.$path.DIRECTORY_SEPARATOR.$classname.'.class.php' => [WEBROOT]/phpsso_server/phpcms/libs/classes/application.class.php

./phpcms/libs/classes/application.class.php


这里可以看到在类application构造的时候加载类param并且调用到其中的三个类方法,并且在自身初始化的时候会加载一个控制器的文件,并且初始化这个控制器最后返回这个控制器的指针到初始化函数中的一个变量,再判断调用的方法在这个控制器中存不存在,如果存在则调用这个方法

./phpcms/libs/classes/param.class.php


进来到这个文件之后,直接找到在application.php调用的三个函数,首先就是控制器的函数,根据URL:/phpsso_server/index.php?m=phpsso&c=index&a=uplaodavatar&auth_data=v=1&appid=1&data=......和这里的解析逻辑,上传的时候调用的控制器的名字来自于GET参数cm,根据文件路径$filepath = PC_PATH.'modules'.DIRECTORY_SEPARATOR.$m.DIRECTORY_SEPARATOR.$filename.'.php';可得:$filepath = [WEBROOT]/phpsso_server/phpcms/modules/phpsso/index.php;
调用的方法名来自于GET参数a,根据文件路径,这里调用的方法是uplaodavatar

./phpcms/modules/phpsso/index.php


找到uplaodavatar方法,审查代码后发现在解压文件后有一个删除其它文件的操作,但是它并没有删除解压出来的其它目录,所以这里就可以构造一个含有目录且目录下有webshell的一个压缩包来getshell

$avatararr = array('180x180.jpg', '30x30.jpg', '45x45.jpg', '90x90.jpg');
if($handle = opendir($dir)) {
    while(false !== ($file = readdir($handle))) {
        if($file !== '.' && $file !== '..') {
            if(!in_array($file, $avatararr)) {
                @unlink($dir.$file); //这里没有删除价压出来的目录,漏洞核心
            } else {
                $info = @getimagesize($dir.$file);
                if(!$info || $info[2] !=2) {
                    @unlink($dir.$file);
                }
            }
        }
    }
    closedir($handle);    
}

PHPCMS V9.6.0

不啰嗦,还是安装那一套步骤。先不看具体有什么漏洞,尝试一步一步来审计
思路:根据CMS的功能,从普通用户可以接触到的功能和逻辑入手:用户注册,用户上传文件(头像,附件等),密码找回,越权访问,etc。

用户注册处的跟踪

按照上一个版本的跟踪技巧,根据URL找到对应的文件和函数进行分析,这里用户注册的URL:/index.php?m=member&c=index$a=register&siteid=1
文件跟踪路径:/index.php => /phpcms/base.php => /phpcms/libs/classes/application.class.php => /phpcms/modules/member/index.php::register()

这个register函数有200+行,一块块地来分析
在接受POST参数这一块,发现了一个比较可疑的地方:$userinfo['modelid'] = isset($_POST['modelid']) ? intval($_POST['modelid']) : 10;,什么地方需要规定一个模型❓结合上下代码,发现这一块代码比较可疑,好像是通过这个id来调用某个模型或者模块

if($member_setting['choosemodel']) {
    require_once CACHE_MODEL_PATH.'member_input.class.php';
    require_once CACHE_MODEL_PATH.'member_update.class.php';
    $member_input = new member_input($userinfo['modelid']);
    $_POST['info'] = array_map('new_html_special_chars',$_POST['info']);
    $user_model_info = $member_input->get($_POST['info']);
}

根据代码跟进到这个member_input.class.php文件看看最后调用的get函数都有什么,这个文件位于/phpcms/caches/caches_model/caches_data目录下(这个caches目录下的文件需要安装之后才会出现,安装包里原本没有的,这个博主当时还觉得很奇怪怎么找么安装包里找了半天目录都是空的,以下代码是安装包的里面的代码)

根据代码和跟踪,这里的modelid的确来自于传入的modelid,也就是调用的模块或者模型是可控的,然后来到下面这里这几行代码有有点可疑了:通过modelid获取模型或者模块,再从中调用相应的函数,这不就是动态加载函数吗❓也就是控制了模型在一定程度上也控制了可以加载的函数

if($this->fields[$field]['isunique'] && $this->db->get_one(array($field=>$value),$field) && ROUTE_A != 'edit') showmessage("$name 的值不得重复!");
$func = $this->fields[$field]['formtype'];
if(method_exists($this, $func)) $value = $this->$func($field, $value);

根据这一块以上的代码,能看到有个字段的提示,来到数据库翻一翻(根据关键字model,modelid,field,formtype)

这里可以看见,如果modelid为空就默认是10,而10对应的formtype是datetime,而datetime这个值在上面的代码是作为函数的(这是可控的关键),再来查看其它的formtype,可以看到各种函数名。

结合当前的类还有类方法,这里有个editor函数,对应的modelid是11,field是content,它有个下载调用$this->attachments->download(),这个下载功能跟进一下(文件路径:/phpcms/libs/classes/attachment.class.php)