安全漏洞检测工具

为了帮助你开发安全的 WordPress 插件和主题,有许多工具可以帮助你检测代码中的安全漏洞。

在本课中,你将了解一些可用于测试代码安全漏洞的工具,并简要介绍如何使用它们。

你还将了解在哪里可以找到关于 Web 应用程序关键安全风险的更多信息。

插件

WordPress.org 仓库中有两个插件可以帮助你验证代码。

Plugin Check 是一个 WordPress 插件,你可以用它来测试你的插件是否符合 WordPress.org 插件目录的要求标准,其中一项要求就是你的插件代码是安全的。

安装并激活该插件后,你可以从 WordPress 管理仪表盘的“工具”菜单中访问 Plugin Check 管理页面。

选择你要测试的插件,并确保勾选了“安全”复选框。

点击检查!按钮运行测试。

结果会以表格形式显示,列出需要解决的问题,包括文件名和行号。

还有一个 Theme Check 插件,它与 Plugin Check 类似,但适用于主题。

安装此插件后,你可以从 WordPress 管理仪表盘的“外观”菜单中访问 Theme Check 管理页面。

同样,你选择要检查的主题,然后点击检查!按钮运行测试。

结果会显示在页面底部,列出需要解决的问题。

命令行

如果你想要可以从命令行运行的工具,可以使用 PHP_CodeSniffer 配合 WordPress 编码标准规则。

这种组合不仅会根据 WordPress 编码标准检查你的代码,还会检查安全漏洞。

要使用 PHP_CodeSniffer 配合 WordPress 编码标准,你需要安装 Composer,这是一个用于 PHP 项目的依赖管理器。

安装 Composer 不在本课的讨论范围内,但你可以在 Composer 网站上找到适用于 macOS/Linux 和 Windows 操作系统的安装说明。

为了让 Composer 工作,你还需要在计算机上安装 PHP,这样你就可以使用 PHP CLI 二进制文件,它允许你在终端中运行 PHP 脚本,而不仅仅是在浏览器中。

你可以在 PHP 手册的“安装与配置”部分找到在你的系统上安装 PHP 的方法。

安装 PHP 后,请确保将 PHP CLI 二进制文件的路径添加到计算机的操作系统路径中,这样你就可以在计算机的任何位置运行 PHP 命令。

要测试这一点,你可以在终端中运行以下命令:

php -v

它应该会输出 PHP 版本。

安装 Composer 后,你在终端中运行以下命令,在你的插件或主题目录中初始化一个新的 Composer 项目:

composer init

按照终端内的说明创建一个新的 Composer 项目。

这将在当前目录中创建一个 composer.json 文件,其中包含你项目的依赖项列表。

接下来,按照 WordPress 编码标准规则仓库中的说明,在你的插件目录中安装所有必需的依赖项:

composer require --dev wp-coding-standards/wpcs:"^3.0"

安装完成后,你可以使用 WordPress 标准对代码运行 PHP_CodeSniffer,它会输出需要解决的问题列表。

vendor/bin/phpcs --standard=WordPress your-plugin-file.php

代码编辑器

根据你使用的代码编辑器,有多种方法可以在编码时检查漏洞。

例如,Visual Studio Code 有许多扩展可以在编辑器内运行 PHP_CodeSniffer 工具,你可以在 VS Code 扩展市场中搜索“phpcs”找到它们。

此外,还有一些第三方服务可以在你的代码编辑器中扫描代码漏洞,例如 Sonar 的 SonarLint 工具。

SonarLint 对所有开源项目完全免费。只有当你想要分析私有仓库时才需要付费。

SonarLint 可作为 Visual Studio Code、JetBrains 编辑器和 Eclipse 的插件使用。

配置正确后,这些扩展可以在你编写代码时高亮显示问题,这样你就可以在提交代码之前修复它们。

OWASP

虽然不特指某个工具,但熟悉开放 Web 应用程序安全项目(OWASP)也是一个好主意。

OWASP 是一个致力于提高软件安全性的非营利基金会。他们提供了许多资源,包括 OWASP Top 10,这是 Web 应用程序最关键的十大安全风险列表。

