数字化技能提升教程,数字化时代生存宝典

首页 / 建站程序 / CMS / WordPress教程

WordPress插件开发安全排查

浏览:822
WordPress教程
WordPress教程
类型:CMS

WordPress教程提供了关于WordPress的基础知识和技巧,包括安装、设置、发布内容、选择主题、插件管理等内容。通过学习WordPress教程,您可以轻松地创建个性化的网站,并了解如何优化网站性能、增加功能以及进行搜索引擎优化等。无需编写代码,您可以快速掌握使用WordPress构建强大网站的方法。

WordPress插件目录中的所有插件都必须在安全层面经过严格打磨,才能确保用户数据和网站稳定不受威胁。因此WordPress插件安全性是开发过程中绝对不能忽视的核心环节。本教程将系统讲解插件安全开发的关键技术,包括用户权限检查、数据验证、输入清理、输出转义,以及随机数的创建与验证。​

一、用户权限检查

如果你的插件涉及用户提交数据的功能,那么用户权限检查都是必须优先实现的安全步骤。

1、用户角色与权限

构建WordPress插件安全防护的核心在于理解并利用WordPress自带的用户角色、权限体系,为不同身份的用户分配了明确的操作权限,是插件权限控制的基础。​

每当用户登录WordPress时系统会根据其所属角色自动分配对应的权限。这里的 “角色”,本质上是对用户群体的分类,每个角色都预设了一组特定的操作权限。例如网站所有者通常拥有管理员角色,而内容创作者可能是编辑或作者角色;一个站点可以设置多个管理员,共同拥有最高操作权限。​

而权限则是具体的权限标识,比如 “manage_options” 能力代表用户可查看、编辑并保存网站全局设置,仅默认分配给 “管理员” 角色,“编辑” 或其他低级别角色均无此权限。

2、用户等级制度

WordPress的用户角色存在明确的等级关系,角色等级越高,拥有的能力越全面,高级角色会自动继承所有低级角色的能力。​

以单站点WordPress为例,管理员是等级最高的角色,一般同时拥有“订阅者”、“投稿者”、“作者”、“编辑” 这四个低级角色的全部权限。

示例:权限控制的正反对比​

无权限限制的危险代码​:

下面的代码在文章前端添加了删除文章链接,但未做任何权限检查。所有访问网站的人,包括未登录的访客都能点击链接将文章移入回收站,严重威胁网站内容安全。​

<?php
/**
* 在前端创建一个删除文章的链接
*/
function wporg_generate_delete_link($content)
{
// 只在单文章页面运行
if (is_single() && in_the_loop() && is_main_query()) {
// 添加查询参数: action, post
$url = add_query_arg(
[
'action' => 'wporg_frontend_delete',
'post' => get_the_ID(),
],
home_url()
);
return $content . ' <a href=' . esc_url($url) . '>' . esc_html__('Delete Post', 'wporg') . '</a>';
}
return null;
}

/**
* 处理用户请求
*/
function wporg_delete_post()
{
if (isset($_GET['action']) && $_GET['action'] === 'wporg_frontend_delete') {

// 检查请求中是否有文章参数
$post_id = (isset($_GET['post'])) ? ($_GET['post']) : (null);

// 检查请求的文章是否存在
$post = get_post((int)$post_id);
if (empty($post)) {
return;
}

// 删除文章
wp_trash_post($post_id);

// 跳转到文章管理界面
$redirect = admin_url('edit.php');
wp_safe_redirect($redirect);

// 退出
die;
}
}

/**
* 添加删除文章的链接到文章内容后面
*/
add_filter('the_content', 'wporg_generate_delete_link');

/**
* WordPress 初始化时注册请求处理函数
*/
add_action('init', 'wporg_delete_post');

例如让WordPress中只有编辑以上角色的用户才拥有这个权限,只需将上述代码稍微修改一下,在显示链接时检查用户是否拥有 “edit_others_posts” 。

<?php
if (current_user_can('edit_others_posts')) {
/**
* 添加删除文章的链接到文章内容后面
*/
add_filter('the_content', 'wporg_generate_delete_link');

/**
* WordPress 初始化时注册请求处理函数
*/
add_action('init', 'wporg_delete_post');
}

二、数据验证

数据验证是指按照预设的规则对输入数据进行检测,判断其是否符合预期格式与范围,结果只有 “有效” 或 “无效” 两种,过程主要用于处理外部传入的数据,比如用户在表单中填写的内容、通过API接口获取的第三方数据等。​

常见的数据验证场景​:

  • 检查必须字段是否为空
  • 检查输入的电话号码是否只包含数字和符号
  • 检查邮编是否为有效的邮政编码
  • 检查数量字段是否大于 0
  • 检查 Email 是否为有效的 Email 地址

