EP099. “去除 Private 前缀、权限收紧与 XSS 防护”
🔒 登录后可标记已读三件收尾工作:① 笔记设成 private 后,WordPress 默认会在标题前自动加一个「Private: 」前缀,这只是显示层面的行为(数据库里标题本身没有这几个字),用 str_replace() 在输出时把它去掉;② 笔记状态既然一定是 private,Subscriber 就不再需要「编辑/删除已发布内容」这两个权限,进一步收紧到最小权限集合;③ 用真实的 XSS(跨站脚本)攻击场景演示为什么 HTML 转义函数(esc_attr/esc_html/esc_textarea)如此重要,并且在服务端追加一层无论如何都会执行的兜底净化(sanitize_text_field/sanitize_textarea_field),彻底不允许 Subscriber 通过笔记内容注入任何 HTML/JS。
涉及文件
wp-content/themes/fictional-university-theme/page-my-notes.php(修改)- 后台 Members → Roles → Subscriber:取消勾选
edit_published_notes、delete_published_notes(后台操作,无对应代码文件) wp-content/themes/fictional-university-theme/functions.php(修改)
代码实现
page-my-notes.php:去掉标题里的「Private: 」前缀:
<input readonly class="note-title-field" value="<?php echo str_replace('Private: ', '', esc_attr(get_the_title())); ?>">
functions.php:makeNotePrivate() 追加服务端强制净化标题和正文:
// wp-content/themes/fictional-university-theme/functions.php
function makeNotePrivate($data) {
if ($data['post_type'] == 'note') {
$data['post_content'] = sanitize_textarea_field($data['post_content']);
$data['post_title'] = sanitize_text_field($data['post_title']);
}
if($data['post_type'] == 'note' AND $data['post_status'] != 'trash') {
$data['post_status'] = "private";
}
return $data;
}
关键改动点:
str_replace($search, $replace, $subject):第三个参数(要处理的文本)传入的是已经跑过esc_attr(get_the_title())之后的结果,先转义安全再做字符串替换,两者顺序不能反——是「先转义、再基于转义后的文本做替换」- Subscriber 角色只保留
edit_notes/publish_notes/delete_notes三项权限——既然笔记一定会被服务端强制成private、永远不会处于「published」状态,edit_published_notes/delete_published_notes这两个权限对 Subscriber 来说就是多余的攻击面,遵循「只给完成功能所必须的最小权限」原则去掉 - XSS 演示的核心结论:输出到页面的地方比输入的地方更重要——即使恶意脚本被存进了数据库,只要输出时用对了转义函数(
esc_attr/esc_html/esc_textarea),浏览器就不会把它当成可执行代码,只会显示成没有威胁的纯文本;反过来,哪怕输入时看似「过滤过」,只要输出时忘了转义,攻击代码依然可能被执行 - 不同输出场景要用不同的转义函数:属性值用
esc_attr(),普通 HTML 文本用esc_html(),<textarea>内容更适合专用的esc_textarea()(跟esc_attr策略略有差异,是给 textarea 场景特别优化的)——用错场景不是不安全,但不是最佳实践 - 服务端净化是「双保险」而不是「替代」前端转义:
sanitize_text_field()/sanitize_textarea_field()在数据存入数据库之前就把 HTML 标签整个剥离掉,即使前端某处转义函数用错或漏用,恶意内容也从未真正进入数据库;这也是「不能只信任浏览器传来的数据,必须在服务端兜底」这条安全原则的又一次体现(跟上一讲强制private状态是同一个道理) - WordPress 默认只有 Administrator 角色拥有
unfiltered_html权限(可以提交不被过滤的原始 HTML/JS),连 Editor、Author 都没有这个权限——这是 WordPress 核心自带的安全默认值,不需要额外配置
[截图:前台笔记标题不再显示 "Private: " 前缀的效果]
Hook / Function 速查
| 名称 | 类型 | 用途 |
|---|---|---|
str_replace($search, $replace, $subject) | PHP 内建 function | 字符串查找替换 |
esc_html() | WP 内建 function | 转义字符串,用于输出到普通 HTML 文本内容 |
esc_textarea() | WP 内建 function | 专门用于 <textarea> 内容的转义函数 |
sanitize_text_field() | WP 内建 function | 净化单行文本输入,去除 HTML 标签等潜在危险内容 |
sanitize_textarea_field() | WP 内建 function | 净化多行文本输入(保留换行),同样会剥离 HTML 标签 |
unfiltered_html(权限能力) | WP 内建权限能力 | 允许提交不经过滤的原始 HTML,默认只有 Administrator 拥有 |
常见坑
- 以为「反正笔记是私有的,只有本人能看到,不用管 XSS」——转录里明确指出「层层防护」的思路:即使今天没有已知的泄露途径,也要假设未来可能出现意外的泄露场景,多一层服务端净化不会有害
- 只在前端展示时转义、不在服务端净化——如果某处输出场景忘了套转义函数(比如新增一个展示笔记的地方),存在数据库里的原始恶意代码依然会被执行;服务端净化能保证不管输出端有没有疏漏,恶意内容从源头就已经被清除
- 收紧 Subscriber 权限时手滑连
edit_notes/publish_notes/delete_notes也取消了——这三项是核心功能所需,只应该去掉edit_published_notes/delete_published_notes这两项冗余权限
延伸 / 后续讲座会用到
下一讲会给每个用户设置笔记数量上限,防止恶意用户疯狂创建笔记拖垮数据库/网站性能。
Sources
Udemy:
- Become a WordPress Developer: Unlocking Power With Code — Section 19, EP099