插件安全

清理数据

不受信任的数据来自许多来源(用户、第三方网站,甚至您自己的数据库!),所有这些数据在使用之前都需要进行检查。

请记住:即使管理员也是用户,用户也会有意或无意地输入错误的数据。你的工作就是保护他们免受伤害。

清理输入是保护/清理/过滤输入数据的过程。验证优于清理,因为验证更具体。但当“更具体”不可能时,消毒就是下一个最好的选择。

 

例子

假设我们有一个名为的输入字段title

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

我们不能在这里使用验证,因为文本字段太笼统:它可以是任何东西。因此,我们使用以下函数清理输入数据sanitize_text_field()

$title = sanitize_text_field( $_POST['title'] );
update_post_meta( $post->ID, 'title', $title );

在后台,sanitize_text_field()执行以下操作:

  1. 检查无效的 UTF-8
  2. 将单个小于号 (<) 转换为实体
  3. 删除所有标签
  4. 删除换行符、制表符和多余的空白
  5. 剥离八位字节

消除功能

有许多功能可以帮助您清理数据。

 

 

验证数据

不受信任的数据来自许多来源(用户、第三方网站,甚至您自己的数据库!),所有这些数据在使用之前都需要进行检查。

请记住:即使管理员也是用户,用户也会有意或无意地输入错误的数据。你的工作就是保护他们免受伤害。

验证输入是根据预定义模式(或多个模式)测试数据的过程,并得出明确的结果:有效或无效。与清理相比,验证是一种更具体的方法,但两者都有其作用。

简单的验证示例:

应尽早进行数据验证。这意味着在执行任何操作之前验证数据。

验证理念

关于如何进行验证有几种不同的理念。每个都适合不同的场景。

安全名单

仅接受来自已知和可信值的有限列表的数据。

将不受信任的数据与安全列表进行比较时,确保使用严格的类型检查非常重要。否则,攻击者可能会以某种方式制作输入,使其通过安全列表,但仍然具有恶意效果。

比较运算符

$untrusted_input = '1 malicious string';  // will evaluate to integer 1 during loose comparisons

if ( 1 === $untrusted_input ) {  // == would have evaluated to true, but === evaluates to false
    echo '<p>Valid data';
} else {
    wp_die( 'Invalid data' );
}

in_array()

$untrusted_input = '1 malicious string';  // will evaluate to integer 1 during loose comparisons
$safe_values     = array( 1, 5, 7 );

if ( in_array( $untrusted_input, $safe_values, true ) ) {  // `true` enables strict type checking
    echo '<p>Valid data';
} else {
    wp_die( 'Invalid data' );
}

switch()

$untrusted_input = '1 malicious string';  // will evaluate to integer 1 during loose comparisons

switch ( true ) {
    case 1 === $untrusted_input:  // do your own strict comparison instead of relying on switch()'s loose comparison
        echo '<p>Valid data';
        break;

    default:
        wp_die( 'Invalid data' );
}

黑名单

拒绝来自已知不可信值的有限列表中的数据。这很少是一个好主意。

格式检测

测试数据的格式是否正确。如果是的话,只接受它。

if ( ! ctype_alnum( $data ) ) {
  wp_die( "Invalid format" );
}

if ( preg_match( "/[^0-9.-]/", $data ) ) {
  wp_die( "Invalid format" );
}

格式修正

接受大部分数据,但删除或更改危险部分。

$trusted_integer = (int) $untrusted_integer;
$trusted_alpha = preg_replace( '/[^a-z]/i', "", $untrusted_alpha );
$trusted_slug = sanitize_title( $untrusted_slug );

实例一

假设我们有一个输入字段旨在接受美国邮政编码:

<input type="text" id="wporg_zip_code" name="my-zipcode" maxlength="10" />

在这里,我们告诉浏览器最多只允许输入十个字符……但是对于可以输入字符没有限制。他们可以输入11221eval()

这就是验证的用武之地。在处理表单时,我们编写代码来检查每个字段的数据类型是否正确,如果不正确则将其丢弃。