安全是一个不断变化的领域,漏洞也会随着时间的推移而演变。通过遵循开放网络应用安全项目(OWASP)十大列表,你可以及时了解最新的安全风险和最佳实践。

修复常见安全漏洞

在本模块的第一课中,您学习了开发自定义插件或主题时应遵循的主要安全原则。

在本课中,您将学习如何在开发 WordPress 插件和主题时应用这些原则,通过修复一个编码不安全的表单提交插件来实践。

编码糟糕的表单提交插件

首先,下载 Learn Plugin Security 插件,并将其安装到您的本地开发环境中。

然后,在代码编辑器中打开主插件文件,查看代码。

  1. 在主插件 PHP 文件的顶部,设置了一些常量,这些常量在插件其他地方使用。前两个用于定义插件将用于重定向的页面别名。为了使插件功能正常工作,这些页面必须存在,且具有正确的别名。
  2. 接下来,在插件激活钩子上注册了一个回调函数。这会在数据库中创建一个自定义的 form_submissions 表。
  3. 之后,插件的管理 JavaScript 和前端样式 CSS 文件被加载。
  4. 接着,注册了一个短代码,用于在前端显示表单。
  5. 然后,一个回调函数被挂载到 wp 动作上,插件使用它来处理表单提交。
  6. 接下来,插件注册了一个管理子菜单,用于显示表单提交列表。
  7. 有一个函数,管理子菜单使用它从数据库中获取表单提交数据。
  8. 最后,有一个回调函数被挂载到 wp_ajax 函数上,这是插件用于从管理子菜单页面删除表单提交的 Ajax 端点。

管理 JavaScript 文件处理从提交页面删除表单提交的 Ajax 请求。

前端 style.css 文件在用户为短代码输入 class 属性时使用。默认颜色为红色,但用户可以将其更改为蓝色。它只是为表单添加了一个边框。

当短代码被添加到文章或页面时,它会渲染表单,用户可以提交他们的详细信息。表单提交后,会根据提交是否成功重定向到成功或错误页面。然后在仪表盘中,管理员可以查看表单提交,并使用 Ajax 删除提交。

SQL 注入

我们要查找的第一个常见漏洞是 SQL 注入。

当输入的值没有被正确清理,允许使用输入数据的任何 SQL 命令可能在数据库上执行时,就会发生 SQL 注入。

我们需要解决潜在 SQL 注入漏洞的第一个地方是在 wp_learn_maybe_process_form 函数中。

function wp_learn_maybe_process_form() {
    if (!isset($_POST['wp_learn_form'])){
        return;
    }
    $name = $_POST['name'];
    $email = $_POST['email'];

    global $wpdb;
    $table_name = $wpdb->prefix . 'form_submissions';

    $sql = "INSERT INTO $table_name (name, email) VALUES ('$name', '$email')";
    $result = $wpdb->query($sql);
    if ( 0 < $result ) {
        wp_redirect( WPLEARN_SUCCESS_PAGE_SLUG );
        die();
    }

    wp_redirect( WPLEARN_ERROR_PAGE_SLUG );
    die();
}
  1. 我们需要确保在查询中使用任何 $_POST 数据之前,先对其进行清理。
  2. 我们需要使用 wpdb 的 prepare 或 insert 函数。
function wp_learn_maybe_process_form() {
    if (!isset($_POST['wp_learn_form'])){
        return;
    }
    $name = sanitize_text_field($_POST['name']);
    $email = sanitize_email($_POST['email']);

    global $wpdb;
    $table_name = $wpdb->prefix . 'form_submissions';

    $rows = $wpdb->insert(
        $table_name,
        array(
            'name' => $name,
            'email' => $email,
        )
    );
    if ( 0 < $rows ) {
        wp_redirect( WPLEARN_SUCCESS_PAGE_SLUG );
        die();
    }

    wp_redirect( WPLEARN_ERROR_PAGE_SLUG );
    die();
}

这将确保名称和电子邮件字段值在从表单提交请求中接收时都被清理,并且在用于将记录存储到数据库之前也被清理。虽然这看起来可能有些过度,但如果您只清理输入,而代码后来被更改为以不同方式使用这些值,您仍然可能容易受到 SQL 注入攻击。

