wordpress-security-validation

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

WordPress Security & Data Validation

WordPress安全与数据验证

Version: 1.0.0 Target: WordPress 6.7+ | PHP 8.3+ Skill Level: Intermediate to Advanced
版本: 1.0.0 适配目标: WordPress 6.7+ | PHP 8.3+ 技能等级: 中高级

Overview

概述

Security is not optional in WordPress development—it's fundamental. This skill teaches the three-layer security model that prevents XSS, CSRF, SQL injection, and other common web vulnerabilities through proper input sanitization, business logic validation, and output escaping.
The Golden Rule: "Sanitize on input, validate for logic, escape on output."
在WordPress开发中,安全并非可选功能,而是核心基础。本技能将教授三层安全模型,通过正确的输入清理、业务逻辑验证和输出转义来防范XSS、CSRF、SQL注入及其他常见Web漏洞。
黄金法则: "输入时清理,逻辑上验证,输出时转义。"

Why This Matters

为什么这很重要

Every year, thousands of WordPress sites are compromised due to security vulnerabilities in plugins and themes. Most of these attacks exploit one of three weaknesses:
  1. XSS (Cross-Site Scripting): Malicious JavaScript injected through unsanitized output
  2. CSRF (Cross-Site Request Forgery): Unauthorized actions performed on behalf of authenticated users
  3. SQL Injection: Database manipulation through unsanitized database queries
This skill provides complete, production-ready patterns for preventing all three attack vectors.

每年都有成千上万的WordPress站点因插件和主题中的安全漏洞而被攻陷。这些攻击大多利用以下三类弱点:
  1. XSS(跨站脚本攻击): 通过未清理的输出注入恶意JavaScript
  2. CSRF(跨站请求伪造): 代表已认证用户执行未授权操作
  3. SQL注入: 通过未清理的数据库查询操纵数据库
本技能提供完整的生产级实现模式,可防范这三类攻击向量。

The Three-Layer Security Model

1. Nonce:CSRF防护

什么是Nonce?

WordPress security follows a defense-in-depth strategy with three distinct layers:
User Input → [1. SANITIZE] → [2. VALIDATE] → Process → [3. ESCAPE] → Output
Nonce(一次性数字)是一种加密令牌,用于验证请求来自你的站点,而非恶意外部来源。它们可防范**跨站请求伪造(CSRF)**攻击。
CSRF攻击的原理:
html
<!-- 攻击者的恶意站点:evil.com -->
<img src="https://yoursite.com/wp-admin/admin.php?action=delete_user&id=1">
<!-- 如果用户已登录yoursite.com,该请求会被执行! -->
Nonce如何防范CSRF:
html
<!-- 带有nonce的合法请求 -->
<form action="admin.php?action=delete_user&id=1" method="POST">
    <?php wp_nonce_field('delete_user_1', 'delete_nonce'); ?>
    <button>删除用户</button>
</form>

<!-- 攻击者无法生成有效的nonce(与用户会话绑定) -->

Layer 1: Sanitization (Input Cleaning)

Nonce实现模式

模式1:表单Nonce(最常用)

Purpose: Remove dangerous characters and normalize data format When: Immediately upon receiving user input Example:
sanitize_text_field($_POST['username'])
之前(存在漏洞):
php
// 存在漏洞的表单处理
if (isset($_POST['submit'])) {
    $user_id = absint($_POST['user_id']);
    delete_user($user_id); // ⚠️ 易受CSRF攻击!
}
之后(安全版本):
php
// 在表单中生成nonce
<form method="post" action="">
    <?php wp_nonce_field('delete_user_action', 'delete_user_nonce'); ?>
    <input type="hidden" name="user_id" value="42">
    <button type="submit" name="submit">删除用户</button>
</form>

// 提交时验证nonce
if (isset($_POST['submit'])) {
    // 安全检查1:验证nonce
    if (!isset($_POST['delete_user_nonce']) ||
        !wp_verify_nonce($_POST['delete_user_nonce'], 'delete_user_action')) {
        wp_die('安全检查失败:无效的nonce');
    }

    // 安全检查2:权限验证
    if (!current_user_can('delete_users')) {
        wp_die('你没有删除用户的权限');
    }

    // 现在可以安全处理
    $user_id = absint($_POST['user_id']);
    wp_delete_user($user_id);
}
核心函数:
  • wp_nonce_field($action, $name)
    - 生成隐藏的nonce字段
  • wp_verify_nonce($nonce, $action)
    - 验证nonce的有效性

Layer 2: Validation (Logic Checks)

模式2:URL Nonce

Purpose: Ensure data meets business requirements When: After sanitization, before processing Example:
if (!is_email($email)) { /* error */ }
使用场景: 删除/回收站链接、后台操作
php
// 生成带nonce的URL
$delete_url = wp_nonce_url(
    admin_url('admin.php?action=delete_post&post_id=123'),
    'delete_post_123',  // 操作(必须唯一)
    'delete_nonce'      // 查询参数名
);

echo '<a href="' . esc_url($delete_url) . '">删除文章</a>';

// 在处理函数中验证nonce
add_action('admin_action_delete_post', 'handle_delete_post');
function handle_delete_post() {
    // 验证URL中的nonce
    if (!isset($_GET['delete_nonce']) ||
        !wp_verify_nonce($_GET['delete_nonce'], 'delete_post_123')) {
        wp_die('无效的安全令牌');
    }

    // 验证权限
    $post_id = absint($_GET['post_id']);
    if (!current_user_can('delete_post', $post_id)) {
        wp_die('你无法删除这篇文章');
    }

    // 删除文章
    wp_delete_post($post_id, true); // true = 强制删除

    // 重定向并显示成功消息
    wp_redirect(add_query_arg('message', 'deleted', wp_get_referer()));
    exit;
}

Layer 3: Escaping (Output Protection)

模式3:AJAX Nonce

Purpose: Prevent XSS by encoding special characters When: Every time you output data to browser Example:
echo esc_html($user_input);
Critical Distinction:
  • Sanitization removes/transforms invalid data (changes the value)
  • Validation checks if data is acceptable (returns true/false)
  • Escaping makes data safe for display (context-specific encoding)

使用场景: 前端AJAX请求
之前(存在漏洞):
javascript
// ⚠️ 存在漏洞的AJAX请求
jQuery.post(ajaxurl, {
    action: 'update_user_meta',
    user_id: 42,
    meta_key: 'favorite_color',
    meta_value: 'blue'
}, function(response) {
    console.log(response);
});
之后(安全版本):
PHP(注册脚本并传递nonce):
php
add_action('wp_enqueue_scripts', 'enqueue_ajax_script');
function enqueue_ajax_script() {
    wp_enqueue_script('my-ajax-script',
        plugin_dir_url(__FILE__) . 'js/ajax.js',
        ['jquery'],
        '1.0.0',
        true
    );

    // 向JavaScript传递nonce和AJAX URL
    wp_localize_script('my-ajax-script', 'myAjax', [
        'ajaxurl' => admin_url('admin-ajax.php'),
        'nonce' => wp_create_nonce('my_ajax_nonce'), // 生成nonce
    ]);
}

// 带nonce验证的AJAX处理函数
add_action('wp_ajax_update_user_meta', 'handle_ajax_update');
function handle_ajax_update() {
    // 验证nonce
    check_ajax_referer('my_ajax_nonce', 'nonce');

    // 验证权限
    if (!current_user_can('edit_users')) {
        wp_send_json_error(['message' => '权限不足']);
    }

    // 清理输入
    $user_id = absint($_POST['user_id']);
    $meta_key = sanitize_key($_POST['meta_key']);
    $meta_value = sanitize_text_field($_POST['meta_value']);

    // 更新元数据
    update_user_meta($user_id, $meta_key, $meta_value);

    wp_send_json_success(['message' => '更新成功']);
}
JavaScript(在AJAX中使用nonce):
javascript
jQuery(document).ready(function($) {
    $('#update-button').on('click', function() {
        $.post(myAjax.ajaxurl, {
            action: 'update_user_meta',
            nonce: myAjax.nonce,  // 包含nonce
            user_id: 42,
            meta_key: 'favorite_color',
            meta_value: 'blue'
        }, function(response) {
            if (response.success) {
                console.log(response.data.message);
            } else {
                console.error(response.data.message);
            }
        });
    });
});
核心函数:
  • wp_create_nonce($action)
    - 生成nonce令牌
  • check_ajax_referer($action, $query_arg)
    - 验证AJAX nonce(验证失败时终止执行)
  • wp_send_json_success($data)
    - 发送JSON成功响应
  • wp_send_json_error($data)
    - 发送JSON错误响应

1. Nonces: CSRF Protection

Nonce最佳实践

What Are Nonces?

Nonces (Numbers Used Once) are cryptographic tokens that verify a request originated from your site, not a malicious external source. They prevent Cross-Site Request Forgery (CSRF) attacks.
How CSRF Attacks Work:
html
<!-- Attacker's malicious site: evil.com -->
<img src="https://yoursite.com/wp-admin/admin.php?action=delete_user&id=1">
<!-- If user is logged into yoursite.com, this executes! -->
How Nonces Prevent CSRF:
html
<!-- Legitimate request with nonce -->
<form action="admin.php?action=delete_user&id=1" method="POST">
    <?php wp_nonce_field('delete_user_1', 'delete_nonce'); ?>
    <button>Delete User</button>
</form>