例如:要检查该my-zipcode字段,我们可能会这样做:

/**
 * Validate a US zip code.
 *
 * @param string $zip_code   RAW zip code to check.
 *
 * @return bool              true if valid, false otherwise.
 */
function wporg_is_valid_us_zip_code( string $zip_code ):bool {
    // Scenario 1: empty.
    if ( empty( $zip_code ) ) {
        return false;
    }

    // Scenario 2: more than 10 characters.
    // The `maxlength` attribute is only enforced by 
    // the browser, so we still need to validate the
    // length of the input on the server to protect
    // against a manual submission.
    if ( 10 < strlen( trim( $zip_code ) ) ) {
        return false;
    }

    // Scenario 3: incorrect format.
    if ( ! preg_match( '/^d{5}(-?d{4})?$/', $zip_code ) ) {
        return false;
    }

    // Passed successfully.
    return true;
}

然后,在处理表单时,您的代码应该检查该wporg_zip_code字段并根据结果执行操作:

if ( isset( $_POST['wporg_zip_code'] ) && wporg_is_valid_us_zip_code( $_POST['wporg_zip_code'] ) ) {
    // $_POST['wporg_zip_code'] is valid; carry on
}

请注意,此特定示例正在检查提供的数据的格式是否正确;它不会检查提供的且格式正确的数据是否是有效的邮政编码。为此,您需要第二个函数来与有效邮政编码列表进行比较。

示例二

假设您的代码将查询数据库中的帖子,并且您希望允许用户对查询结果进行排序。

$allowed_keys = array( 'author', 'post_author', 'date', 'post_date' );
$orderby      = sanitize_key( $_POST['orderby'] );
if ( in_array( $orderby, $allowed_keys, true ) ) {
    // $orderby is valid; carry on
}

此示例代码通过将传入的排序键(存储在orderby 输入参数中)与允许的排序键数组进行比较来检查其有效性。这可以防止用户传递任意和潜在的恶意数据。

在根据数组检查传入的排序键之前,该键会被传递到内置的 WordPress 函数中 sanitize_key()。该函数确保(除其他外)密钥为小写,这是我们想要的,因为in_array()执行区分大小写的搜索。

传递true到第三个参数 in_array()可以启用严格的类型检查,这告诉函数不仅比较值,还比较值类型。这允许代码确定传入的排序键是字符串而不是其他数据类型。

验证功能

大多数验证都是作为自定义代码的一部分完成的,但也有一些辅助函数。这些是除“消毒”页面上列出的内容之外的内容。

检查WordPress 代码参考以获取更多此类功能。搜索具有如下名称的函数:*_exists()*_validate()和 is_*()。并非所有这些都是验证函数,但许多都是有帮助的。

 

 

转义数据

转义 输出是通过删除不需要的数据(例如格式错误的 HTML 或脚本标记)来保护输出数据的过程。此过程有助于在向最终用户呈现数据之前保护您的数据。 

大多数 WordPress 函数都会正确准备输出数据,并且不需要额外的转义。

转义函数

WordPress 有许多辅助函数,可用于最常见的场景。

请密切注意每个函数的作用,因为有些函数会删除 HTML,而另一些函数则允许它。您必须使用最适合您所呼应的内容和上下文的功能。你总是在回声的时候想逃跑,而不是之前。

<h4><?php echo esc_html( $title ); ?></h4>
<div onclick='<?php echo esc_js( $value ); ?>' />

 

<img alt="" src="<?php echo esc_url( $media_url ); ?>" />
<ul class="<?php echo esc_attr( $stored_class ); ?>">

 

自定义转义示例

如果您需要以特定方式转义输出,函数wp_kses() (发音为“kisses”)将会派上用场。

此函数可确保输出中仅出现指定的 HTML 元素、属性和属性值,并规范化 HTML 实体。

<?php
echo wp_kses_post( $partial_html );
echo wp_kses(
    $another_partial_html,
    array(
        'a'      => array(
            'href'  => array(),
            'title' => array(),
        ),
        'br'     => array(),
        'em'     => array(),
        'strong' => array(),
    )
); ?>