另一个需要防止 SQL 注入的地方是在 wp_learn_delete_form_submission 函数中。

  1. 我们需要确保在查询中使用任何 $_POST 数据之前,先对其进行清理。
  2. 我们需要使用 wpdb 的 prepare 或 delete 函数。
function wp_learn_delete_form_submission() {
    $id = (int) $_POST['id'];
    global $wpdb;
    $table_name = $wpdb->prefix . 'form_submissions';

    $rows_deleted = $wpdb->delete( $table_name, array( 'id' => $id ) );
    if ( 0 < $rows_deleted ) {
        $result = 'success';
    } else {
        $result = 'error';
    }
    return wp_send_json( array( 'result' => $result ) );
}

由于这是一个整数,我们可以使用 PHP 类型转换功能来确保它始终被转换为整数。

跨站脚本攻击 (XSS) (3:47)

我们要查找的下一个常见漏洞是跨站脚本攻击 (XSS)。

当恶意方将 JavaScript 注入到网页中时,就会发生跨站脚本攻击 (XSS),这可用于从网站发起多种不同的攻击或恶意活动。

您可以通过转义输出、剥离不需要的数据来避免 XSS 漏洞。您的代码应根据要转义的内容类型,使用适当的函数转义动态内容。

让我们看看一些输出数据的地方,并确保它们被正确转义。

第一个地方是在 wp_learn_form_shortcode 短代码回调的包装 div 中。

<div id="wp_learn_form" class="<?php echo $atts['class'] ?>">

在这里,div 的 class 属性是根据传递给短代码的属性渲染的。这是一个潜在的 XSS 漏洞,因为 class 属性没有被转义。

<div id="wp_learn_form" class="<?php echo esc_attr( $atts['class'] ) ?>">

请注意,您应该专门使用 esc_attr 函数来转义 HTML 属性。

接下来,我们有 wp_learn_render_admin_page 函数,它用于渲染管理页面。

function wp_learn_render_admin_page(){
    $submissions = wp_learn_get_form_submissions();
    ?>
    <div class="wrap" id="wp_learn_admin">
        <h1>Admin</h1>
        <table>
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Email</th>
                </tr>
            </thead>
            <?php foreach ($submissions as $submission){ ?>
                <tr>
                    <td><?php echo $submission->name?></td>
                    <td><?php echo $submission->email?></td>
                    <td><a class="delete-submission" data-id="<?php echo $submission->id?>" style="cursor:pointer;">Delete</a></td>
                </tr>
            <?php } ?>
        </table>
    </div>
    <?php
}

这里,$submission->name、$submission->email 和 $submission->id 应该被转义。

<?php foreach ($submissions as $submission){ ?>
    <tr>
        <td><?php echo esc_html($submission->name)?></td>
        <td><?php echo esc_html($submission->email)?></td>
        <td><a class="delete-submission" data-id="<?php echo (int) $submission->id?>" style="cursor:pointer;">Delete</a></td>
    </tr>
<?php } ?>

在这个示例中,你可以使用 esc_html,因为当 HTML 元素包含要显示的数据段时,这是正确的函数。务必密切关注每个转义函数的作用,因为有些会移除 HTML,而有些则允许保留。

你必须根据要输出的内容和上下文,选择最合适的函数。

最后,你将 ID 转换为整数,因为它被用于数据属性中。

跨站请求伪造 (CSRF)

下一个需要防范的漏洞是跨站请求伪造。CSRF 是指恶意方诱骗用户在他们已认证的 Web 应用中执行非预期的操作。

在使用 WordPress 开发时,熟悉 WordPress nonce 是帮助防范 CSRF 的必备技能。

Nonce 是“一次性使用的数字”,它提供了一种验证请求来源是否合法的方式。

  1. 当你需要验证请求是否合法时,创建一个 nonce。
  2. 你将 nonce 输出或传递给需要发起请求的地方。
  3. 当请求发起时,你验证该 nonce。

这个插件中存在两个可能的 CSRF 漏洞。

第一个漏洞出现在表单提交并处理数据时。要修复此问题,我们需要在短代码渲染的表单中添加一个 nonce,然后在表单提交时验证它。

在表单中,我们使用 wp_nonce_field 函数来添加一个包含 nonce 的隐藏字段。