<!-- Attacker cannot generate valid nonce (tied to user session) -->
建议:
  • 使用唯一的操作名称(例如
    delete_post_$post_id
    ,而非仅
    delete
  • 始终在处理任何数据之前验证nonce
  • 将nonce检查与权限验证结合使用
  • 使用特定的nonce函数(AJAX请求使用
    check_ajax_referer
禁止:
  • 为多个操作重复使用同一个nonce操作名称
  • 对“只读”操作跳过nonce验证
  • 仅依赖nonce验证(始终同时检查权限)
  • 将nonce存储在Cookie或URL中长期使用(它们会过期)
Nonce有效期: WordPress nonce默认24小时后过期(由于时间窗口设置,实际有效期为前后各12小时)。

Nonce Implementation Patterns

2. 清理函数参考

Pattern 1: Form Nonces (Most Common)

BEFORE (Vulnerable):
php
// Vulnerable form processing
if (isset($_POST['submit'])) {
    $user_id = absint($_POST['user_id']);
    delete_user($user_id); // ⚠️ CSRF vulnerable!
}
AFTER (Secure):
php
// Generate nonce in form
<form method="post" action="">
    <?php wp_nonce_field('delete_user_action', 'delete_user_nonce'); ?>
    <input type="hidden" name="user_id" value="42">
    <button type="submit" name="submit">Delete User</button>
</form>

// Verify nonce on submission
if (isset($_POST['submit'])) {
    // Security check #1: Verify nonce
    if (!isset($_POST['delete_user_nonce']) ||
        !wp_verify_nonce($_POST['delete_user_nonce'], 'delete_user_action')) {
        wp_die('Security check failed: Invalid nonce');
    }

    // Security check #2: Capability check
    if (!current_user_can('delete_users')) {
        wp_die('You do not have permission to delete users');
    }

    // Now safe to process
    $user_id = absint($_POST['user_id']);
    wp_delete_user($user_id);
}
Key Functions:
  • wp_nonce_field($action, $name)
    - Generates hidden nonce field
  • wp_verify_nonce($nonce, $action)
    - Verifies nonce validity
清理通过移除或编码危险字符,将用户输入转换为安全格式。它是防范恶意数据的第一道防线

Pattern 2: URL Nonces

核心清理函数

Use Case: Delete/trash links, admin actions
php
// Generate nonce URL
$delete_url = wp_nonce_url(
    admin_url('admin.php?action=delete_post&post_id=123'),
    'delete_post_123',  // Action (must be unique)
    'delete_nonce'      // Query parameter name
);

echo '<a href="' . esc_url($delete_url) . '">Delete Post</a>';

// Verify nonce in handler
add_action('admin_action_delete_post', 'handle_delete_post');
function handle_delete_post() {
    // Verify nonce from URL
    if (!isset($_GET['delete_nonce']) ||
        !wp_verify_nonce($_GET['delete_nonce'], 'delete_post_123')) {
        wp_die('Invalid security token');
    }

    // Verify capability
    $post_id = absint($_GET['post_id']);
    if (!current_user_can('delete_post', $post_id)) {
        wp_die('You cannot delete this post');
    }

    // Delete post
    wp_delete_post($post_id, true); // true = force delete

    // Redirect with success message
    wp_redirect(add_query_arg('message', 'deleted', wp_get_referer()));
    exit;
}
函数使用场景示例输入输出
sanitize_text_field()
单行文本(用户名、标题)
"Hello <script>alert('xss')</script>"
"Hello alert('xss')"
sanitize_email()
电子邮件地址
"user@example.com<script>"
"user@example.com"
sanitize_url()
/
esc_url_raw()
URL(用于存储)
"javascript:alert('xss')"
""
(被拦截)
sanitize_key()
数组键、元数据键
"my key!"
"my_key"
sanitize_file_name()
文件上传
"../../etc/passwd"
"..etcpasswd"
absint()
正整数
"-5"
,
"42abc"
5
,
42
intval()
任意整数
"-5"
,
"42.7"
-5
,
42
floatval()
浮点数
"3.14abc"
3.14
wp_kses_post()
HTML内容(允许安全标签)
"<p>Safe</p><script>Bad</script>"
"<p>Safe</p>"
wp_kses()
自定义允许标签的HTML见下文自定义过滤
sanitize_textarea_field()
多行文本
"Line 1\nLine 2<script>"
"Line 1\nLine 2"
sanitize_title()
文章别名
"Hello World!"
"hello-world"

Pattern 3: AJAX Nonces

详细示例

文本清理

Use Case: Frontend AJAX requests
BEFORE (Vulnerable):
javascript
// ⚠️ Vulnerable AJAX request
jQuery.post(ajaxurl, {
    action: 'update_user_meta',
    user_id: 42,
    meta_key: 'favorite_color',
    meta_value: 'blue'
}, function(response) {
    console.log(response);
});
AFTER (Secure):
PHP (Enqueue script with nonce):
php
add_action('wp_enqueue_scripts', 'enqueue_ajax_script');
function enqueue_ajax_script() {
    wp_enqueue_script('my-ajax-script',
        plugin_dir_url(__FILE__) . 'js/ajax.js',
        ['jquery'],
        '1.0.0',
        true
    );

    // Pass nonce and AJAX URL to JavaScript
    wp_localize_script('my-ajax-script', 'myAjax', [
        'ajaxurl' => admin_url('admin-ajax.php'),
        'nonce' => wp_create_nonce('my_ajax_nonce'), // Generate nonce
    ]);
}

// AJAX handler with nonce verification
add_action('wp_ajax_update_user_meta', 'handle_ajax_update');
function handle_ajax_update() {
    // Verify nonce
    check_ajax_referer('my_ajax_nonce', 'nonce');

    // Verify capability
    if (!current_user_can('edit_users')) {
        wp_send_json_error(['message' => 'Permission denied']);
    }

    // Sanitize input
    $user_id = absint($_POST['user_id']);
    $meta_key = sanitize_key($_POST['meta_key']);
    $meta_value = sanitize_text_field($_POST['meta_value']);

    // Update meta
    update_user_meta($user_id, $meta_key, $meta_value);

    wp_send_json_success(['message' => 'Updated successfully']);
}
JavaScript (Use nonce in AJAX):
javascript
jQuery(document).ready(function($) {
    $('#update-button').on('click', function() {
        $.post(myAjax.ajaxurl, {
            action: 'update_user_meta',
            nonce: myAjax.nonce,  // Include nonce
            user_id: 42,
            meta_key: 'favorite_color',
            meta_value: 'blue'
        }, function(response) {
            if (response.success) {
                console.log(response.data.message);
            } else {
                console.error(response.data.message);
            }
        });
    });
});
Key Functions:
  • wp_create_nonce($action)
    - Generate nonce token
  • check_ajax_referer($action, $query_arg)
    - Verify AJAX nonce (dies on failure)
  • wp_send_json_success($data)
    - Send JSON success response
  • wp_send_json_error($data)
    - Send JSON error response
php
// 单行文本(移除HTML、换行符、多余空格)
$username = sanitize_text_field($_POST['username']);
// 输入: "  John <b>Doe</b>\n"
// 输出: "John Doe"

// 多行文本(保留换行符,移除HTML)
$bio = sanitize_textarea_field($_POST['bio']);
// 输入: "Line 1\nLine 2<script>alert('xss')</script>"
// 输出: "Line 1\nLine 2alert('xss')"

// 电子邮件(验证格式并移除无效字符)
$email = sanitize_email($_POST['email']);
// 输入: "user@EXAMPLE.com <script>"
// 输出: "user@example.com"

// URL(移除危险协议)
$website = esc_url_raw($_POST['website']);
// 输入: "javascript:alert('xss')"
// 输出: ""(协议被拦截)
// 输入: "http://example.com"
// 输出: "http://example.com"

Nonce Best Practices

数值清理

DO:
  • Use unique action names (e.g.,
    delete_post_$post_id
    , not just
    delete
    )
  • Always verify nonces BEFORE processing any data
  • Combine nonce checks with capability checks
  • Use specific nonce functions (
    check_ajax_referer
    for AJAX)
DON'T:
  • Reuse the same nonce action for multiple operations
  • Skip nonce verification for "read-only" operations
  • Trust nonce verification alone (always check capabilities too)
  • Store nonces in cookies or URLs for long-term use (they expire)
Nonce Lifespan: WordPress nonces expire after 24 hours by default (12 hours in each direction due to time window).

php
// 仅允许正整数(取绝对值)
$post_id = absint($_POST['post_id']);
// 输入: "-5", "42", "123abc"
// 输出: 5, 42, 123

// 任意整数(保留负数)
$temperature = intval($_POST['temperature']);
// 输入: "-5", "42.7", "99abc"
// 输出: -5, 42, 99

// 浮点数
$price = floatval($_POST['price']);
// 输入: "19.99", "20.5abc"
// 输出: 19.99, 20.5

2. Sanitization Functions Reference

HTML清理

Sanitization transforms user input into a safe format by removing or encoding dangerous characters. It's the first line of defense against malicious data.
wp_kses_post() - 允许WordPress编辑器标签:
php
$content = wp_kses_post($_POST['content']);
// 允许的标签: <p>, <a>, <strong>, <em>, <ul>, <ol>, <li>, <blockquote>, <img>等
// 拦截的标签: <script>, <iframe>, <object>, <embed>, <form>

// 输入: "<p>安全内容</p><script>恶意代码</script>"
// 输出: "<p>安全内容</p>alert('xss')"
wp_kses() - 自定义允许标签:
php
// 定义允许的标签和属性
$allowed_html = [
    'a' => [
        'href' => true,
        'title' => true,
        'target' => true,
    ],
    'strong' => [],
    'em' => [],
    'br' => [],
];

$clean_html = wp_kses($_POST['content'], $allowed_html);

// 输入: "<a href='#' onclick='alert(1)'>链接</a><script>恶意代码</script>"
// 输出: "<a href='#'>链接</a>恶意代码"(onclick属性被移除,script标签被剥离)
移除所有HTML:
php
$plain_text = wp_strip_all_tags($_POST['content']);
// 输入: "<p>Hello <b>World</b></p>"
// 输出: "Hello World"

Core Sanitization Functions

文件上传清理

FunctionUse CaseExample InputOutput
sanitize_text_field()
Single-line text (usernames, titles)
"Hello <script>alert('xss')</script>"
"Hello alert('xss')"
sanitize_email()
Email addresses
"user@example.com<script>"
"user@example.com"
sanitize_url()
/
esc_url_raw()
URLs (for storage)
"javascript:alert('xss')"
""
(blocked)
sanitize_key()
Array keys, meta keys
"my key!"
"my_key"
sanitize_file_name()
File uploads
"../../etc/passwd"
"..etcpasswd"
absint()
Positive integers
"-5"
,
"42abc"
5
,
42
intval()
Any integer
"-5"
,
"42.7"
-5
,
42
floatval()
Floating-point numbers
"3.14abc"
3.14
wp_kses_post()
HTML content (allows safe tags)
"<p>Safe</p><script>Bad</script>"
"<p>Safe</p>"
wp_kses()
HTML with custom allowed tagsSee belowCustom filtering
sanitize_textarea_field()
Multi-line text
"Line 1\nLine 2<script>"
"Line 1\nLine 2"
sanitize_title()
Post slugs
"Hello World!"
"hello-world"
php
// 清理文件名(移除路径遍历字符、特殊字符)
$safe_filename = sanitize_file_name($_FILES['upload']['name']);
// 输入: "../../etc/passwd", "my file!.php"
// 输出: "..etcpasswd", "my-file.php"

// 完整的文件上传示例
if (isset($_FILES['user_avatar'])) {
    // 首先验证nonce!
    if (!wp_verify_nonce($_POST['upload_nonce'], 'upload_avatar')) {
        wp_die('安全检查失败');
    }

    // 清理文件名
    $filename = sanitize_file_name($_FILES['user_avatar']['name']);

    // 验证文件类型
    $allowed_types = ['image/jpeg', 'image/png', 'image/gif'];
    $file_type = $_FILES['user_avatar']['type'];

    if (!in_array($file_type, $allowed_types)) {
        wp_die('无效的文件类型。仅允许JPG、PNG、GIF格式。');
    }

    // 使用WordPress上传处理函数(内置安全机制)
    $upload = wp_handle_upload($_FILES['user_avatar'], [
        'test_form' => false,
        'mimes' => [
            'jpg|jpeg' => 'image/jpeg',
            'png' => 'image/png',
            'gif' => 'image/gif',
        ],
    ]);

    if (isset($upload['error'])) {
        wp_die('上传失败:' . $upload['error']);
    }

    // 存储上传文件的URL
    $avatar_url = $upload['url'];
    update_user_meta(get_current_user_id(), 'avatar_url', $avatar_url);
}

Detailed Examples

数组清理

Text Sanitization

php
// Single-line text (removes HTML, line breaks, extra whitespace)
$username = sanitize_text_field($_POST['username']);
// Input: "  John <b>Doe</b>\n"
// Output: "John Doe"

// Multi-line text (preserves line breaks, removes HTML)
$bio = sanitize_textarea_field($_POST['bio']);
// Input: "Line 1\nLine 2<script>alert('xss')</script>"
// Output: "Line 1\nLine 2alert('xss')"

// Email (validates format and removes invalid characters)
$email = sanitize_email($_POST['email']);
// Input: "user@EXAMPLE.com <script>"
// Output: "user@example.com"

// URL (removes dangerous protocols)
$website = esc_url_raw($_POST['website']);
// Input: "javascript:alert('xss')"
// Output: "" (blocked protocol)
// Input: "http://example.com"
// Output: "http://example.com"
php
// 清理文本字段数组
$tags = array_map('sanitize_text_field', $_POST['tags']);
// 输入: ['tag1', '<script>tag2</script>', 'tag3']
// 输出: ['tag1', 'tag2', 'tag3']

// 清理整数数组
$ids = array_map('absint', $_POST['post_ids']);
// 输入: ['1', '2abc', '-5']
// 输出: [1, 2, 5]

// 清理电子邮件数组
$emails = array_map('sanitize_email', $_POST['email_list']);

Numeric Sanitization

自定义清理回调函数

php
// Positive integers only (absolute value)
$post_id = absint($_POST['post_id']);
// Input: "-5", "42", "123abc"
// Output: 5, 42, 123

// Any integer (preserves negative)
$temperature = intval($_POST['temperature']);
// Input: "-5", "42.7", "99abc"
// Output: -5, 42, 99

// Floating-point numbers
$price = floatval($_POST['price']);
// Input: "19.99", "20.5abc"
// Output: 19.99, 20.5
php
// 注册带清理回调的设置
register_setting('my_plugin_options', 'my_plugin_settings', [
    'type' => 'array',
    'sanitize_callback' => 'my_plugin_sanitize_settings',
]);

function my_plugin_sanitize_settings($input) {
    $sanitized = [];

    // 清理API密钥(仅允许字母数字)
    if (isset($input['api_key'])) {
        $sanitized['api_key'] = preg_replace('/[^a-zA-Z0-9]/', '', $input['api_key']);
    }

    // 清理布尔复选框
    $sanitized['enable_feature'] = isset($input['enable_feature']) ? 1 : 0;

    // 清理颜色(十六进制格式)
    if (isset($input['primary_color'])) {
        $color = sanitize_hex_color($input['primary_color']);
        $sanitized['primary_color'] = $color ? $color : '#000000';
    }

    // 清理选择选项(白名单)
    $allowed_modes = ['mode1', 'mode2', 'mode3'];
    if (isset($input['mode']) && in_array($input['mode'], $allowed_modes)) {
        $sanitized['mode'] = $input['mode'];
    } else {
        $sanitized['mode'] = 'mode1'; // 默认值
    }

    return $sanitized;
}

HTML Sanitization

3. 验证模式

wp_kses_post() - Allow WordPress Post Editor Tags:
php
$content = wp_kses_post($_POST['content']);
// Allows: <p>, <a>, <strong>, <em>, <ul>, <ol>, <li>, <blockquote>, <img>, etc.
// Blocks: <script>, <iframe>, <object>, <embed>, <form>

// Input: "<p>Safe content</p><script>alert('xss')</script>"
// Output: "<p>Safe content</p>alert('xss')"
wp_kses() - Custom Allowed Tags:
php
// Define allowed tags and attributes
$allowed_html = [
    'a' => [
        'href' => true,
        'title' => true,
        'target' => true,
    ],
    'strong' => [],
    'em' => [],
    'br' => [],
];

$clean_html = wp_kses($_POST['content'], $allowed_html);

// Input: "<a href='#' onclick='alert(1)'>Link</a><script>Bad</script>"
// Output: "<a href='#'>Link</a>Bad" (onclick removed, script stripped)
Strip All HTML:
php
$plain_text = wp_strip_all_tags($_POST['content']);
// Input: "<p>Hello <b>World</b></p>"
// Output: "Hello World"
验证用于确保数据在清理后符合业务逻辑要求。与清理(转换数据)不同,验证返回布尔值

File Upload Sanitization

内置验证函数

php
// Sanitize filename (removes path traversal, special characters)
$safe_filename = sanitize_file_name($_FILES['upload']['name']);
// Input: "../../etc/passwd", "my file!.php"
// Output: "..etcpasswd", "my-file.php"

// Complete file upload example
if (isset($_FILES['user_avatar'])) {
    // Verify nonce first!
    if (!wp_verify_nonce($_POST['upload_nonce'], 'upload_avatar')) {
        wp_die('Security check failed');
    }

    // Sanitize filename
    $filename = sanitize_file_name($_FILES['user_avatar']['name']);

    // Validate file type
    $allowed_types = ['image/jpeg', 'image/png', 'image/gif'];
    $file_type = $_FILES['user_avatar']['type'];

    if (!in_array($file_type, $allowed_types)) {
        wp_die('Invalid file type. Only JPG, PNG, GIF allowed.');
    }

    // Use WordPress upload handler (handles security)
    $upload = wp_handle_upload($_FILES['user_avatar'], [
        'test_form' => false,
        'mimes' => [
            'jpg|jpeg' => 'image/jpeg',
            'png' => 'image/png',
            'gif' => 'image/gif',
        ],
    ]);

    if (isset($upload['error'])) {
        wp_die('Upload failed: ' . $upload['error']);
    }

    // Store uploaded file URL
    $avatar_url = $upload['url'];
    update_user_meta(get_current_user_id(), 'avatar_url', $avatar_url);
}
函数用途示例
is_email($email)
验证电子邮件格式
is_email('user@example.com')
true
is_numeric($value)
验证是否为数字字符串
is_numeric('42')
true
is_int($value)
验证是否为整数类型
is_int(42)
true
is_array($value)
验证是否为数组类型
is_array([1,2,3])
true
is_user_logged_in()
验证用户是否已登录
is_user_logged_in()
true/false
username_exists($user)
验证用户名是否存在
username_exists('admin')
→ 用户ID或
null
email_exists($email)
验证电子邮件是否已注册
email_exists('user@example.com')
→ 用户ID或
false

Array Sanitization

验证示例

电子邮件验证

php
// Sanitize array of text fields
$tags = array_map('sanitize_text_field', $_POST['tags']);
// Input: ['tag1', '<script>tag2</script>', 'tag3']
// Output: ['tag1', 'tag2', 'tag3']

// Sanitize array of integers
$ids = array_map('absint', $_POST['post_ids']);
// Input: ['1', '2abc', '-5']
// Output: [1, 2, 5]

// Sanitize array of emails
$emails = array_map('sanitize_email', $_POST['email_list']);
php
$email = sanitize_email($_POST['email']);

// 验证格式
if (!is_email($email)) {
    $errors[] = '无效的电子邮件格式';
}

// 验证唯一性(注册场景)
if (email_exists($email)) {
    $errors[] = '该电子邮件已注册';
}

Custom Sanitization Callbacks

数值范围验证

php
// Register setting with sanitization callback
register_setting('my_plugin_options', 'my_plugin_settings', [
    'type' => 'array',
    'sanitize_callback' => 'my_plugin_sanitize_settings',
]);

function my_plugin_sanitize_settings($input) {
    $sanitized = [];

    // Sanitize API key (alphanumeric only)
    if (isset($input['api_key'])) {
        $sanitized['api_key'] = preg_replace('/[^a-zA-Z0-9]/', '', $input['api_key']);
    }

    // Sanitize boolean checkbox
    $sanitized['enable_feature'] = isset($input['enable_feature']) ? 1 : 0;

    // Sanitize color (hex format)
    if (isset($input['primary_color'])) {
        $color = sanitize_hex_color($input['primary_color']);
        $sanitized['primary_color'] = $color ? $color : '#000000';
    }

    // Sanitize select option (whitelist)
    $allowed_modes = ['mode1', 'mode2', 'mode3'];
    if (isset($input['mode']) && in_array($input['mode'], $allowed_modes)) {
        $sanitized['mode'] = $input['mode'];
    } else {
        $sanitized['mode'] = 'mode1'; // Default
    }

    return $sanitized;
}

php
$age = absint($_POST['age']);

// 验证范围
if ($age < 18 || $age > 100) {
    $errors[] = '年龄必须在18到100之间';
}

// 验证正数
if ($quantity <= 0) {
    $errors[] = '数量必须大于0';
}

3. Validation Patterns

字符串长度验证

Validation ensures data meets business logic requirements after sanitization. Unlike sanitization (which transforms data), validation returns true/false.
php
$username = sanitize_text_field($_POST['username']);

// 验证最小长度
if (strlen($username) < 3) {
    $errors[] = '用户名至少需要3个字符';
}

// 验证最大长度
if (strlen($username) > 20) {
    $errors[] = '用户名不能超过20个字符';
}

Built-in Validation Functions

必填字段验证

FunctionPurposeExample
is_email($email)
Valid email format
is_email('user@example.com')
true
is_numeric($value)
Numeric string
is_numeric('42')
true
is_int($value)
Integer type
is_int(42)
true
is_array($value)
Array type
is_array([1,2,3])
true
is_user_logged_in()
User authentication
is_user_logged_in()
true/false
username_exists($user)
Username exists
username_exists('admin')
user_id
or
null
email_exists($email)
Email exists
email_exists('user@example.com')
user_id
or
false
php
// 检查字段是否存在且不为空
if (empty($_POST['title']) || trim($_POST['title']) === '') {
    $errors[] = '标题为必填项';
}

// 另一种方式:isset() + 非空检查
if (!isset($_POST['terms']) || $_POST['terms'] !== 'accepted') {
    $errors[] = '你必须接受服务条款';
}

Validation Examples

模式匹配(正则表达式)

Email Validation

php
$email = sanitize_email($_POST['email']);

// Validate format
if (!is_email($email)) {
    $errors[] = 'Invalid email address format';
}

// Validate uniqueness (for registration)
if (email_exists($email)) {
    $errors[] = 'Email address already registered';
}
php
$phone = sanitize_text_field($_POST['phone']);

// 验证电话号码格式(美国格式:(555) 123-4567)
if (!preg_match('/^\(\d{3}\) \d{3}-\d{4}$/', $phone)) {
    $errors[] = '电话号码格式必须为:(555) 123-4567';
}

// 验证仅包含字母数字
$product_code = sanitize_text_field($_POST['product_code']);
if (!preg_match('/^[a-zA-Z0-9]+$/', $product_code)) {
    $errors[] = '产品代码只能包含字母和数字';
}

Numeric Range Validation

多字段验证

php
$age = absint($_POST['age']);

// Validate range
if ($age < 18 || $age > 100) {
    $errors[] = 'Age must be between 18 and 100';
}

// Validate positive number
if ($quantity <= 0) {
    $errors[] = 'Quantity must be greater than zero';
}
php
function validate_registration_form($data) {
    $errors = [];

    // 电子邮件验证
    $email = sanitize_email($data['email']);
    if (!is_email($email)) {
        $errors['email'] = '无效的电子邮件地址';
    } elseif (email_exists($email)) {
        $errors['email'] = '该电子邮件已注册';
    }

    // 用户名验证
    $username = sanitize_text_field($data['username']);
    if (strlen($username) < 3) {
        $errors['username'] = '用户名过短(至少3个字符)';
    } elseif (username_exists($username)) {
        $errors['username'] = '该用户名已被使用';
    }

    // 密码验证
    if (strlen($data['password']) < 8) {
        $errors['password'] = '密码至少需要8个字符';
    }

    // 密码确认
    if ($data['password'] !== $data['password_confirm']) {
        $errors['password_confirm'] = '两次输入的密码不一致';
    }

    // 年龄验证
    $age = absint($data['age']);
    if ($age < 18) {
        $errors['age'] = '你必须年满18岁才能注册';
    }

    return empty($errors) ? true : $errors;
}

// 使用示例
$result = validate_registration_form($_POST);
if ($result === true) {
    // 处理注册
} else {
    // 显示错误
    foreach ($result as $field => $error) {
        echo "<p class='error'>$error</p>";
    }
}

String Length Validation

自定义验证规则

php
$username = sanitize_text_field($_POST['username']);

// Validate minimum length
if (strlen($username) < 3) {
    $errors[] = 'Username must be at least 3 characters';
}

// Validate maximum length
if (strlen($username) > 20) {
    $errors[] = 'Username cannot exceed 20 characters';
}
php
// 验证URL是否来自允许的域名
function validate_allowed_domain($url) {
    $allowed_domains = ['example.com', 'wordpress.org'];
    $host = parse_url($url, PHP_URL_HOST);

    return in_array($host, $allowed_domains);
}

// 验证日期格式和范围
function validate_date($date_string) {
    $date = DateTime::createFromFormat('Y-m-d', $date_string);

    if (!$date) {
        return false; // 格式无效
    }

    // 检查日期是否不在过去
    $now = new DateTime();
    if ($date < $now) {
        return false;
    }

    return true;
}

// 验证信用卡(Luhn算法)
function validate_credit_card($number) {
    $number = preg_replace('/\D/', '', $number); // 移除非数字字符

    if (strlen($number) < 13 || strlen($number) > 19) {
        return false;
    }

    $sum = 0;
    $double = false;

    for ($i = strlen($number) - 1; $i >= 0; $i--) {
        $digit = (int) $number[$i];

        if ($double) {
            $digit *= 2;
            if ($digit > 9) {
                $digit -= 9;
            }
        }

        $sum += $digit;
        $double = !$double;
    }

    return ($sum % 10) === 0;
}

Required Field Validation

4. 输出转义参考

php
// Check if field exists and is not empty
if (empty($_POST['title']) || trim($_POST['title']) === '') {
    $errors[] = 'Title is required';
}

// Alternative: isset() + non-empty check
if (!isset($_POST['terms']) || $_POST['terms'] !== 'accepted') {
    $errors[] = 'You must accept the terms and conditions';
}
转义通过在输出前编码特殊字符来防范XSS(跨站脚本攻击)。这是最后一道安全防线

Pattern Matching (Regex)

核心转义函数

php
$phone = sanitize_text_field($_POST['phone']);

// Validate phone format (US format: (555) 123-4567)
if (!preg_match('/^\(\d{3}\) \d{3}-\d{4}$/', $phone)) {
    $errors[] = 'Phone must be in format: (555) 123-4567';
}

// Validate alphanumeric only
$product_code = sanitize_text_field($_POST['product_code']);
if (!preg_match('/^[a-zA-Z0-9]+$/', $product_code)) {
    $errors[] = 'Product code must contain only letters and numbers';
}
函数上下文转义字符示例用法
esc_html()
HTML内容
< > & " '
echo esc_html($user_input);
esc_attr()
HTML属性
< > & " '
<input value="<?php echo esc_attr($value); ?>">
esc_url()
HTML href/src危险协议
<a href="<?php echo esc_url($link); ?>">
esc_js()
JavaScript字符串
' " \ /
<script>var msg = '<?php echo esc_js($message); ?>';</script>
esc_sql()
已废弃(使用
$wpdb->prepare()
SQL特殊字符❌ 请勿使用
esc_textarea()
文本框内容
< > &
<textarea><?php echo esc_textarea($content); ?></textarea>

Multi-Field Validation

详细转义示例

HTML内容转义

php
function validate_registration_form($data) {
    $errors = [];

    // Email validation
    $email = sanitize_email($data['email']);
    if (!is_email($email)) {
        $errors['email'] = 'Invalid email address';
    } elseif (email_exists($email)) {
        $errors['email'] = 'Email already registered';
    }

    // Username validation
    $username = sanitize_text_field($data['username']);
    if (strlen($username) < 3) {
        $errors['username'] = 'Username too short (minimum 3 characters)';
    } elseif (username_exists($username)) {
        $errors['username'] = 'Username already taken';
    }

    // Password validation
    if (strlen($data['password']) < 8) {
        $errors['password'] = 'Password must be at least 8 characters';
    }

    // Password confirmation
    if ($data['password'] !== $data['password_confirm']) {
        $errors['password_confirm'] = 'Passwords do not match';
    }

    // Age validation
    $age = absint($data['age']);
    if ($age < 18) {
        $errors['age'] = 'You must be 18 or older to register';
    }

    return empty($errors) ? true : $errors;
}

// Usage
$result = validate_registration_form($_POST);
if ($result === true) {
    // Process registration
} else {
    // Display errors
    foreach ($result as $field => $error) {
        echo "<p class='error'>$error</p>";
    }
}
php
// 转义HTML内容(将特殊字符转换为实体)
$user_comment = "<script>alert('XSS')</script>Hello";
echo esc_html($user_comment);
// 输出: &lt;script&gt;alert('XSS')&lt;/script&gt;Hello
// 浏览器显示: <script>alert('XSS')</script>Hello(作为文本,而非代码)

// 错误做法:不进行转义
echo $user_comment; // ⚠️ 会执行JavaScript!

Custom Validation Rules

HTML属性转义

php
// Validate URL is from allowed domain
function validate_allowed_domain($url) {
    $allowed_domains = ['example.com', 'wordpress.org'];
    $host = parse_url($url, PHP_URL_HOST);

    return in_array($host, $allowed_domains);
}

// Validate date format and range
function validate_date($date_string) {
    $date = DateTime::createFromFormat('Y-m-d', $date_string);

    if (!$date) {
        return false; // Invalid format
    }

    // Check date is not in the past
    $now = new DateTime();
    if ($date < $now) {
        return false;
    }

    return true;
}

// Validate credit card (Luhn algorithm)
function validate_credit_card($number) {
    $number = preg_replace('/\D/', '', $number); // Remove non-digits

    if (strlen($number) < 13 || strlen($number) > 19) {
        return false;
    }

    $sum = 0;
    $double = false;

    for ($i = strlen($number) - 1; $i >= 0; $i--) {
        $digit = (int) $number[$i];

        if ($double) {
            $digit *= 2;
            if ($digit > 9) {
                $digit -= 9;
            }
        }

        $sum += $digit;
        $double = !$double;
    }

    return ($sum % 10) === 0;
}

php
// 转义属性值
$title = 'My "Awesome" Title';
?>
<input type="text"
       value="<?php echo esc_attr($title); ?>"
       placeholder="<?php echo esc_attr($placeholder); ?>">
<!-- 输出: value="My &quot;Awesome&quot; Title" -->

<!-- 错误做法:不进行转义 -->
<input value="<?php echo $title; ?>">
<!-- 输出: <input value="My "Awesome" Title">(破坏HTML结构!) -->

4. Output Escaping Reference

URL转义

Escaping prevents XSS (Cross-Site Scripting) by encoding special characters before output. This is the final security layer.
php
// 转义URL(拦截危险协议)
$user_url = "javascript:alert('XSS')";
echo '<a href="' . esc_url($user_url) . '">链接</a>';
// 输出: <a href="">链接</a>(javascript协议被拦截)

// 安全URL
$safe_url = "https://example.com";
echo '<a href="' . esc_url($safe_url) . '">链接</a>';
// 输出: <a href="https://example.com">链接</a>

// 错误做法:不进行转义
echo '<a href="' . $user_url . '">链接</a>'; // ⚠️ 存在XSS漏洞!

Core Escaping Functions

JavaScript转义

FunctionContextEscapesExample Use
esc_html()
HTML content
< > & " '
echo esc_html($user_input);
esc_attr()
HTML attributes
< > & " '
<input value="<?php echo esc_attr($value); ?>">
esc_url()
HTML href/srcDangerous protocols
<a href="<?php echo esc_url($link); ?>">
esc_js()
JavaScript strings
' " \ /
<script>var msg = '<?php echo esc_js($message); ?>';</script>
esc_sql()
DEPRECATED (use
$wpdb->prepare()
)
SQL special chars❌ Don't use
esc_textarea()
Textarea content
< > &
<textarea><?php echo esc_textarea($content); ?></textarea>
php
// 转义JavaScript字符串
$user_message = "It's \"dangerous\" to trust user input";
?>
<script>
    var message = '<?php echo esc_js($user_message); ?>';
    alert(message);
</script>
<!-- 输出: var message = 'It\'s \"dangerous\" to trust user input'; -->

<!-- 错误做法:不进行转义 -->
<script>
    var message = '<?php echo $user_message; ?>'; // ⚠️ 破坏JavaScript结构!
</script>

Detailed Escaping Examples

文本框转义

HTML Content Escaping

php
// Escape HTML content (converts special characters to entities)
$user_comment = "<script>alert('XSS')</script>Hello";
echo esc_html($user_comment);
// Output: &lt;script&gt;alert('XSS')&lt;/script&gt;Hello
// Browser displays: <script>alert('XSS')</script>Hello (as text, not code)

// WRONG: No escaping
echo $user_comment; // ⚠️ Executes JavaScript!
php
// 转义文本框内容
$bio = "Line 1\nLine 2 <script>alert('XSS')</script>";
?>
<textarea><?php echo esc_textarea($bio); ?></textarea>
<!-- 输出保留换行符,转义HTML -->

<!-- 错误做法:在文本框中使用esc_html() -->
<textarea><?php echo esc_html($bio); ?></textarea>
<!-- ⚠️ 换行符被转换为&lt;br&gt;(无法正确显示) -->

HTML Attribute Escaping

上下文特定转义

HTML上下文

php
// Escape attribute values
$title = 'My "Awesome" Title';
?>
<input type="text"
       value="<?php echo esc_attr($title); ?>"
       placeholder="<?php echo esc_attr($placeholder); ?>">
<!-- Output: value="My &quot;Awesome&quot; Title" -->

<!-- WRONG: No escaping -->
<input value="<?php echo $title; ?>">
<!-- Output: <input value="My "Awesome" Title"> (breaks HTML!) -->
php
// 段落内容
echo '<p>' . esc_html($user_content) . '</p>';

// 链接文本
echo '<a href="' . esc_url($url) . '">' . esc_html($link_text) . '</a>';

// 图片替代文本
echo '<img src="' . esc_url($image_url) . '" alt="' . esc_attr($alt_text) . '">';

URL Escaping

属性上下文

php
// Escape URLs (blocks dangerous protocols)
$user_url = "javascript:alert('XSS')";
echo '<a href="' . esc_url($user_url) . '">Link</a>';
// Output: <a href="">Link</a> (javascript: protocol blocked)

// Safe URL
$safe_url = "https://example.com";
echo '<a href="' . esc_url($safe_url) . '">Link</a>';
// Output: <a href="https://example.com">Link</a>

// WRONG: No escaping
echo '<a href="' . $user_url . '">Link</a>'; // ⚠️ XSS vulnerability!
php
// 数据属性
echo '<div data-user-id="' . esc_attr($user_id) . '"
           data-username="' . esc_attr($username) . '"></div>';

// 类名(使用sanitize_html_class)
echo '<div class="' . esc_attr(sanitize_html_class($class)) . '"></div>';

// 样式属性(危险 - 尽量避免使用)
$safe_color = sanitize_hex_color($user_color); // 先验证
echo '<div style="color: ' . esc_attr($safe_color) . ';"></div>';

JavaScript Escaping

JavaScript上下文

php
// Escape JavaScript strings
$user_message = "It's \"dangerous\" to trust user input";
?>
<script>
    var message = '<?php echo esc_js($user_message); ?>';
    alert(message);
</script>
<!-- Output: var message = 'It\'s \"dangerous\" to trust user input'; -->

<!-- WRONG: No escaping -->
<script>
    var message = '<?php echo $user_message; ?>'; // ⚠️ Breaks JavaScript!
</script>
php
// 内联JavaScript(尽量避免,优先使用wp_localize_script)
<script>
    var config = {
        username: '<?php echo esc_js($username); ?>',
        apiUrl: '<?php echo esc_js(admin_url('admin-ajax.php')); ?>'
    };
</script>

// 更好的方式:使用wp_localize_script
wp_localize_script('my-script', 'myConfig', [
    'username' => $username, // 自动进行JSON编码
    'apiUrl' => admin_url('admin-ajax.php'),
]);

Textarea Escaping

国际化 + 转义

php
// Escape textarea content
$bio = "Line 1\nLine 2 <script>alert('XSS')</script>";
?>
<textarea><?php echo esc_textarea($bio); ?></textarea>
<!-- Output preserves line breaks, escapes HTML -->

<!-- WRONG: Using esc_html() in textarea -->
<textarea><?php echo esc_html($bio); ?></textarea>
<!-- ⚠️ Line breaks converted to &lt;br&gt; (not displayed correctly) -->
php
// 翻译并转义
echo esc_html__('Welcome User', 'my-plugin');

// 带变量的翻译,然后转义
$message = sprintf(
    __('Hello %s, you have %d new messages', 'my-plugin'),
    esc_html($username),
    absint($message_count)
);
echo $message;

// 转义可翻译的属性
<input placeholder="<?php echo esc_attr__('Enter your name', 'my-plugin'); ?>">

// 允许翻译内容中包含HTML(使用wp_kses_post)
$welcome_html = __('Welcome to <strong>My Plugin</strong>!', 'my-plugin');
echo wp_kses_post($welcome_html);

Context-Specific Escaping

常见转义错误

HTML Context

php
// Paragraph content
echo '<p>' . esc_html($user_content) . '</p>';

// Link text
echo '<a href="' . esc_url($url) . '">' . esc_html($link_text) . '</a>';

// Image alt text
echo '<img src="' . esc_url($image_url) . '" alt="' . esc_attr($alt_text) . '">';
错误做法:
php
// 双重转义(向用户显示HTML实体)
echo esc_html(esc_html($content)); // ⚠️ 显示&amp;lt;script&amp;gt;

// 上下文使用错误的函数
echo '<a href="' . esc_html($url) . '">链接</a>'; // ⚠️ 应使用esc_url()

// JavaScript中不转义
echo "<script>var x = '$user_input';</script>"; // ⚠️ 应使用esc_js()

// 存储前转义(应存储原始数据,输出时转义)
update_option('setting', esc_html($value)); // ⚠️ 输出时转义,而非存储时
正确做法:
php
// 仅转义一次,在输出时
echo esc_html($content);

// 为上下文使用正确的函数
echo '<a href="' . esc_url($url) . '">' . esc_html($text) . '</a>';

// 正确转义JavaScript
wp_localize_script('script', 'data', ['value' => $user_input]);

// 存储原始数据,输出时转义
update_option('setting', $value); // 存储原始数据
echo esc_html(get_option('setting')); // 输出时转义

Attribute Context

5. 权限验证(授权)

php
// Data attributes
echo '<div data-user-id="' . esc_attr($user_id) . '"
           data-username="' . esc_attr($username) . '"></div>';

// Class names (use sanitize_html_class)
echo '<div class="' . esc_attr(sanitize_html_class($class)) . '"></div>';

// Style attribute (dangerous - avoid if possible)
$safe_color = sanitize_hex_color($user_color); // Validate first
echo '<div style="color: ' . esc_attr($safe_color) . ';"></div>';
权限验证确保用户拥有执行操作的权限。始终与nonce验证结合使用。

JavaScript Context

内置权限

php
// Inline JavaScript (avoid if possible, use wp_localize_script instead)
<script>
    var config = {
        username: '<?php echo esc_js($username); ?>',
        apiUrl: '<?php echo esc_js(admin_url('admin-ajax.php')); ?>'
    };
</script>

// BETTER: Use wp_localize_script
wp_localize_script('my-script', 'myConfig', [
    'username' => $username, // Automatically JSON-encoded
    'apiUrl' => admin_url('admin-ajax.php'),
]);
权限描述默认角色
read
查看内容所有已登录用户
edit_posts
创建/编辑自己的文章作者、编辑、管理员
edit_published_posts
编辑已发布的文章编辑、管理员
delete_posts
删除自己的文章作者、编辑、管理员
manage_options
管理站点设置仅管理员
upload_files
上传媒体文件作者、编辑、管理员
edit_users
编辑用户账户仅管理员
delete_users
删除用户仅管理员
install_plugins
安装/激活插件仅管理员
switch_themes
更换主题仅管理员

Internationalization + Escaping

权限验证模式

基础权限验证

php
// Translate and escape
echo esc_html__('Welcome User', 'my-plugin');

// Translate with variable, then escape
$message = sprintf(
    __('Hello %s, you have %d new messages', 'my-plugin'),
    esc_html($username),
    absint($message_count)
);
echo $message;

// Escape translatable attributes
<input placeholder="<?php echo esc_attr__('Enter your name', 'my-plugin'); ?>">

// Allow HTML in translations (use wp_kses_post)
$welcome_html = __('Welcome to <strong>My Plugin</strong>!', 'my-plugin');
echo wp_kses_post($welcome_html);
php
// 检查用户是否已登录
if (!is_user_logged_in()) {
    wp_die('你必须登录才能访问此页面');
}

// 检查用户是否拥有权限
if (!current_user_can('manage_options')) {
    wp_die('你没有管理设置的权限');
}

// 检查用户是否可以编辑特定文章
$post_id = absint($_GET['post_id']);
if (!current_user_can('edit_post', $post_id)) {
    wp_die('你无法编辑这篇文章');
}

Common Escaping Mistakes

完整安全示例

WRONG:
php
// Double-escaping (displays HTML entities to user)
echo esc_html(esc_html($content)); // ⚠️ Displays &amp;lt;script&amp;gt;

// Wrong function for context
echo '<a href="' . esc_html($url) . '">Link</a>'; // ⚠️ Use esc_url()

// No escaping in JavaScript
echo "<script>var x = '$user_input';</script>"; // ⚠️ Use esc_js()

// Escaping before storage (store raw, escape on output)
update_option('setting', esc_html($value)); // ⚠️ Escape on output, not input
CORRECT:
php
// Escape once, on output
echo esc_html($content);

// Use correct function for context
echo '<a href="' . esc_url($url) . '">' . esc_html($text) . '</a>';

// Escape JavaScript properly
wp_localize_script('script', 'data', ['value' => $user_input]);

// Store raw, escape on output
update_option('setting', $value); // Store raw
echo esc_html(get_option('setting')); // Escape on output

php
add_action('admin_post_update_settings', 'handle_settings_update');
function handle_settings_update() {
    // 1. 检查用户是否已登录
    if (!is_user_logged_in()) {
        wp_die('你必须登录');
    }

    // 2. 验证nonce
    if (!isset($_POST['settings_nonce']) ||
        !wp_verify_nonce($_POST['settings_nonce'], 'update_settings')) {
        wp_die('安全检查失败');
    }

    // 3. 检查用户权限
    if (!current_user_can('manage_options')) {
        wp_die('你没有更新设置的权限');
    }

    // 4. 清理输入
    $api_key = sanitize_text_field($_POST['api_key']);
    $enable_feature = isset($_POST['enable_feature']) ? 1 : 0;

    // 5. 验证数据
    if (strlen($api_key) < 10) {
        wp_die('API密钥至少需要10个字符');
    }

    // 6. 更新选项
    update_option('my_plugin_api_key', $api_key);
    update_option('my_plugin_enable_feature', $enable_feature);

    // 7. 安全重定向
    wp_redirect(add_query_arg('message', 'updated', wp_get_referer()));
    exit;
}

5. Capability Checks (Authorization)

文章特定权限

Capability checks ensure users have permission to perform actions. Always combine with nonce verification.
php
// 检查用户是否可以编辑特定文章
$post_id = absint($_POST['post_id']);
if (!current_user_can('edit_post', $post_id)) {
    wp_send_json_error(['message' => '你无法编辑这篇文章']);
}

// 检查用户是否可以删除特定文章
if (!current_user_can('delete_post', $post_id)) {
    wp_send_json_error(['message' => '你无法删除这篇文章']);
}

// 检查用户是否可以发布文章
if (!current_user_can('publish_posts')) {
    wp_send_json_error(['message' => '你无法发布文章']);
}

Built-in Capabilities

自定义权限

CapabilityDescriptionDefault Roles
read
View contentAll logged-in users
edit_posts
Create/edit own postsAuthor, Editor, Admin
edit_published_posts
Edit published postsEditor, Admin
delete_posts
Delete own postsAuthor, Editor, Admin
manage_options
Manage site settingsAdmin only
upload_files
Upload mediaAuthor, Editor, Admin
edit_users
Edit user accountsAdmin only
delete_users
Delete usersAdmin only
install_plugins
Install/activate pluginsAdmin only
switch_themes
Change themesAdmin only
php
// 注册带自定义权限的角色
add_action('init', 'register_custom_role');
function register_custom_role() {
    add_role('store_manager', '店铺管理员', [
        'read' => true,
        'edit_posts' => true,
        'manage_products' => true, // 自定义权限
    ]);
}

// 为现有角色添加自定义权限
$role = get_role('editor');
$role->add_cap('manage_products');

// 检查自定义权限
if (current_user_can('manage_products')) {
    // 允许产品管理操作
}

Capability Check Patterns

6. SQL注入防范

Basic Capability Check

php
// Check if user is logged in
if (!is_user_logged_in()) {
    wp_die('You must be logged in to access this page');
}

// Check if user has capability
if (!current_user_can('manage_options')) {
    wp_die('You do not have permission to manage settings');
}

// Check if user can edit specific post
$post_id = absint($_GET['post_id']);
if (!current_user_can('edit_post', $post_id)) {
    wp_die('You cannot edit this post');
}
关键提示: 永远不要在SQL查询中信任用户输入。始终使用
$wpdb->prepare()

Complete Security Example

问题:SQL注入

php
add_action('admin_post_update_settings', 'handle_settings_update');
function handle_settings_update() {
    // 1. Check if user is logged in
    if (!is_user_logged_in()) {
        wp_die('You must be logged in');
    }

    // 2. Verify nonce
    if (!isset($_POST['settings_nonce']) ||
        !wp_verify_nonce($_POST['settings_nonce'], 'update_settings')) {
        wp_die('Security check failed');
    }

    // 3. Check user capability
    if (!current_user_can('manage_options')) {
        wp_die('You do not have permission to update settings');
    }

    // 4. Sanitize input
    $api_key = sanitize_text_field($_POST['api_key']);
    $enable_feature = isset($_POST['enable_feature']) ? 1 : 0;

    // 5. Validate data
    if (strlen($api_key) < 10) {
        wp_die('API key must be at least 10 characters');
    }

    // 6. Update options
    update_option('my_plugin_api_key', $api_key);
    update_option('my_plugin_enable_feature', $enable_feature);

    // 7. Redirect with success message
    wp_redirect(add_query_arg('message', 'updated', wp_get_referer()));
    exit;
}
之前(存在漏洞):
php
global $wpdb;

// ⚠️ 严重漏洞 - SQL注入!
$user_id = $_GET['user_id'];
$results = $wpdb->get_results(
    "SELECT * FROM {$wpdb->posts} WHERE post_author = $user_id"
);

// 攻击者可以注入SQL:
// ?user_id=1 OR 1=1 --(返回所有文章)
// ?user_id=1; DROP TABLE wp_posts; --(删除表!)
之后(安全版本):
php
global $wpdb;

// ✅ 安全版本 - 使用预处理语句
$user_id = absint($_GET['user_id']); // 先清理
$results = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM {$wpdb->posts} WHERE post_author = %d",
        $user_id
    )
);

Post-Specific Capabilities

预处理语句占位符

php
// Check if user can edit specific post
$post_id = absint($_POST['post_id']);
if (!current_user_can('edit_post', $post_id)) {
    wp_send_json_error(['message' => 'You cannot edit this post']);
}

// Check if user can delete specific post
if (!current_user_can('delete_post', $post_id)) {
    wp_send_json_error(['message' => 'You cannot delete this post']);
}

// Check if user can publish posts
if (!current_user_can('publish_posts')) {
    wp_send_json_error(['message' => 'You cannot publish posts']);
}
占位符类型示例
%s
字符串
"SELECT * FROM table WHERE name = %s"
%d
整数
"SELECT * FROM table WHERE id = %d"
%f
浮点数
"SELECT * FROM table WHERE price = %f"

Custom Capabilities

完整示例

SELECT查询

php
// Register custom role with custom capability
add_action('init', 'register_custom_role');
function register_custom_role() {
    add_role('store_manager', 'Store Manager', [
        'read' => true,
        'edit_posts' => true,
        'manage_products' => true, // Custom capability
    ]);
}

// Add custom capability to existing role
$role = get_role('editor');
$role->add_cap('manage_products');

// Check custom capability
if (current_user_can('manage_products')) {
    // Allow product management
}

php
global $wpdb;

$email = sanitize_email($_POST['email']);

// 预处理语句(防范SQL注入)
$user = $wpdb->get_row(
    $wpdb->prepare(
        "SELECT * FROM {$wpdb->users} WHERE user_email = %s",
        $email
    )
);

if ($user) {
    echo "找到用户:" . esc_html($user->user_login);
}

6. SQL Injection Prevention

INSERT查询

CRITICAL: Never trust user input in SQL queries. Always use
$wpdb->prepare()
.
php
global $wpdb;

// 使用wpdb->insert()(自动进行预处理)
$result = $wpdb->insert(
    $wpdb->prefix . 'my_table',
    [
        'title' => sanitize_text_field($_POST['title']),
        'content' => wp_kses_post($_POST['content']),
        'user_id' => absint($_POST['user_id']),
        'price' => floatval($_POST['price']),
        'created_at' => current_time('mysql'),
    ],
    ['%s', '%s', '%d', '%f', '%s'] // 格式说明符
);

if ($result === false) {
    wp_die('数据库插入失败:' . $wpdb->last_error);
}

$inserted_id = $wpdb->insert_id;

The Problem: SQL Injection

UPDATE查询

BEFORE (Vulnerable):
php
global $wpdb;

// ⚠️ CRITICAL VULNERABILITY - SQL INJECTION!
$user_id = $_GET['user_id'];
$results = $wpdb->get_results(
    "SELECT * FROM {$wpdb->posts} WHERE post_author = $user_id"
);

// Attacker can inject SQL:
// ?user_id=1 OR 1=1 -- (returns all posts)
// ?user_id=1; DROP TABLE wp_posts; -- (deletes table!)
AFTER (Secure):
php
global $wpdb;

// ✅ SECURE - Using prepared statements
$user_id = absint($_GET['user_id']); // Sanitize first
$results = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM {$wpdb->posts} WHERE post_author = %d",
        $user_id
    )
);
php
global $wpdb;