在此示例中,除 <a>、  <br>、 <em>和 之外的所有标签都<strong> 将被删除。此外,如果 <a> 传递了标记,则转义可确保仅  返回thehref 和 the  。title

总是晚使用escape

最好尽可能晚地进行输出转义,最好是在输出数据时进行。

出于以下几个原因,最好晚点逃跑:

// Okay, but not great.
$url = esc_url( $url );
$text = esc_html( $text );
echo '<a href="'. $url . '">' . $text . '</a>';

// Much better!
echo '<a href="'. esc_url( $url ) . '">' . esc_html( $text ) . '</a>';

......除非你不能

有时晚逃是不切实际的。在极少数情况下,输出无法传递到 wp_kses(),因为根据定义,它会删除正在生成的脚本。

_escaped在这种情况下,在创建字符串时始终进行转义,并将值存储在以,  _safe or 为后缀的变量中 _clean (例如, $variable 变为 $variable_escaped or  $variable_safe)。

如果一个函数无法在内部输出并且转义较晚,那么它必须始终返回“安全”HTML。这允许 不需要通过 允许此类标签echo my_custom_script_code(); 的版本传递脚本标签来完成 。wp_kses()

通过本地化逃脱

通常使用 WordPress 本地化功能,例如或 ,而不是用于echo输出数据。_e()__()

这些函数只是将本地化函数包装在转义函数中:

esc_html_e( 'Hello World', 'text_domain' );
// Same as
echo esc_html( __( 'Hello World', 'text_domain' ) );

这些辅助函数结合了本地化和转义:

例子

echo $int;

根据它是整数还是浮点数,  (int)、  absint()、 (float) 都是正确且可接受的。
有时, number_format() 或者 number_format_i18n() 可能更合适。

intval()、 floatval() 是可以接受的,但是是过时的 (PHP4) 函数。

转义 HTML 属性中的任意变量

echo '<div id="', $prefix, '-box', $id, '">';

这应该通过一次调用来转义 esc_attr()
当变量用作属性或 url 的一部分时,最好对整个字符串进行转义,这样变量之前的潜在转义字符将被正确转义。

正确的:

echo '<div id="', esc_attr( $prefix . '-box' . $id ), '">';

错误:

echo '<div id="', esc_attr( $prefix ), '-box', esc_attr( $id ), '">';

注意: wp_create_nonce() 如果在 HTML 属性中使用,则使用创建的随机数也应该像这样转义。

在 HTML 属性中以及其他上下文中转义任意 URL

 

echo '<a href="', $url, '">';

这应该用 转义 esc_url()

正确的:

echo '<a href="', esc_url( $url ), '">';

 

错误:

echo '<a href="', esc_attr( $url ), '">'; 
echo '<a href="', esc_attr( esc_url( $url ) ), '">';

通过以下方式将任意变量传递给 JavaScript wp_localize_script()

wp_localize_script( 'handle', 'name',
	array(
		'prefix_nonce' => wp_create_nonce( 'plugin-name' ),
		'ajaxurl'      => admin_url( 'admin-ajax.php' ),
		'errorMsg'     => __( 'An error occurred', 'plugin-name' ),
	)
);

不需要转义,WordPress 会转义。

在 JavaScript 块中转义任意变量

<script type="text/javascript"> 
    var myVar = <?php echo $my_var; ?> 
</script>

 

$my_var 应该用 转义 esc_js()

正确的:

<script type="text/javascript">
    var myVar = <?php echo esc_js( $my_var ); ?>
</script>

在内联 JavaScript 中转义任意变量

<a href="#" onclick="do_something(<?php echo $var; ?>); return false;">

$var 应该用 转义 esc_js()

正确的:

<a href="#" onclick="do_something(<?php echo esc_js( $var ); ?>); return false;">

转义 HTML 属性中的任意变量以供 JavaScript 使用

<a href="#" data-json="<?php echo $var; ?>">">

$var 应使用 esc_js()json_encode() 或 进行转义wp_json_encode()

正确的:

<a href="#" data-json="<?php echo esc_js( $var ); ?>">

在 HTML 文本区域中转义任意字符串

echo '<textarea>', $data, '</textarea>';

$data 应该用 转义 esc_textarea()

正确的:

echo '<textarea>', esc_textarea( $data ), '</textarea>';

转义 HTML 标签内的任意字符串

echo '<div>', $phrase, '</div>';

 

这取决于是否 $phrase 期望包含 HTML。

在 XML 或 XSL 上下文中转义任意字符串

echo '<loc>', $var, '</loc>';

 

正确的:

echo '<loc>', ent2ncr( $var ), '</loc>';

 

 

 

 

 

 

 

 

 

 

随机数

随机数是“使用一次的数字”,有助于保护 URL 和表单免遭某些类型的滥用、恶意或其他形式的滥用。

从技术上讲,WordPress 随机数并不是严格意义上的数字;而是数字。它们是由数字和字母组成的哈希值。它们也不是只使用一次:它们的“寿命”有限,之后就会过期。在此时间段内,将为给定上下文中的给定用户生成相同的随机数。该操作的随机数对于该用户将保持不变,直到该随机数生命周期完成。

WordPress 的安全令牌被称为“随机数”(尽管与真正的随机数存在上述差异),因为它们的用途与随机数几乎相同。它们有助于防范包括 CSRF 在内的多种类型的攻击,但不能防范重放攻击,因为它们不会被检查为一次性使用。切勿依赖随机数进行身份验证、授权或访问控制。使用 保护您的函数current_user_can(),并始终假设随机数可能会受到损害。

为什么要使用随机数?

有关使用随机数的示例,请考虑管理屏幕可能会生成这样的 URL,该 URL 会丢弃帖子编号 123

http://example.com/wp-admin/post.php?post=123&action=trash

 

当您访问该 URL 时,WordPress 将验证您的身份验证 cookie 信息,并且如果您被允许删除该帖子,则将继续删除它。攻击者可以利用此漏洞让您的浏览器在您不知情的情况下访问该 URL。例如,攻击者可以在第 3 方页面上制作一个伪装链接,如下所示:

<img src="http://example.com/wp-admin/post.php?post=123&action=trash" />

 

这将触发您的浏览器向 WordPress 发出请求,浏览器会自动附加您的身份验证 cookie,并且 WordPress 会认为这是一个有效的请求。

添加随机数可以防止这种情况发生。例如,当使用随机数时,WordPress 为用户生成的 URL 如下所示:

http://example.com/wp-admin/post.php?post=123&action=trash&_wpnonce=b192fc4204

 

如果有人试图在没有 WordPress 生成并提供给用户的正确随机数的情况下删除编号 123 的帖子,WordPress 将向浏览器发送“403 Forbidden”响应。

创建随机数

您可以创建一个随机数并将其添加到 URL 中的查询字符串中,也可以将其添加到表单中的隐藏字段中,或者可以通过其他方式使用它。

对于要在 AJAX 请求中使用的随机数,通常将随机数添加到隐藏字段,JavaScript 代码可以从中获取它。

请注意,随机数对于当前用户的会话是唯一的,因此如果用户异步登录或注销,页面上的任何随机数将不再有效。

为访客(非登录用户)自定义随机数

默认情况下,WordPress 核心会为具有相同用户 ID(值 0)的访客生成相同的随机数。也就是说,它不能阻止访客遭受 CSRF 攻击。为了增强关键操作的安全性,您可以为访客开发会话机制,并挂钩 nonce_user_logged_out 过滤器,以将用户 ID 值替换 0 为会话机制中的另一个随机 ID。

将随机数添加到 URL

要将随机数添加到 URL,请调用wp_nonce_url()指定裸 URL 和表示操作的字符串。例如:

$complete_url = wp_nonce_url( $bare_url, 'trash-post_'.$post->ID );

为了获得最大程度的保护,请确保表示操作的字符串尽可能具体。

默认情况下,wp_nonce_url()添加一个名为 的字段_wpnonce。您可以在函数调用中指定不同的名称。例如:

$complete_url = wp_nonce_url( $bare_url, 'trash-post_'.$post->ID, 'my_nonce' );

向表单添加随机数

要将随机数添加到表单,请调用wp_nonce_field()指定表示操作的字符串。默认情况下wp_nonce_field()生成两个隐藏字段,一个的值为随机数,另一个的值为当前 URL(引荐来源网址),并且它会回显结果。例如,这个调用:

wp_nonce_field( 'delete-comment_'.$comment_id );

可能会回显类似的内容:

<input type="hidden" id="_wpnonce" name="_wpnonce" value="796c7766b1" />
<input type="hidden" name="_wp_http_referer" value="/wp-admin/edit-comments.php" />

为了获得最大程度的保护,请确保表示操作的字符串尽可能具体。

您可以为 nonce 字段指定不同的名称,您可以指定您不需要引用字段,并且您可以指定您希望返回结果而不是回显。有关语法的详细信息,请参阅:wp_nonce_field()

创建一个随机数以其他方式使用

要创建以其他方式使用的随机数,请调用wp_create_nonce()指定表示操作的字符串。例如:

$nonce = wp_create_nonce( 'my-action_'.$post->ID );

这只是返回随机数本身。例如:295a686963

为了获得最大程度的保护,请确保表示操作的字符串尽可能具体。

验证随机数

您可以验证在 URL、管理屏幕中的表单、AJAX 请求或其他上下文中传递的随机数。

验证从管理屏幕传递的随机数
要验证在管理屏幕中的 URL 或表单中传递的随机数,请调用check_admin_referer()指定表示操作的字符串。

例如:

check_admin_referer( 'delete-comment_'.$comment_id );

此调用检查随机数和引荐来源网址,如果检查失败,则采取正常操作(使用“403 Forbidden”响应和错误消息终止脚本执行)。

如果您在创建随机数时未使用默认字段名称 ( _wpnonce),请指定字段名称。

例如:

check_admin_referer( 'delete-comment_'.$comment_id, 'my_nonce' );

验证 AJAX 请求中传递的随机数

要验证 AJAX 请求中传递的随机数,请调用check_ajax_referer() 并指定表示操作的字符串。例如:

check_ajax_referer( 'process-comment' );

此调用检查随机数(但不检查引用者),如果检查失败,则默认情况下它会终止脚本执行。

如果您在创建随机数时未使用默认字段名称(_wpnonce或)之一,或者如果您想采取其他操作而不是终止执行,则可以指定其他参数。_ajax_nonce详细信息请参见:check_ajax_referer()

验证在其他上下文中传递的随机数
要验证在其他上下文中传递的随机数,请wp_verify_nonce()调用指定随机数和表示操作的字符串。

例如:

wp_verify_nonce( $_REQUEST['my_nonce'], 'process-comment'.$comment_id );

如果结果为 false,则不再继续处理该请求。相反,采取一些适当的行动。通常的操作是调用wp_nonce_ays(),它会向浏览器发送“403 Forbidden”响应。

修改nonce系统

您可以通过添加各种操作和过滤器来修改随机数系统。

修改随机数生存期

默认情况下,随机数的生命周期为一天。此后,即使随机数与操作字符串匹配,随机数也不再有效。要更改生命周期,请添加一个 nonce_life 过滤器,指定生命周期(以秒为单位)。

例如,要将生命周期更改为四小时:

add_filter( 'nonce_life', function () { return 4 * HOUR_IN_SECONDS; } );

执行额外验证

check_admin_referrer()要在发现随机数和引荐来源网址有效时执行附加验证,请添加check_admin_referer操作。

例如:

function wporg_additional_check ( $action, $result ) {
    ...
}
add_action( 'check_admin_referer', 'wporg_additional_check', 10, 2 );

以同样的方式check_ajax_referer()添加一个动作。check_ajax_referer

 

更改错误消息

您可以使用翻译系统更改随机数无效时发送的错误消息。例如:

function my_nonce_message ($translation) {
    if ($translation === 'Are you sure you want to do this?') {
       return 'No! No! No!';
    } 

    return $translation;
}