<?php
wp_nonce_field( 'wp_learn_form_nonce_action', 'wp_learn_form_nonce_field' );
?>

注意你是如何传入一个 action 和一个 name 的。action 用于标识 nonce,而 name 是添加到表单中的字段名称。

如果你检查表单,可以看到 nonce 字段,它使用了你传递给函数的名称以及 nonce 值。

然后在表单提交函数中,你使用 wp_verify_nonce 函数来验证 nonce,传入 nonce 字段的值和 action。

    /**
     * 04 (b). Verify the nonce
     * https://developer.wordpress.org/apis/security/nonces/
     */
    if ( ! isset( $_POST['wp_learn_form_nonce_field'] ) || ! wp_verify_nonce( $_POST['wp_learn_form_nonce_field'], 'wp_learn_form_nonce_action' ) ) {
        wp_redirect( WPLEARN_ERROR_PAGE_SLUG );
        die();
    }

这里我们检查 nonce 字段是否已在请求中传递,然后验证 nonce 是否有效。如果 nonce 未传递或无效,我们将重定向到错误页面。

另一个需要防范 CSRF 的地方是用于删除表单提交数据的 Ajax 回调。

function wp_learn_delete_form_submission() {
    $id = (int) $_POST['id'];
    global $wpdb;
    $table_name = $wpdb->prefix . 'form_submissions';

    $rows_deleted = $wpdb->delete( $table_name, array( 'id' => $id ) );
    if ( 0 < $rows_deleted ) {
        $result = 'success';
    } else {
        $result = 'error';
    }
    return wp_send_json( array( 'result' => $result ) );
}

要修复此问题,首先我们需要使用 wp_create_nonce 手动创建一个 nonce,然后使用 wp_localize_script 将 nonce 传递给 JavaScript 层。

  /**
   * 04 (a). Add an ajax nonce to the script
   * https://developer.wordpress.org/apis/security/nonces/
   */
  $ajax_nonce = wp_create_nonce( 'wp_learn_ajax_nonce' );
  wp_localize_script(
      'wp-learn-admin',
      'wp_learn_ajax',
      array(
          'ajax_url' => admin_url( 'admin-ajax.php' ),
          'nonce'    => $ajax_nonce,
      )
  );

然后,我们需要在 jQuery POST 请求中包含该 nonce。

jQuery.post(
    wp_learn_ajax.ajax_url,
    {
        '_ajax_nonce': wp_learn_ajax.nonce,
        'action': 'delete_form_submission',
        'id': id,
    },
    function (response) {
        console.log( response );
        alert( 'Form submission deleted' );
        document.location.reload();
    }
);

注意我们如何在 POST 请求中将 nonce 指定为 _ajax_nonce。这是 WordPress 在处理 Ajax 请求时预期的 nonce 名称。

最后,在 Ajax 回调中,我们使用方便的 check_ajax_referrer 函数来验证 nonce。

    check_ajax_referer( 'wp_learn_ajax_nonce' );

你会看到传递给 check_ajax_referer 的字符串与我们创建 nonce 时传递给 wp_create_nonce 的字符串相同。

如果 check_ajax_referrer 失败,它将导致执行停止,因此我们不需要检查该函数的结果。

访问控制失效

这个插件中还有一个漏洞,即访问控制失效。BAC 是指用户能够访问他们本不应访问的资源。例如,用户可能能够访问管理员功能,即使他们不是管理员。

在我们的示例中,Ajax 函数存在一个访问控制失效漏洞。目前,任何人都可以使用正确的数据向 Ajax 请求 URL 发起请求,从而删除一个 form_submission。

要修复此问题,我们可以使用 WordPress 角色和权限 API 来检查用户是否具有删除表单提交数据的正确权限。在这种情况下,只需检查用户是否为管理员用户即可。

    if ( ! current_user_can( 'manage_options' ) ) {
        return wp_send_json( array( 'result' => 'Authentication error' ) );
    }

注意我们在这里进行了两次检查,一次针对 CSRF,一次针对访问控制。在这个示例中,执行顺序不是特别重要,但通常来说,先检查 CSRF,再检查访问控制是一个好习惯。

附加题 – 开放重定向

这个插件中还有一个额外的安全漏洞。你能找到它吗?