$wpdb->update(
    $wpdb->prefix . 'my_table',
    [
        'title' => sanitize_text_field($_POST['title']), // 新值
        'updated_at' => current_time('mysql'),
    ],
    ['id' => absint($_POST['id'])], // WHERE条件
    ['%s', '%s'], // 新值的格式
    ['%d']        // WHERE条件的格式
);

Prepared Statement Placeholders

DELETE查询

PlaceholderTypeExample
%s
String
"SELECT * FROM table WHERE name = %s"
%d
Integer
"SELECT * FROM table WHERE id = %d"
%f
Float
"SELECT * FROM table WHERE price = %f"
php
global $wpdb;

$wpdb->delete(
    $wpdb->prefix . 'my_table',
    ['id' => absint($_POST['id'])],
    ['%d']
);

Complete Examples

复杂WHERE子句

SELECT Query

php
global $wpdb;

$email = sanitize_email($_POST['email']);

// Prepared statement (prevents SQL injection)
$user = $wpdb->get_row(
    $wpdb->prepare(
        "SELECT * FROM {$wpdb->users} WHERE user_email = %s",
        $email
    )
);

if ($user) {
    echo "User found: " . esc_html($user->user_login);
}
php
global $wpdb;

$status = sanitize_text_field($_POST['status']);
$min_price = floatval($_POST['min_price']);

