PbootCMS内容标题存在存储型XSS漏洞
影响版本:影响版本 V3.2.x(复现于 V3.2.21)
漏洞描述:
后台新增/编辑文章时,标题字段 title 经过 escape_string() 被 htmlspecialchars() 编码为 HTML 实体后存入数据库;但前台读取内容时调用 ->decode() → decode_string() 将实体重新解码回原始字符,并直接在模板渲染的 default 分支中输出(未经过 filter_html() 二次过滤),导致攻击者提交的
等 HTML/事件属性在详情页、列表页、首页以原始 HTML 形式渲染,形成存储型 XSS。攻击者只需具备「内容新增/修改」权限(普通编辑角色即可),即可注入持久化脚本,在访问该文章/列表/首页的所有用户浏览器中执行任意 JavaScript,可窃取 Cookie、会话、后台敏感数据或进行钓鱼。该 encode→decode→直接输出链为长期存在的设计,推测 V3.2.x 系列均受影响。
后台新增/编辑文章时,标题字段 title 经过 escape_string() 被 htmlspecialchars() 编码为 HTML 实体后存入数据库;但前台读取内容时调用 ->decode() → decode_string() 将实体重新解码回原始字符,随后在模板渲染的 default 分支中直接输出(未经过 filter_html() 二次过滤),导致攻击者提交的
等 HTML/事件属性在详情页、列表页、首页以原始 HTML 形式渲染,形成存储型 XSS。
攻击者只需具备「内容新增/修改」权限(普通编辑角色即可),即可注入持久化脚本,在访问该文章/列表/首页的所有用户浏览器中执行任意 JavaScript,可窃取 Cookie、会话、后台敏感数据或进行钓鱼。
代码链(master 当前一致)
apps/admin/controller/content/ContentController.php
L92 $title = post('title');
L169 'title' => $title,
post() → filter() → escape_string()
core/function/helper.php L456 return escape_string($data);
core/function/handle.php L443 $string = htmlspecialchars(trim($string), ENT_QUOTES, 'UTF-8');
② 数据库存储(实体形态)
→ <img src=x onerror=alert(1)>
③ 前台查询并解码
apps/home/model/ParserModel.php
L627-633 getContent() → parent::table(...)->decode()->find()
L543/555 getList() → ->decode()
core/basic/Model.php L188-189
if ($this->decode) $result = decode_string($result);
core/function/handle.php L636-637
$string = stripcslashes($string);
$string = htmlspecialchars_decode($string, ENT_QUOTES); // 实体被解码回原始 HTML
④ 模板渲染(title 直接输出,未走 filter_html)
apps/home/controller/ParserController.php
详情 default 分支 L4008-4015
列表 default 分支 L3752-3759
两处均执行:
$fieldVal = $data->$label; // title 未走 filter_html
str_replace($search, $this->adjustLabelData($params, $fieldVal), $content);
成因分析
encode→decode 设计不对称:入库用 escape_string() 编码,前台查询统一用 decode() 解码,等于把入库的 XSS 防护「还原」掉了。
输出侧缺少对普通字段的过滤:content(富文本)字段在 ParserController.php L3735-3739 / L3980-3983 显式调用了 filter_html()(会剥离 <script>、onerror、onload、onclick、javascript: 等),但 title、author、source 等普通字段走 default 分支,没有任何过滤,直接拼接进模板输出。
历史加固不完整/回归:V3.2.18 发布说明已提到「加固内容、栏目、站点标题与描述的输入清洗」,但当前 master 仍存在上述 encode→decode→直接输出链,说明该加固在标题字段上被 decode 环节绕过,可作为加固不完整/回归的证据。
漏洞影响版本
复现版本:PbootCMS V3.2.21(构建号 20260811)
该 encode→decode→直接输出链为长期存在的设计,推测 V3.2.x 系列均受影响;建议项目方核实具体受影响版本范围。
漏洞等级
中危(Medium)——需低权限(内容编辑角色)+ 受害者浏览,但可持久化窃取会话、后台越权。
CVSS 向量
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N
PbootCMS内容标题存在存储型XSS漏洞
影响版本:影响版本 V3.2.x(复现于 V3.2.21)
漏洞描述:
等 HTML/事件属性在详情页、列表页、首页以原始 HTML 形式渲染,形成存储型 XSS。攻击者只需具备「内容新增/修改」权限(普通编辑角色即可),即可注入持久化脚本,在访问该文章/列表/首页的所有用户浏览器中执行任意 JavaScript,可窃取 Cookie、会话、后台敏感数据或进行钓鱼。该 encode→decode→直接输出链为长期存在的设计,推测 V3.2.x 系列均受影响。
后台新增/编辑文章时,标题字段 title 经过 escape_string() 被 htmlspecialchars() 编码为 HTML 实体后存入数据库;但前台读取内容时调用 ->decode() → decode_string() 将实体重新解码回原始字符,并直接在模板渲染的 default 分支中输出(未经过 filter_html() 二次过滤),导致攻击者提交的
后台新增/编辑文章时,标题字段 title 经过 escape_string() 被 htmlspecialchars() 编码为 HTML 实体后存入数据库;但前台读取内容时调用 ->decode() → decode_string() 将实体重新解码回原始字符,随后在模板渲染的 default 分支中直接输出(未经过 filter_html() 二次过滤),导致攻击者提交的
等 HTML/事件属性在详情页、列表页、首页以原始 HTML 形式渲染,形成存储型 XSS。
攻击者只需具备「内容新增/修改」权限(普通编辑角色即可),即可注入持久化脚本,在访问该文章/列表/首页的所有用户浏览器中执行任意 JavaScript,可窃取 Cookie、会话、后台敏感数据或进行钓鱼。
代码链(master 当前一致)
apps/admin/controller/content/ContentController.php
L92 $title = post('title');
L169 'title' => $title,
post() → filter() → escape_string()
core/function/helper.php L456 return escape_string($data);
core/function/handle.php L443 $string = htmlspecialchars(trim($string), ENT_QUOTES, 'UTF-8');
② 数据库存储(实体形态)
→ <img src=x onerror=alert(1)>
③ 前台查询并解码
apps/home/model/ParserModel.php
L627-633 getContent() → parent::table(...)->decode()->find()
L543/555 getList() → ->decode()
core/basic/Model.php L188-189
if ($this->decode) $result = decode_string($result);
core/function/handle.php L636-637
$string = stripcslashes($string);
$string = htmlspecialchars_decode($string, ENT_QUOTES); // 实体被解码回原始 HTML
④ 模板渲染(title 直接输出,未走 filter_html)
apps/home/controller/ParserController.php
详情 default 分支 L4008-4015
列表 default 分支 L3752-3759
两处均执行:
$fieldVal = $data->$label; // title 未走 filter_html
str_replace($search, $this->adjustLabelData($params, $fieldVal), $content);
成因分析
encode→decode 设计不对称:入库用 escape_string() 编码,前台查询统一用 decode() 解码,等于把入库的 XSS 防护「还原」掉了。
输出侧缺少对普通字段的过滤:content(富文本)字段在 ParserController.php L3735-3739 / L3980-3983 显式调用了 filter_html()(会剥离 <script>、onerror、onload、onclick、javascript: 等),但 title、author、source 等普通字段走 default 分支,没有任何过滤,直接拼接进模板输出。
历史加固不完整/回归:V3.2.18 发布说明已提到「加固内容、栏目、站点标题与描述的输入清洗」,但当前 master 仍存在上述 encode→decode→直接输出链,说明该加固在标题字段上被 decode 环节绕过,可作为加固不完整/回归的证据。
漏洞影响版本
复现版本:PbootCMS V3.2.21(构建号 20260811)
该 encode→decode→直接输出链为长期存在的设计,推测 V3.2.x 系列均受影响;建议项目方核实具体受影响版本范围。
漏洞等级
中危(Medium)——需低权限(内容编辑角色)+ 受害者浏览,但可持久化窃取会话、后台越权。
CVSS 向量
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N