这是一个很难发现的漏洞,但所有使用 wp_redirect 的地方都应替换为 wp_safe_redirect。这是因为代码正在重定向到本地 URL,而 wp_safe_redirect 会检查其使用的 $location 在具有绝对路径时是否为允许的主机。这可以防止如果重定向 $location 受到攻击时可能发生的恶意重定向。

进一步阅读

要了解更多关于修复 WordPress 代码中常见漏洞的信息,请务必收藏 WordPress 开发者文档安全部分中关于常见漏洞的页面,以及包含权限检查、数据验证、安全输入、安全输出和 nonce 的示例。

安全开发插件和主题

到目前为止,你已经熟悉了为 WordPress 开发插件和主题的基础知识。现在也是考虑编写代码时安全影响的好时机。

在本课中,你将学习如何以安全为出发点进行开发。

你将了解确保代码安全的好处、保护代码安全的步骤,以及在哪里可以找到更多关于以安全为出发点进行开发的信息。

免责声明

本课旨在作为开发插件和主题时培养安全意识的入门介绍。本课中使用的代码非常简化。你不应在插件或主题中使用本教程中的任何代码。

请务必阅读 WordPress 开发者手册中关于安全的完整文档,网址为 https://developer.wordpress.org/apis/security/,以确保你遵循正确的方法和流程。

什么是以安全为出发点进行开发?

以安全为出发点进行开发,是指确保你的代码不仅能够正常工作,而且不会引入任何安全漏洞的过程。

如果你的插件或主题代码存在安全漏洞,可能会使任何安装了你的产品的 WordPress 网站面临潜在攻击,并可能导致该网站被入侵。

在编写代码时,培养安全思维并思考你的代码可能被恶意利用的方式非常重要。

  • 不要信任任何数据,无论是用户输入、第三方 API 数据,甚至是数据库中的数据。始终检查以确保数据有效且使用安全。
  • WordPress 提供了许多 API,可以帮助你完成常见任务,例如清理用户输入、验证数据和转义输出。依赖这些 API 来帮助验证和清理数据,而不是编写自己的函数。
  • 及时了解常见漏洞,并保持代码更新以防止它们。

安全在开发过程中处于什么位置?

安全应该在开发过程的每个阶段都加以考虑。

通常,发现的大多数安全漏洞都发生在 PHP 层,该层在服务器上执行。

因此,本课将重点介绍 PHP 代码中最常见的预防措施。

清理输入

开发时要采取的首要步骤之一是确保任何用户输入都经过清理。这意味着任何来自用户的数据,例如表单提交或 URL 参数,都要经过检查以确保使用安全。

在此示例代码中,名称和电子邮件字段是从插件生成的表单提交的,然后保存在名为 form_submissions 的自定义数据库表中。

$name = $_POST['name'];
$email = $_POST['email'];
global $wpdb;
$table_name = $wpdb->prefix . 'form_submissions';

$sql = "INSERT INTO $table_name (name, email) VALUES ('$name', '$email')";
$result = $wpdb->query($sql);

如你所见,数据直接保存到数据库,没有任何清理。

这意味着如果用户提交的名称为 John'; DROP TABLE form_submissions;--,SQL INSERT 查询将运行,随后是 DROP 查询,该表将从数据库中删除!

WordPress 提供了一个清理 API,可用于清理传入的数据。你可以使用 sanitize_text_fieldsanitize_email 函数在名称和电子邮件字段用于查询之前对其进行清理。

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

global $wpdb;
$table_name = $wpdb->prefix . 'form_submissions';

$sql = "INSERT INTO $table_name (name, email) VALUES ('$name', '$email')";
$result = $wpdb->query($sql);

请注意,代码遵循了清理数据的一个关键原则:尽可能早地进行清理。

要了解更多关于 WordPress 开发者可用的清理函数,请查看 WordPress 开发者文档中的“清理数据”页面。

验证数据 (4:06)

验证数据是根据预定义模式(或多个模式)对其进行测试的过程,结果明确:有效或无效。

不可信数据可能来自许多来源:用户、第三方 API 数据,甚至你的数据库数据也可能被视为不可信,尤其是当其他内容修改过它时。即使是网站管理员也可能犯错,输入不正确或不安全的数据,因此始终检查数据非常重要。