// 多个占位符
$results = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM {$wpdb->prefix}products
         WHERE status = %s AND price >= %f
         ORDER BY created_at DESC
         LIMIT %d",
        $status,
        $min_price,
        10 // 限制数量
    )
);

INSERT Query

常见SQL注入错误

php
global $wpdb;

// Use wpdb->insert() (automatically prepares)
$result = $wpdb->insert(
    $wpdb->prefix . 'my_table',
    [
        'title' => sanitize_text_field($_POST['title']),
        'content' => wp_kses_post($_POST['content']),
        'user_id' => absint($_POST['user_id']),
        'price' => floatval($_POST['price']),
        'created_at' => current_time('mysql'),
    ],
    ['%s', '%s', '%d', '%f', '%s'] // Format specifiers
);

if ($result === false) {
    wp_die('Database insert failed: ' . $wpdb->last_error);
}

$inserted_id = $wpdb->insert_id;
错误做法:
php
// 字符串拼接(存在漏洞!)
$sql = "SELECT * FROM table WHERE name = '" . $_POST['name'] . "'";

// 使用esc_sql()(已废弃且不足够)
$sql = "SELECT * FROM table WHERE name = '" . esc_sql($_POST['name']) . "'";

// 不使用占位符
$wpdb->query("DELETE FROM table WHERE id = $id"); // ⚠️ 存在漏洞
正确做法:
php
// 始终使用$wpdb->prepare()
$wpdb->get_results($wpdb->prepare(
    "SELECT * FROM table WHERE name = %s",
    $_POST['name']
));