add_filter('gettext', 'my_nonce_message');

附加信息

本节包含有关 WordPress 中的随机数系统的其他信息,这些信息有时可能有用。

随机数生命周期

请注意,正如 WordPress 随机数不是“使用一次的数字”一样,随机数生命周期也不是真正的随机数生命周期。WordPress 使用具有两个刻度(生命周期的一半)的系统,并验证当前刻度和最后一个刻度的随机数。在默认设置(24 小时生命周期)中,这意味着随机数中的时间信息与自 Unix 纪元以来经过了多少个 12 小时时间段相关。这意味着在中午和午夜之间创建的随机数将具有生命周期,直到第二天中午。因此,实际寿命在 12 至 24 小时之间变化。

当随机数有效时,验证随机数的函数将返回当前刻度编号 1 或 2。例如,您可以使用此信息来刷新第二个刻度中的随机数,以便它们不会过期。

随机数安全性

如果您正确安装了 WordPress,则使用您网站特有的密钥和盐生成随机数。NONCE_KEYNONCE_SALT在您的文件中定义wp-config.php,并且该文件包含提供更多信息的注释。

切勿依赖随机数进行身份验证或授权或访问控制。使用 保护您的函数current_user_can(),始终假设随机数可能会受到损害。

更换随机数系统

构成随机数系统的一些函数是可插入的,以便您可以通过提供自己的函数来替换它们。

要更改管理请求或 AJAX 请求的验证方式,您可以替换check_admin_referrer()check_ajax_referrer(),或两者都替换。

要将随机数系统替换为其他随机数系统,您可以替换wp_create_nonce(),wp_verify_nonce()wp_nonce_tick()

有关的

随机数函数:wp_nonce_ays()wp_nonce_field()wp_nonce_url()wp_verify_nonce()wp_create_nonce()check_admin_referer()check_ajax_referer(),wp_referer_field()

随机数挂钩:nonce_lifenonce_user_logged_outexplain_nonce_(verb)-(noun),check_admin_referer

用户角色和能力

用户角色和能力

如果您的插件允许用户提交数据(无论是在管理员端还是公共端),它应该检查用户功能。

用户角色和能力

创建高效安全层的最重要步骤是建立用户权限系统。WordPress 以用户角色和功能的形式提供这一点。

每个登录 WordPress 的用户都会根据其用户角色自动分配特定的用户功能。

用户角色只是一种表示用户属于哪个组的奇特方式。每个组都有一组特定的预定义功能。

例如,网站的主要用户将具有管理员的用户角色,而其他用户可能具有编辑者或作者等角色。您可以为一个角色分配多个用户,即一个网站可能有两个管理员。

用户能力是您分配给每个用户或用户角色的特定权限。

例如,管理员具有“manage_options”功能,允许他们查看、编辑和保存网站的选项。另一方面,编辑者缺乏这种能力,这将阻止他们与选项进行交互。

然后在管理中的各个点检查这些功能。取决于分配给角色的能力;菜单、功能和 WordPress 体验的其他方面可能会被添加或删除。

当您构建插件时,请确保仅在当前用户具有必要的功能时才运行您的代码。

等级制度

用户角色越高,用户拥有的能力越多。每个用户角色都会继承层次结构中先前的角色。

例如,“管理员”是单个站点安装中的最高用户角色,它继承以下角色及其功能:“订阅者”、“贡献者”、“作者”和“编辑者”。

例子

无限制

下面的示例在前端创建一个链接,该链接提供了垃圾帖子的功能。因为此代码不检查用户能力,所以它允许该网站的任何访问者删除帖子!


/**
 * Generate a Delete link based on the homepage url.
 *
 * @param string $content   Existing content.
 *
 * @return string|null
 */
function wporg_generate_delete_link( $content ) {
	// Run only for single post page.
	if ( is_single() && in_the_loop() && is_main_query() ) {
		// Add query arguments: action, post.
		$url = add_query_arg(
			[
				'action' => 'wporg_frontend_delete',
				'post'   => get_the_ID(),
			], home_url()
		);

		return $content . ' <a href="' . esc_url( $url ) . '">' . esc_html__( 'Delete Post', 'wporg' ) . '</a>';
	}

	return null;
}