注意:数据验证必须在后续业务逻辑(如数据库操作、功能执行)之前完成,无效数据应直接拦截,避免进入代码流程。​

前端可通过JavaScript实现即时验证(提升用户体验),但后端必须再次验证,因为前端验证可被轻易绕过(如修改浏览器代码、使用爬虫提交数据),后端验证才是真正的安全屏障。​

三种数据验证方法​

在WordPress插件开发中,我们可通过以下三类工具实现数据验证,覆盖绝大多数场景:​

1、PHP内置函数​

适合基础的数据格式与范围检查,常用函数包括:​

  • isset() 和 empty() :用来检查一个变量是否存在;
  • mb_strlen() 或者 strlen() :用来检查一个字符串长度是否符合要求;
  • preg_match(),strpos() :用于检查字符串中是否包含某些指定的字符串;
  • count() :用于检查数组中有多少元素;
  • in_array() :用于检查数组中是否存在某元素。
  • 2.、WordPress核心验证函数​
  • WordPress针对常见场景封装了专用验证函数,效率更高、兼容性更强,常用包括:​
  • is_email() :验证电子邮件地址是否有效;
  • term_exists() :检查分类项目是否存在;
  • username_exists() :检查用户名是否存在;
  • validate_file() :检查输入的文件路径是不是一个真实的路径(不是检查文件是否存在)​。

可在WordPress代码参考中,通过搜索 “_exists()”“validate()”“is*()” 这类命名规则找到更多场景化的验证函数。​

3、自定义PHP/JavaScript函数​

若遇到特殊验证需求,可自行编写验证函数。命名建议遵循语义化原则,如is_phone()(验证手机号)、is_available_coupon()(验证优惠券有效性)、is_zipcode()(验证邮编),便于后续维护。​

自定义验证函数应返回布尔值(true表示验证通过,false表示失败),可直接作为if条件判断的依据,简化代码逻辑。​

验证示例实战​

示例 1:验证美国邮政编码​

<input id=wporg_zip_code type=text maxlength=10 name=wporg_zip_code>

该字段限制最大输入长度为10字符,但无法限制输入内容,且浏览器可能不支持maxlength属性,或用户通过技术手段修改前端代码绕过限制,因此后端必须二次验证。​

编写自定义验证函数:​

<?php 
function is_us_zip_code($zip_code) { 
// 验证 1: 是否为空 
if (empty($zip_code)) { 
return false; 
}

// 验证 2: 是否多于 10 个字符
if (strlen(trim($zip_code)) > 10) {
return false;
}

// 验证 3: 数据格式是否正确
if (!preg_match('/^\d{5}(\-?\d{4})?$/', $zip_code)) {
return false;
}

// 验证如果,返回 true
return true;
}

在处理表单数据时,先执行验证,再执行后续操作:​

if (isset($_POST['wporg_zip_code']) && is_us_zip_code($_POST['wporg_zip_code'])) {
// 需要执行的操作
}

示例 2:验证文章排序参数​

假设插件允许用户选择文章排序方式(如按作者、发布日期),需确保用户提交的排序参数在允许范围内,避免传入恶意值导致SQL注入或功能异常。​

通过in_array()函数结合sanitize_key(),实现安全验证:​

<?php
$allowed_keys = ['author', 'post_author', 'date', 'post_date'];

$orderby = sanitize_key($_POST['orderby']);

if (in_array($orderby, $allowed_keys, true)) {
// 修改查询,根据 order_by 键来排序
}

三、安全输入

安全输入即对用户提交的原始数据进行 “清理”,移除或转换其中可能存在的恶意内容(如HTML 标签、脚本代码)。当我们无法通过严格验证确定数据类型,或数据格式灵活(如用户留言、富文本内容)时,输入清理就成为保障安全的关键步骤。​

需明确的是,输入清理与数据验证并非互斥,而是互补关系,验证确保数据 “符合规则”,清理确保数据 “无恶意内容”,两者结合才能最大化输入安全。​

清理数据的最优方案:使用WordPress内置函数​

WordPress封装了一系列sanitize_*()前缀的辅助函数,针对不同类型的数据场景做了优化,无需自行编写复杂过滤逻辑,直接调用即可实现安全清理。​

常用的sanitize_*()函数清单:​

  • sanitize_email()
  • sanitize_file_name()
  • sanitize_html_class()
  • sanitize_key()
  • sanitize_meta()
  • sanitize_mime_type()
  • sanitize_option()
  • sanitize_sql_orderby()
  • sanitize_text_field()
  • sanitize_title()
  • sanitize_title_for_query()
  • sanitize_title_with_dashes()
  • sanitize_user()
  • esc_url_raw()
  • wp_filter_post_kses()
  • wp_filter_nohtml_kses()

输入清理示例:普通文本字段处理​