// 使用wpdb方法(insert、update、delete)
$wpdb->insert('table', ['name' => $_POST['name']], ['%s']);

UPDATE Query

7. 常见漏洞与攻击场景

XSS(跨站脚本攻击)

php
global $wpdb;

$wpdb->update(
    $wpdb->prefix . 'my_table',
    [
        'title' => sanitize_text_field($_POST['title']), // New values
        'updated_at' => current_time('mysql'),
    ],
    ['id' => absint($_POST['id'])], // WHERE condition
    ['%s', '%s'], // Format for new values
    ['%d']        // Format for WHERE condition
);
攻击场景:
php
// 存在漏洞的代码
echo "Welcome, " . $_GET['username'];
// 攻击者访问:?username=<script>alert(document.cookie)</script>
// 浏览器执行JavaScript,窃取会话Cookie
防范措施:
php
// 转义输出
echo "欢迎," . esc_html($_GET['username']);
// 输出: 欢迎,&lt;script&gt;alert(document.cookie)&lt;/script&gt;

DELETE Query

CSRF(跨站请求伪造)

php
global $wpdb;

$wpdb->delete(
    $wpdb->prefix . 'my_table',
    ['id' => absint($_POST['id'])],
    ['%d']
);
攻击场景:
html
<!-- 攻击者的站点(evil.com) -->
<img src="https://yoursite.com/wp-admin/admin.php?action=delete_all_posts">
<!-- 如果管理员已登录,该请求会在用户不知情的情况下执行! -->
防范措施:
php
// 要求nonce验证
if (!wp_verify_nonce($_GET['nonce'], 'delete_all_posts')) {
    wp_die('无效的安全令牌');
}