/**
 * Request handler
 */
function wporg_delete_post() {
	if ( isset( $_GET['action'] ) && 'wporg_frontend_delete' === $_GET['action'] ) {

		// Verify we have a post id.
		$post_id = ( isset( $_GET['post'] ) ) ? ( $_GET['post'] ) : ( null );

		// Verify there is a post with such a number.
		$post = get_post( (int) $post_id );
		if ( empty( $post ) ) {
			return;
		}

		// Delete the post.
		wp_trash_post( $post_id );

		// Redirect to admin page.
		$redirect = admin_url( 'edit.php' );
		wp_safe_redirect( $redirect );

		// We are done.
		die;
	}
}


/**
 * Add the delete link to the end of the post content.
 */
add_filter( 'the_content', 'wporg_generate_delete_link' );

/**
 * Register our request handler with the init hook.
 */
add_action( 'init', 'wporg_delete_post' );

受限于特定能力

上面的示例允许该网站的任何访问者单击“删除”链接并删除该帖子。但是,我们只希望编辑及以上级别能够单击“删除”链接。

为了实现这一点,我们将检查当前用户是否具有该能力edit_others_posts,只有编辑者或以上人员才具有:


/**
 * Generate a Delete link based on the homepage url.
 *
 * @param string $content   Existing content.
 *
 * @return string|null
 */
function wporg_generate_delete_link( $content ) {
	// Run only for single post page.
	if ( is_single() && in_the_loop() && is_main_query() ) {
		// Add query arguments: action, post.
		$url = add_query_arg(
			[
				'action' => 'wporg_frontend_delete',
				'post'   => get_the_ID(),
			], home_url()
		);

		return $content . ' <a href="' . esc_url( $url ) . '">' . esc_html__( 'Delete Post', 'wporg' ) . '</a>';
	}

	return null;
}


/**
 * Request handler
 */
function wporg_delete_post() {
	if ( isset( $_GET['action'] ) && 'wporg_frontend_delete' === $_GET['action'] ) {

		// Verify we have a post id.
		$post_id = ( isset( $_GET['post'] ) ) ? ( $_GET['post'] ) : ( null );

		// Verify there is a post with such a number.
		$post = get_post( (int) $post_id );
		if ( empty( $post ) ) {
			return;
		}

		// Delete the post.
		wp_trash_post( $post_id );

		// Redirect to admin page.
		$redirect = admin_url( 'edit.php' );
		wp_safe_redirect( $redirect );

		// We are done.
		die;
	}
}


/**
 * Add delete post ability
 */
add_action('plugins_loaded', 'wporg_add_delete_post_ability');
 
function wporg_add_delete_post_ability() {    
    if ( current_user_can( 'edit_others_posts' ) ) {
        /**
         * Add the delete link to the end of the post content.
         */
        add_filter( 'the_content', 'wporg_generate_delete_link' );
      
        /**
         * Register our request handler with the init hook.
         */
        add_action( 'init', 'wporg_delete_post' );
    }
}

 

常见漏洞

常见漏洞

安全是一个不断变化的环境,漏洞会随着时间的推移而演变。以下讨论了您应该防范的常见漏洞,以及保护您的主题免遭利用的技术。

漏洞类型

SQL注入

当输入的值未正确清理时,就会发生 SQL 注入,从而可能执行输入数据中的任何 SQL 命令。为了防止这种情况,WordPress API 非常丰富,提供了诸如此类的功能, add_post_meta();而无需您通过 SQL ( INSERT INTO wp_postmeta…) 手动添加帖子元。

妈妈的剥削

xkcd妈妈的功绩

强化主题抵御 SQL 注入的第一条规则是:当有 WordPress 功能时,使用它。

但有时您需要执行 API 中未考虑到的复杂查询。如果是这种情况,请始终使用这些 $wpdb&nbsp;功能。这些是专门为保护您的数据库而构建的。