在此示例中,删除函数要求将数字 ID 发布到 admin-ajax 回调:

add_action( 'wp_ajax_delete_form_submission', 'wp_learn_delete_form_submission' );
function wp_learn_delete_form_submission() {
    if ( ! isset( $_POST['id'] ) ) {
        wp_send_json_error( 'Invalid ID' );
    }
    $id = $_POST['id'];

    global $wpdb;
    $table_name = $wpdb->prefix . 'form_submissions';

    $sql    = "DELETE FROM $table_name WHERE id = $id";
    $result = $wpdb->get_results( $sql );

    return wp_send_json( array( 'result' => $result ) );
}

这里 ID 直接用于 SQL 查询,没有任何验证。同样,这意味着如果用户提交的 ID 为 1; DROP TABLE form_submissions;,相同的 SQL DROP 查询将在删除后运行,表将被删除。

为了防止这种情况,你可以使用 PHP 的类型转换功能,确保 $id 的值始终是整数。可以通过在变量名前添加 (int) 来实现,如下所示:

$id = (int) $_POST['id'];

请注意,这仅当通过 $_POST 数组传递的字符串的第一个字符可以转换为整数时才有效,否则 $id 的值将为 0。在这种情况下,最好更新代码以处理这种情况。

该代码还遵循了验证数据的一个关键原则,即尽早进行验证。

if ($id === 0){
    // return early with an error
    return wp_send_json( array( 'result' => 'Invalid ID passed' ) );

}

要了解更多关于验证数据的不同方法,请查看 WordPress 开发者文档中的“验证数据”部分。

转义输出

安全的另一个方面是确保你输出到浏览器的任何信息都是安全的,包括任何文本、HTML 或 JavaScript 代码,以及来自数据库的数据。

即使你的代码不负责所显示数据的来源,它也有责任安全地显示它。

在这个例子中,代码从数据库中获取表单提交数据,然后循环遍历这些提交数据,并在 WordPress 仪表盘的管理界面中输出提交数据:

    $submissions = wp_learn_get_form_submissions();
    ?>
    <div class="wrap" id="wp_learn_admin">
        <h1>Admin</h1>
        <table>
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Email</th>
                </tr>
            </thead>
            <?php foreach ($submissions as $submission){ ?>
                <tr>
                    <td><?php echo $submission->name; ?></td>
                    <td><?php echo $submission->email; ?></td>
                    <td><a class="delete-submission" data-id="<?php echo $submission->id?>" style="cursor:pointer;">Delete</a></td>
                </tr>
            <?php } ?>
        </table>
    </div>
    <?php

这里我们有三个需要转义的数据项:$submission->name$submission->email 字段,以及 $submission->id

<td><a class="delete-submission" data-id="<?php echo (int) $submission->id?>" style="cursor:pointer;">Delete</a></td>

对于名称和电子邮件字段,我们可以使用 WordPress 内置的转义函数 esc_html()。ID 可以通过将其转换为整数来转义,就像你在数据验证时可能做的那样。

  <td><?php echo esc_html( $submission->name ); ?></td>
  <td><?php echo esc_html( $submission->email ); ?></td>
  <td><a class="delete-submission" data-id="<?php echo (int) $submission->id?>" style="cursor:pointer;">Delete</a></td>

请注意,这段代码遵循了转义数据的一个关键原则,即尽可能晚地转义数据。

要了解更多关于转义输出的不同方法,请查看 WordPress 开发者文档中的“转义数据”部分。

防止无效请求

每当发出请求时,检查该请求是否有效非常重要。这意味着要检查请求是否来自可信来源。

例如,你可能有一个短代码用于渲染表单,用户可以通过它提交信息。

渲染表单的函数可能看起来像这样。

add_shortcode( 'wp_learn_form_shortcode', 'wp_learn_form_shortcode' );
function wp_learn_form_shortcode() {
    ob_start();
    ?>
    <form method="post">
        <input type="hidden" name="wp_learn_form" value="submit">
        <div>
            <label for="email">Name</label>
            <input type="text" id="name" name="name" placeholder="Name">
        </div>
        <div>
            <label for="email">Email address</label>
            <input type="text" id="email" name="email" placeholder="Email address">
        </div>
        <div>
            <input type="submit" id="submit" name="submit" value="Submit">
        </div>
    </form>
    <?php
    $form = ob_get_clean();
    return $form;
}
?>