Complex WHERE Clause

SQL注入

php
global $wpdb;

$status = sanitize_text_field($_POST['status']);
$min_price = floatval($_POST['min_price']);

// Multiple placeholders
$results = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM {$wpdb->prefix}products
         WHERE status = %s AND price >= %f
         ORDER BY created_at DESC
         LIMIT %d",
        $status,
        $min_price,
        10 // LIMIT value
    )
);
攻击场景:
php
// 存在漏洞的代码
$wpdb->query("DELETE FROM posts WHERE id = " . $_GET['id']);
// 攻击者访问:?id=1 OR 1=1
// 删除所有文章!
防范措施:
php
// 使用预处理语句
$wpdb->query($wpdb->prepare(
    "DELETE FROM posts WHERE id = %d",
    absint($_GET['id'])
));

Common SQL Injection Mistakes

文件上传攻击

WRONG:
php
// String concatenation (vulnerable!)
$sql = "SELECT * FROM table WHERE name = '" . $_POST['name'] . "'";

// Using esc_sql() (deprecated and insufficient)
$sql = "SELECT * FROM table WHERE name = '" . esc_sql($_POST['name']) . "'";

// Not using placeholders
$wpdb->query("DELETE FROM table WHERE id = $id"); // ⚠️ Vulnerable
CORRECT:
php
// Always use $wpdb->prepare()
$wpdb->get_results($wpdb->prepare(
    "SELECT * FROM table WHERE name = %s",
    $_POST['name']
));