SQL查询中的所有数据在执行SQL查询之前都必须进行SQL转义,以防止SQL注入攻击。用于 SQL 转义的最佳函数是$wpdb->prepare()同时支持类似sprintf()和类似vsprintf()的语法。

$wpdb->get_var( $wpdb->prepare(
    "SELECT something FROM table WHERE foo = %s and status = %d",
    $name, // an unescaped string (function will do the sanitization for you)
    $status // an untrusted integer (function will do the sanitization for you)
) );

跨站脚本 (XSS)

当不法分子将 JavaScript 注入网页时,就会发生跨站脚本攻击 (XSS)。

通过转义输出、删除不需要的数据来避免 XSS 漏洞。由于主题的主要职责是输出内容,因此主题应 根据内容的类型使用适当的功能转义动态内容。

转义功能之一的示例是从用户配置文件中转义 URL。

<img src="<?php echo esc_url( $great_user_picture_url ); ?>" />

可以对包含 HTML 实体的内容进行清理,以仅允许指定的 HTML 元素。

$allowed_html = array(
    'a' => array(
        'href' => array(),
        'title' => array()
    ),
    'br' => array(),
    'em' => array(),
    'strong' => array(),
);

echo wp_kses( $custom_content, $allowed_html );

跨站请求伪造 (CSRF)

跨站点请求伪造或 CSRF(发音为 sea-surf)是指不法分子诱骗用户在其进行身份验证的 Web 应用程序中执行不需要的操作。例如,网络钓鱼电子邮件可能包含指向某个页面的链接,该页面在 WordPress 管理员中删除用户帐户。

如果您的主题包含任何基于 HTML 或 HTTP 的表单提交,请使用 随机数 来保证用户打算执行操作。

<form method="post">
    <!-- some inputs here … -->
    <?php wp_nonce_field( 'name_of_my_action', 'name_of_nonce_field' ); ?>
</form>

保持最新状态

及时了解潜在的安全漏洞非常重要。以下资源提供了一个良好的起点:

例子

例子

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

/**
 * Generate a Delete link based on the homepage url.
 *
 * @param string $content   Existing content.
 *
 * @return string|null
 */
function wporg_generate_delete_link( $content ) {
	// Run only for single post page.
	if ( is_single() && in_the_loop() && is_main_query() ) {
		// Add query arguments: action, post, nonce
		$url = add_query_arg(
			[
				'action' => 'wporg_frontend_delete',
				'post'   => get_the_ID(),
				'nonce'  => wp_create_nonce( 'wporg_frontend_delete' ),
			], home_url()
		);

		return $content . ' <a href="' . esc_url( $url ) . '">' . esc_html__( 'Delete Post', 'wporg' ) . '</a>';
	}

	return null;
}


/**
 * Request handler
 */
function wporg_delete_post() {
	if ( isset( $_GET['action'] )
         && isset( $_GET['nonce'] )
         && 'wporg_frontend_delete' === $_GET['action']
         && wp_verify_nonce( $_GET['nonce'], 'wporg_frontend_delete' ) ) {

		// Verify we have a post id.
		$post_id = ( isset( $_GET['post'] ) ) ? ( $_GET['post'] ) : ( null );

		// Verify there is a post with such a number.
		$post = get_post( (int) $post_id );
		if ( empty( $post ) ) {
			return;
		}

		// Delete the post.
		wp_trash_post( $post_id );

		// Redirect to admin page.
		$redirect = admin_url( 'edit.php' );
		wp_safe_redirect( $redirect );

		// We are done.
		die;
	}
}


/**
 * Add delete post ability
 */
add_action('plugins_loaded', 'wporg_add_delete_post_ability');
 
function wporg_add_delete_post_ability() {    
    if ( current_user_can( 'edit_others_posts' ) ) {
        /**
         * Add the delete link to the end of the post content.
         */
        add_filter( 'the_content', 'wporg_generate_delete_link' );
      
        /**
         * Register our request handler with the init hook.
         */
        add_action( 'init', 'wporg_delete_post' );
    }
}