假设插件有一个 “标题” 输入字段,需存储到文章自定义元数据中,使用sanitize_text_field()清理输入:​

<input id=title type=text name=title>

后端清理与存储:​

$title = sanitize_text_field($_POST['title']);
update_post_meta($post->ID, 'title', $title);
​
sanitize_text_field() 执行以下操作:
  • 检查无效的 UTF-8 字符;
  • 将大于小于字符(><)转换为实体;
  • 删除所有 HTML 标签;
  • 删除换行符,制表符和额外的空白字符;
  • 删除所有 8 字节字符。

四、安全输出

安全输出指的是在将数据渲染到网页之前,将可能被浏览器解析为代码的特殊字符(如< > ' ")转换为HTML实体或安全格式,避免浏览器将其执行为脚本。​

这一步的核心目的是防范XSS攻击,攻击者可能通过注入恶意脚本(如<script>stealCookies()</script>),在其他用户访问页面时窃取Cookie、篡改内容甚至控制网站。而输出转义能让这些恶意脚本被当作普通文本显示,无法执行。​

1、WordPress常用输出转义函数​

esc_html():在显示 HTML 时,使用此函数;

esc_url():在输出 URL 时,使用此函数,包括在src和href属性中的 URL;

esc_js():对内联 JavaScript 使用此函数;

esc_attr():把数据设置为 HTML 元素属性时使用此能力。

2、使用本地化函数

WordPress本地化函数包括_e() 或 __()等,例如下面的本地化函数 esc_html_e 函数集成了数据转义的能力:

esc_html_e( 'Hello World', 'text_domain' );
// 等同于
echo esc_html( __( 'Hello World', 'text_domain' ) );

结合了本地化和转义能力的函数有:

  • esc_html __()
  • esc_html_e()
  • esc_html_x()
  • esc_attr__()
  • esc_attr_e()
  • esc_attr_x()

3、自定义转义

自定义转义通过函数wp_kses() 实现,一般适用于需要以特殊方式转义输出的情况,确保只有指定的 HTML元素、属性和属性值会出现在输出中,并对HTML实体进行规范化。

$allowed_html = [
    'a'      => [
        'href'  => [],
        'title' => [],
    ],
    'br'     => [],
    'em'     => [],
    'strong' => [],
];
echo wp_kses( $custom_content, $allowed_html );

wp_kses_post() 是 wp_kses 函数的封装,其中$allowed_html是显示文章内容使用的一组规则。

echo wp_kses_post( $post_content );

五、随机数验证

随机数验证的原理是通过一次性请求的方式验证来源和意图。生成链接时可以使用wp_create_nonce()函数添加一个随机数到链接,传递给该函数的参数确保创建的随机数对于该操作是唯一的。然后在处理删除请求时检查该随机数是否与期望一致。

使用能力检查、数据验证、安全输入、安全输入和随机数验证的完整示例:

<?php
/**
* 在前端创建一个删除文章的链接
*/
function wporg_generate_delete_link($content){
// 只在单文章页面运行
if (is_single() && in_the_loop() && is_main_query()) {
// 添加查询参数: action, post
$url = add_query_arg(
[
'action' => 'wporg_frontend_delete',
'post' => get_the_ID(),
'nonce' => wp_create_nonce('wporg_frontend_delete'),
],
home_url()
);
return $content . ' <a href=' . esc_url($url) . '>' . esc_html__('Delete Post', 'wporg') . '</a>';
}
return null;
}

/**
* 处理用户请求
*/
function wporg_delete_post(){
if (
isset($_GET['action']) &&
isset($_GET['nonce']) &&
$_GET['action'] === 'wporg_frontend_delete' &&
wp_verify_nonce($_GET['nonce'], 'wporg_frontend_delete')
) {

// 检查请求中是否有文章参数
$post_id = (isset($_GET['post'])) ? ($_GET['post']) : (null);

// 检查请求的文章是否存在
$post = get_post((int)$post_id);
if (empty($post)) {
return;
}

// 删除文章
wp_trash_post($post_id);

// 跳转到文章管理界面
$redirect = admin_url('edit.php');
wp_safe_redirect($redirect);

// 退出
die;
}
}

if (current_user_can('edit_others_posts')) {
/**
* 添加删除文章的链接到文章内容后面
*/
add_filter('the_content', 'wporg_generate_delete_link');

/**
* WordPress 初始化时注册请求处理函数
*/
add_action('init', 'wporg_delete_post');
}

广告合作

广告合作

QQ群号:4114653

QQ群号:4114653

温馨提示:

  1. 本网站发布的内容(图片、视频和文字)以原创、转载和分享网络内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。邮箱:2942802716#qq.com(#改为@)。
  2. 本站原创内容未经允许不得转裁,转载请注明出处“站长百科”和原文地址。