// Use wpdb methods (insert, update, delete)
$wpdb->insert('table', ['name' => $_POST['name']], ['%s']);

攻击场景:
php
// 存在漏洞的代码
move_uploaded_file($_FILES['file']['tmp_name'], 'uploads/' . $_FILES['file']['name']);
// 攻击者上传:malicious.php
// 执行:https://yoursite.com/uploads/malicious.php
防范措施:
php
// 验证文件类型并使用wp_handle_upload()
$allowed_types = ['image/jpeg', 'image/png'];
if (!in_array($_FILES['file']['type'], $allowed_types)) {
    wp_die('无效的文件类型');
}

$upload = wp_handle_upload($_FILES['file'], ['test_form' => false]);

7. Common Vulnerabilities & Attack Scenarios

路径遍历

XSS (Cross-Site Scripting)

Attack Scenario:
php
// Vulnerable code
echo "Welcome, " . $_GET['username'];
// Attacker visits: ?username=<script>alert(document.cookie)</script>
// Browser executes JavaScript, stealing session cookies
Prevention:
php
// Escape output
echo "Welcome, " . esc_html($_GET['username']);
// Output: Welcome, &lt;script&gt;alert(document.cookie)&lt;/script&gt;
攻击场景:
php
// 存在漏洞的代码
include($_GET['template'] . '.php');
// 攻击者访问:?template=../../../../etc/passwd
防范措施:
php
// 白名单允许的模板
$allowed_templates = ['template1', 'template2'];
$template = sanitize_file_name($_GET['template']);

if (in_array($template, $allowed_templates)) {
    include($template . '.php');
}

CSRF (Cross-Site Request Forgery)

8. 完整安全实现示例

Attack Scenario:
html
<!-- Attacker's site (evil.com) -->
<img src="https://yoursite.com/wp-admin/admin.php?action=delete_all_posts">
<!-- If admin is logged in, this executes without their knowledge! -->
Prevention:
php
// Require nonce verification
if (!wp_verify_nonce($_GET['nonce'], 'delete_all_posts')) {
    wp_die('Invalid security token');
}
php
<?php
/**
 * 安全表单处理示例
 * 演示所有安全层:nonce、清理、验证、转义
 */

// 1. 显示表单(带nonce)
function display_user_profile_form() {
    $user_id = get_current_user_id();
    $user_data = get_user_meta($user_id, 'profile_data', true);

    ?>
    <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
        <input type="hidden" name="action" value="update_user_profile">
        <?php wp_nonce_field('update_profile_' . $user_id, 'profile_nonce'); ?>

        <label>
            显示名称:
            <input type="text"
                   name="display_name"
                   value="<?php echo esc_attr($user_data['display_name'] ?? ''); ?>"
                   required>
        </label>

        <label>
            邮箱:
            <input type="email"
                   name="email"
                   value="<?php echo esc_attr($user_data['email'] ?? ''); ?>"
                   required>
        </label>

        <label>
            个人简介:
            <textarea name="bio"><?php echo esc_textarea($user_data['bio'] ?? ''); ?></textarea>
        </label>

        <label>
            网站:
            <input type="url"
                   name="website"
                   value="<?php echo esc_attr($user_data['website'] ?? ''); ?>">
        </label>

        <button type="submit">更新个人资料</button>
    </form>
    <?php
}

// 2. 处理表单(完整安全流程)
add_action('admin_post_update_user_profile', 'handle_profile_update');
function handle_profile_update() {
    // 安全层1:身份验证
    if (!is_user_logged_in()) {
        wp_die('你必须登录才能更新个人资料');
    }

    $user_id = get_current_user_id();

    // 安全层2:Nonce验证
    if (!isset($_POST['profile_nonce']) ||
        !wp_verify_nonce($_POST['profile_nonce'], 'update_profile_' . $user_id)) {
        wp_die('安全检查失败:无效的nonce');
    }

    // 安全层3:权限验证
    if (!current_user_can('edit_user', $user_id)) {
        wp_die('你没有更新此个人资料的权限');
    }

    // 安全层4:输入清理
    $display_name = sanitize_text_field($_POST['display_name']);
    $email = sanitize_email($_POST['email']);
    $bio = sanitize_textarea_field($_POST['bio']);
    $website = esc_url_raw($_POST['website']);

    // 安全层5:数据验证
    $errors = [];

    if (empty($display_name) || strlen($display_name) < 3) {
        $errors[] = '显示名称至少需要3个字符';
    }

    if (!is_email($email)) {
        $errors[] = '无效的邮箱地址';
    }

    if (!empty($website) && !filter_var($website, FILTER_VALIDATE_URL)) {
        $errors[] = '无效的网站URL';
    }

    if (!empty($errors)) {
        wp_die(implode('<br>', array_map('esc_html', $errors)));
    }

    // 安全层6:数据处理
    $profile_data = [
        'display_name' => $display_name,
        'email' => $email,
        'bio' => $bio,
        'website' => $website,
    ];

    update_user_meta($user_id, 'profile_data', $profile_data);

    // 安全层7:安全重定向
    wp_redirect(add_query_arg('message', 'profile_updated', wp_get_referer()));
    exit;
}

// 3. 显示成功消息(带转义)
add_action('admin_notices', 'show_profile_update_notice');
function show_profile_update_notice() {
    if (isset($_GET['message']) && $_GET['message'] === 'profile_updated') {
        echo '<div class="notice notice-success is-dismissible">';
        echo '<p>' . esc_html__('个人资料更新成功!', 'my-plugin') . '</p>';
        echo '</div>';
    }
}

SQL Injection

9. 安全检查表

Attack Scenario:
php
// Vulnerable code
$wpdb->query("DELETE FROM posts WHERE id = " . $_GET['id']);
// Attacker visits: ?id=1 OR 1=1
// Deletes ALL posts!
Prevention:
php
// Use prepared statements
$wpdb->query($wpdb->prepare(
    "DELETE FROM posts WHERE id = %d",
    absint($_GET['id'])
));
在实现每个WordPress功能时,请使用此检查表:

File Upload Attack

输入安全(表单、AJAX、API)

Attack Scenario:
php
// Vulnerable code
move_uploaded_file($_FILES['file']['tmp_name'], 'uploads/' . $_FILES['file']['name']);
// Attacker uploads: malicious.php
// Executes: https://yoursite.com/uploads/malicious.php
Prevention:
php
// Validate file type and use wp_handle_upload()
$allowed_types = ['image/jpeg', 'image/png'];
if (!in_array($_FILES['file']['type'], $allowed_types)) {
    wp_die('Invalid file type');
}