当用户提交表单时,数据会被发送到服务器。然后数据被处理、清理并存储在数据库中。

add_action( 'wp', 'wp_learn_maybe_process_form' );
function wp_learn_maybe_process_form() {
    if (!isset($_POST['wp_learn_form'])){
        return;
    }

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

    global $wpdb;
    $table_name = $wpdb->prefix . 'form_submissions';

    $sql = "INSERT INTO $table_name (name, email) VALUES ('$name', '$email')";
    $result = $wpdb->query($sql);
    if ( 0 < $result ) {
        wp_redirect( WPLEARN_SUCCESS_PAGE_SLUG );
        die();
    }

    wp_redirect( WPLEARN_ERROR_PAGE_SLUG );
    die();
}

由于表单可能出现在任何使用该短代码的页面上,恶意用户可能会尝试向表单发送 POST 请求,要么寻找插件中的漏洞,要么发送多个请求。

为了防止这种情况,你可以检查请求是否来自可信来源。为此,你可以实现一种称为 nonce(一次性数字)的机制。

首先,在表单本身中,你可以通过使用 wp_nonce_field 函数添加一个 nonce 字段,并向该函数传递一个 nonce 操作和 nonce 名称:

wp_nonce_field( 'wp_learn_form_nonce_action', 'wp_learn_form_nonce_field' );

当表单在前端渲染时,会向表单中添加一个隐藏字段,使用 nonce 名称作为隐藏字段的 id 和 name 属性,并将生成的 nonce 作为字段值。当表单提交时,这些数据会被发送。

然后,在处理表单数据的函数中,你可以通过使用 wp_verify_nonce 函数来验证 nonce 是否有效,并将 POST 提交的 nonce 字段和 nonce 操作传递给该函数。如果验证结果为 false,你可以提前退出,防止任何进一步的代码执行。

    if ( ! wp_verify_nonce( $_POST['wp_learn_form_nonce_field'], 'wp_learn_form_nonce_action' ) {
        wp_redirect( WPLEARN_ERROR_PAGE_SLUG );
        die();
    }

任何时候你的代码发出网络请求,无论是通过重定向到新 URL、向表单 POST 数据,还是发出 AJAX 请求,你都应该检查请求是否有效。

要了解更多关于如何在插件中使用 nonce,请查看 WordPress 开发者文档中的“Nonce”部分。

防止未认证用户

根据代码的功能,最好将某些功能限制为仅具有特定权限级别的用户使用。例如,你可能有一个从数据库中删除数据的函数。

function wp_learn_delete_form_submission() {
    if ( ! isset( $_POST['id'] ) ) {
        wp_send_json_error( 'Invalid ID' );
    }
    $id = (int) $_POST['id'];

    global $wpdb;
    $table_name = $wpdb->prefix . 'form_submissions';

    $sql    = "DELETE FROM $table_name WHERE id = $id";
    $result = $wpdb->get_results( $sql );

    return wp_send_json( array( 'result' => $result ) );
}

虽然你可能会尽力防止任何没有权限的人运行此函数,但针对这种情况添加检查仍然是一个好主意。

WordPress 包含一个强大的用户角色和权限系统,允许你使用默认的用户角色和权限,或创建自定义角色和权限。

在这种情况下,可以简单地只允许具有管理站点选项权限的用户执行操作,这是管理员角色中包含的标准权限。

function wp_learn_delete_form_submission() {
    if ( ! current_user_can( 'manage_options' ) ) {
        return wp_send_json( array( 'result' => 'Authentication error' ) );
    }
    // rest of function code
}

WordPress 开发者文档中有一个关于用户角色和权限的详细部分,其中包含默认权限列表以及如何创建自定义权限。

进一步阅读

为了准备好以安全为出发点进行开发,请务必阅读 WordPress 开发者文档中关于安全性的条目,其中包含本课程中的所有示例,以及关于安全最佳实践、常见漏洞和更多示例代码的额外信息。