WP DEVELOP

EP099. “去除 Private 前缀、权限收紧与 XSS 防护”

首页 WordPress 开发课程 MY NOTES 前端功能 · EP099
约 10 分钟· #EP099#MY NOTES 前端功能
🔒 登录后可标记已读

三件收尾工作:① 笔记设成 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_notesdelete_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.phpmakeNotePrivate() 追加服务端强制净化标题和正文

// 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