$upload = wp_handle_upload($_FILES['file'], ['test_form' => false]);
  • 已实现Nonce验证(
    wp_verify_nonce()
  • 已执行权限检查(
    current_user_can()
  • 所有输入已使用合适的函数清理
  • 所有输入已针对业务逻辑进行验证
  • 文件上传使用
    wp_handle_upload()
  • 文件类型使用白名单,而非黑名单

Path Traversal

输出安全(模板、API)

Attack Scenario:
php
// Vulnerable code
include($_GET['template'] . '.php');
// Attacker visits: ?template=../../../../etc/passwd
Prevention:
php
// Whitelist allowed templates
$allowed_templates = ['template1', 'template2'];
$template = sanitize_file_name($_GET['template']);

if (in_array($template, $allowed_templates)) {
    include($template . '.php');
}

  • 所有动态内容已使用
    esc_html()
    esc_attr()
    等进行转义
  • URL已使用
    esc_url()
    转义
  • JavaScript变量使用
    wp_localize_script()
    esc_js()
  • 没有直接
    echo $_POST
    echo $_GET
    的代码

8. Complete Security Implementation Example

数据库安全

php
<?php
/**
 * Secure Form Handling Example
 * Demonstrates all security layers: nonces, sanitization, validation, escaping
 */

// 1. Display Form (with nonce)
function display_user_profile_form() {
    $user_id = get_current_user_id();
    $user_data = get_user_meta($user_id, 'profile_data', true);

    ?>
    <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
        <input type="hidden" name="action" value="update_user_profile">
        <?php wp_nonce_field('update_profile_' . $user_id, 'profile_nonce'); ?>

        <label>
            Display Name:
            <input type="text"
                   name="display_name"
                   value="<?php echo esc_attr($user_data['display_name'] ?? ''); ?>"
                   required>
        </label>

        <label>
            Email:
            <input type="email"
                   name="email"
                   value="<?php echo esc_attr($user_data['email'] ?? ''); ?>"
                   required>
        </label>

        <label>
            Bio:
            <textarea name="bio"><?php echo esc_textarea($user_data['bio'] ?? ''); ?></textarea>
        </label>

        <label>
            Website:
            <input type="url"
                   name="website"
                   value="<?php echo esc_attr($user_data['website'] ?? ''); ?>">
        </label>

        <button type="submit">Update Profile</button>
    </form>
    <?php
}

// 2. Process Form (with full security)
add_action('admin_post_update_user_profile', 'handle_profile_update');
function handle_profile_update() {
    // SECURITY LAYER 1: Authentication
    if (!is_user_logged_in()) {
        wp_die('You must be logged in to update your profile');
    }

    $user_id = get_current_user_id();

    // SECURITY LAYER 2: Nonce Verification
    if (!isset($_POST['profile_nonce']) ||
        !wp_verify_nonce($_POST['profile_nonce'], 'update_profile_' . $user_id)) {
        wp_die('Security check failed: Invalid nonce');
    }

    // SECURITY LAYER 3: Capability Check
    if (!current_user_can('edit_user', $user_id)) {
        wp_die('You do not have permission to update this profile');
    }

    // SECURITY LAYER 4: Sanitization
    $display_name = sanitize_text_field($_POST['display_name']);
    $email = sanitize_email($_POST['email']);
    $bio = sanitize_textarea_field($_POST['bio']);
    $website = esc_url_raw($_POST['website']);

    // SECURITY LAYER 5: Validation
    $errors = [];

    if (empty($display_name) || strlen($display_name) < 3) {
        $errors[] = 'Display name must be at least 3 characters';
    }

    if (!is_email($email)) {
        $errors[] = 'Invalid email address';
    }

    if (!empty($website) && !filter_var($website, FILTER_VALIDATE_URL)) {
        $errors[] = 'Invalid website URL';
    }

    if (!empty($errors)) {
        wp_die(implode('<br>', array_map('esc_html', $errors)));
    }

    // SECURITY LAYER 6: Process Data
    $profile_data = [
        'display_name' => $display_name,
        'email' => $email,
        'bio' => $bio,
        'website' => $website,
    ];

    update_user_meta($user_id, 'profile_data', $profile_data);

    // SECURITY LAYER 7: Safe Redirect
    wp_redirect(add_query_arg('message', 'profile_updated', wp_get_referer()));
    exit;
}

// 3. Display Success Message (with escaping)
add_action('admin_notices', 'show_profile_update_notice');
function show_profile_update_notice() {
    if (isset($_GET['message']) && $_GET['message'] === 'profile_updated') {
        echo '<div class="notice notice-success is-dismissible">';
        echo '<p>' . esc_html__('Profile updated successfully!', 'my-plugin') . '</p>';
        echo '</div>';
    }
}

  • 所有查询使用
    $wpdb->prepare()
  • SQL中没有字符串拼接
  • 使用
    $wpdb->insert()
    $wpdb->update()
    $wpdb->delete()
  • 表名使用
    $wpdb->prefix

9. Security Checklist

会话安全

Use this checklist for every WordPress feature you implement:
  • 已检查用户身份验证(
    is_user_logged_in()
  • 已验证用户角色(
    current_user_can()
  • 敏感操作需要重新验证
  • 会话数据从未存储在GET参数中

Input Security (Forms, AJAX, APIs)

代码质量

  • Nonce verification implemented (
    wp_verify_nonce()
    )
  • Capability check performed (
    current_user_can()
    )
  • All input sanitized with appropriate functions
  • All input validated for business logic
  • File uploads use
    wp_handle_upload()
  • File types whitelisted, not blacklisted
  • 没有使用
    eval()
    assert()
    create_function()
  • 没有对用户输入使用
    extract()
  • 错误消息不泄露系统信息
  • 生产环境中已禁用调试模式(
    WP_DEBUG = false

Output Security (Templates, APIs)

10. 测试你的安全实现

手动测试检查表

  • All dynamic content escaped with
    esc_html()
    ,
    esc_attr()
    , etc.
  • URLs escaped with
    esc_url()
  • JavaScript variables use
    wp_localize_script()
    or
    esc_js()
  • No raw
    echo $_POST
    or
    echo $_GET
1. 测试Nonce过期:
bash
undefined

Database Security

生成带nonce的表单,等待25小时后提交

预期结果:显示“安全检查失败”错误

  • All queries use
    $wpdb->prepare()
  • No string concatenation in SQL
  • Use
    $wpdb->insert()
    ,
    $wpdb->update()
    ,
    $wpdb->delete()
  • Table names use
    $wpdb->prefix

**2. 测试CSRF防护:**
```html
<!-- 创建指向你的站点的外部表单 -->
<form action="https://yoursite.com/wp-admin/admin-post.php" method="POST">
    <input name="action" value="your_action">
    <button>提交</button>
</form>
<!-- 预期结果:Nonce验证失败 -->
3. 测试XSS防范:
输入:<script>alert('XSS')</script>
预期输出:&lt;script&gt;alert('XSS')&lt;/script&gt;(作为文本显示)
4. 测试SQL注入:
输入:1 OR 1=1
预期结果:将其视为字面字符串,不执行SQL语句
5. 测试权限绕过:
php
// 以订阅者(低权限用户)身份登录
// 尝试访问仅管理员可用的功能
// 预期结果:显示“你没有权限”错误

Session Security

自动化安全测试

  • User authentication checked (
    is_user_logged_in()
    )
  • User roles validated (
    current_user_can()
    )
  • Sensitive operations require re-authentication
  • Session data never stored in GET parameters
安装安全扫描工具:
bash
undefined

Code Quality

WPScan(CLI工具)

  • No
    eval()
    ,
    assert()
    , or
    create_function()
  • No
    extract()
    on user input
  • Error messages don't reveal system information
  • Debug mode disabled in production (
    WP_DEBUG = false
    )

gem install wpscan wpscan --url https://yoursite.com --enumerate vp

10. Testing Your Security Implementation

Sucuri安全插件

Manual Testing Checklist

1. Test Nonce Expiration:
bash
undefined
wp plugin install sucuri-scanner --activate

**运行PHP代码嗅探器:**
```bash

Generate form with nonce, wait 25 hours, submit

检查安全问题

Expected: "Security check failed" error


**2. Test CSRF Protection:**
```html
<!-- Create external form pointing to your site -->
<form action="https://yoursite.com/wp-admin/admin-post.php" method="POST">
    <input name="action" value="your_action">
    <button>Submit</button>
</form>
<!-- Expected: Nonce verification fails -->
3. Test XSS Prevention:
Input: <script>alert('XSS')</script>
Expected Output: &lt;script&gt;alert('XSS')&lt;/script&gt; (as text)
4. Test SQL Injection:
Input: 1 OR 1=1
Expected: Treats as literal string, no SQL execution
5. Test Capability Bypass:
php
// Log in as subscriber (low-privilege user)
// Try to access admin-only features
// Expected: "You do not have permission" error
vendor/bin/phpcs --standard=WordPress-Extra,WordPress-VIP-Go

---

Automated Security Testing

11. 相关技能与资源

前置知识

Install Security Scanner:
bash
undefined
  • PHP基础 - 了解PHP语法、类型、函数
  • WordPress插件基础 - 钩子、动作、过滤器、插件结构

WPScan (CLI tool)

进阶主题

gem install wpscan wpscan --url https://yoursite.com --enumerate vp
  • WordPress测试与QA - 安全导向的测试策略
  • WordPress REST API - API端点安全
  • WordPress性能优化 - 安全缓存策略

Sucuri Security Plugin

外部资源

wp plugin install sucuri-scanner --activate

**Run PHP Code Sniffer:**
```bash

Check for security issues

用于测试的安全插件

vendor/bin/phpcs --standard=WordPress-Extra,WordPress-VIP-Go

---
  • Wordfence Security - 防火墙和恶意软件扫描器
  • Sucuri Security - 安全审计与监控
  • iThemes Security - 安全加固与监控

11. Related Skills & Resources

快速参考卡片

Prerequisites

  • PHP Fundamentals - Understanding PHP syntax, types, functions
  • WordPress Plugin Fundamentals - Hooks, actions, filters, plugin structure
php
// ============================================
// NONCES(CSRF防护)
// ============================================

// 表单
wp_nonce_field('action_name', 'nonce_field_name');
wp_verify_nonce($_POST['nonce_field_name'], 'action_name');

// URL
wp_nonce_url($url, 'action_name', 'nonce_param');
wp_verify_nonce($_GET['nonce_param'], 'action_name');

// AJAX
wp_create_nonce('ajax_action');
check_ajax_referer('ajax_action', 'nonce');

// ============================================
// 清理(输入处理)
// ============================================

sanitize_text_field()    // 单行文本
sanitize_textarea_field()// 多行文本
sanitize_email()         // 电子邮件地址
esc_url_raw()           // URL(用于存储)
sanitize_file_name()    // 文件名
absint()                // 正整数
wp_kses_post()          // HTML内容

// ============================================
// 验证(逻辑检查)
// ============================================

is_email($email)        // 验证电子邮件格式
is_numeric($value)      // 验证是否为数值
strlen($str) >= 3       // 最小长度
preg_match($pattern)    // 模式匹配
in_array($value, $allowed) // 白名单检查

// ============================================
// 转义(输出防护)
// ============================================

esc_html()              // HTML内容
esc_attr()              // HTML属性
esc_url()               // URL(输出)
esc_js()                // JavaScript字符串
esc_textarea()          // 文本框内容

// ============================================
// 权限(授权)
// ============================================

is_user_logged_in()
current_user_can('capability')
current_user_can('edit_post', $post_id)

// ============================================
// SQL注入防范
// ============================================

$wpdb->prepare("SELECT * FROM table WHERE id = %d", $id);
$wpdb->insert($table, $data, $format);
$wpdb->update($table, $data, $where, $format, $where_format);

记住: 安全不是一个功能,而是一项要求。每一行处理用户输入或显示数据的代码都必须遵循这些原则。如有疑问,就进行清理、验证和转义。

Advanced Topics

  • WordPress Testing & QA - Security-focused testing strategies
  • WordPress REST API - API endpoint security
  • WordPress Performance - Secure caching strategies

External Resources

Security Plugins for Testing

  • Wordfence Security - Firewall and malware scanner
  • Sucuri Security - Security auditing and monitoring
  • iThemes Security - Security hardening and monitoring

Quick Reference Card

php
// ============================================
// NONCES (CSRF Protection)
// ============================================

// Forms
wp_nonce_field('action_name', 'nonce_field_name');
wp_verify_nonce($_POST['nonce_field_name'], 'action_name');

// URLs
wp_nonce_url($url, 'action_name', 'nonce_param');
wp_verify_nonce($_GET['nonce_param'], 'action_name');

// AJAX
wp_create_nonce('ajax_action');
check_ajax_referer('ajax_action', 'nonce');

// ============================================
// SANITIZATION (Input Cleaning)
// ============================================

sanitize_text_field()    // Single-line text
sanitize_textarea_field()// Multi-line text
sanitize_email()         // Email addresses
esc_url_raw()           // URLs (for storage)
sanitize_file_name()    // File names
absint()                // Positive integers
wp_kses_post()          // HTML content

// ============================================
// VALIDATION (Logic Checks)
// ============================================

is_email($email)        // Valid email format
is_numeric($value)      // Numeric value
strlen($str) >= 3       // Minimum length
preg_match($pattern)    // Pattern matching
in_array($value, $allowed) // Whitelist check

// ============================================
// ESCAPING (Output Protection)
// ============================================

esc_html()              // HTML content
esc_attr()              // HTML attributes
esc_url()               // URLs (output)
esc_js()                // JavaScript strings
esc_textarea()          // Textarea content

// ============================================
// CAPABILITIES (Authorization)
// ============================================

is_user_logged_in()
current_user_can('capability')
current_user_can('edit_post', $post_id)

// ============================================
// SQL INJECTION PREVENTION
// ============================================

$wpdb->prepare("SELECT * FROM table WHERE id = %d", $id);
$wpdb->insert($table, $data, $format);
$wpdb->update($table, $data, $where, $format, $where_format);

Remember: Security is not a feature—it's a requirement. Every line of code that handles user input or displays data must follow these principles. When in doubt, sanitize, validate, and escape.