';
```
注意: `wp_create_nonce()`
如果在 HTML 属性中使用,则使用创建的随机数也应该像这样转义。
####
在 HTML 属性中以及其他上下文中转义任意 URL
```php
echo '
';
```
这应该用 转义 `esc_url()`。
**正确的:**
```php
echo '';
```
**错误:**
```php
echo '';
echo '';
```
#### 通过以下方式将任意变量传递给 JavaScript wp\_localize\_script()
```php
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 块中转义任意变量
```php
```
`$my_var` 应该用 转义 `esc_js()`。
**正确的:**
```php
```
#### 在内联 JavaScript 中转义任意变量
```php
```
`$var` 应该用 转义 `esc_js()`。
**正确的:**
```php
```
#### 转义 HTML 属性中的任意变量以供 JavaScript 使用
```php
">
```
`$var` 应使用 `esc_js()`, `json_encode()` 或 进行转义`wp_json_encode()`。
**正确的:**
```php
```
#### 在 HTML 文本区域中转义任意字符串
```php
echo '';
```
`$data` 应该用 转义 `esc_textarea()`。
**正确的:**
```php
echo '';
```
#### 转义 HTML 标签内的任意字符串
```php
echo '', $phrase, '
';
```
这取决于是否 `$phrase` 期望包含 HTML。
- 如果没有,请使用 `esc_html()` 或其任何变体。
- 如果需要 HTML,请使用 `wp_kses_post()`, `wp_kses_allowed_html()` 或 以及 `wp_kses()` 您想要允许的一组 HTML 标记。
#### 在 XML 或 XSL 上下文中转义任意字符串
```php
echo '', $var, '';
```
**正确的:**
```php
echo '', ent2ncr( $var ), '';
```
# 随机数
随机数是“使用一次的数字”,有助于保护 URL 和表单免遭某些类型的滥用、恶意或其他形式的滥用。
从技术上讲,WordPress 随机数并不是严格意义上的数字;而是数字。它们是由数字和字母组成的哈希值。它们也不是只使用一次:它们的“寿命”有限,之后就会过期。在此时间段内,将为给定上下文中的给定用户生成相同的随机数。该操作的随机数对于该用户将保持不变,直到该随机数生命周期完成。
WordPress 的安全令牌被称为“随机数”(尽管与真正的随机数存在上述差异),因为它们的用途与随机数几乎相同。它们有助于防范包括 CSRF 在内的多种类型的攻击,但不能防范重放攻击,因为它们不会被检查为一次性使用。切勿依赖随机数进行身份验证、授权或访问控制。使用 保护您的函数`current_user_can()`,并始终假设随机数可能会受到损害。
#### 为什么要使用随机数?
有关使用随机数的示例,请考虑管理屏幕可能会生成这样的 URL,该 URL 会丢弃帖子编号 123
```php
http://example.com/wp-admin/post.php?post=123&action=trash
```
当您访问该 URL 时,WordPress 将验证您的身份验证 cookie 信息,并且如果您被允许删除该帖子,则将继续删除它。攻击者可以利用此漏洞让您的浏览器在您不知情的情况下访问该 URL。例如,攻击者可以在第 3 方页面上制作一个伪装链接,如下所示:
```php
```
这将触发您的浏览器向 WordPress 发出请求,浏览器会自动附加您的身份验证 cookie,并且 WordPress 会认为这是一个有效的请求。
添加随机数可以防止这种情况发生。例如,当使用随机数时,WordPress 为用户生成的 URL 如下所示:
```php
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](https://developer.wordpress.org/reference/hooks/nonce_user_logged_out/) 过滤器,以将用户 ID 值替换 `0` 为会话机制中的另一个随机 ID。
#### 将随机数添加到 URL
要将随机数添加到 URL,请调用`wp_nonce_url()`指定裸 URL 和表示操作的字符串。例如:
```php
$complete_url = wp_nonce_url( $bare_url, 'trash-post_'.$post->ID );
```
为了获得最大程度的保护,请确保表示操作的字符串尽可能具体。
默认情况下,`wp_nonce_url()`添加一个名为 的字段`_wpnonce`。您可以在函数调用中指定不同的名称。例如:
```php
$complete_url = wp_nonce_url( $bare_url, 'trash-post_'.$post->ID, 'my_nonce' );
```
#### 向表单添加随机数
要将随机数添加到表单,请调用`wp_nonce_field()`指定表示操作的字符串。默认情况下`wp_nonce_field()`生成两个隐藏字段,一个的值为随机数,另一个的值为当前 URL(引荐来源网址),并且它会回显结果。例如,这个调用:
```php
wp_nonce_field( 'delete-comment_'.$comment_id );
```
可能会回显类似的内容:
```php
```
为了获得最大程度的保护,请确保表示操作的字符串尽可能具体。
您可以为 nonce 字段指定不同的名称,您可以指定您不需要引用字段,并且您可以指定您希望返回结果而不是回显。有关语法的详细信息,请参阅:`wp_nonce_field()`。
#### 创建一个随机数以其他方式使用
要创建以其他方式使用的随机数,请调用`wp_create_nonce()`指定表示操作的字符串。例如:
```php
$nonce = wp_create_nonce( 'my-action_'.$post->ID );
```
这只是返回随机数本身。例如:`295a686963`
为了获得最大程度的保护,请确保表示操作的字符串尽可能具体。
#### 验证随机数
您可以验证在 URL、管理屏幕中的表单、AJAX 请求或其他上下文中传递的随机数。
验证从管理屏幕传递的随机数
要验证在管理屏幕中的 URL 或表单中传递的随机数,请调用`check_admin_referer()`指定表示操作的字符串。
例如:
```php
check_admin_referer( 'delete-comment_'.$comment_id );
```
此调用检查随机数和引荐来源网址,如果检查失败,则采取正常操作(使用“403 Forbidden”响应和错误消息终止脚本执行)。
如果您在创建随机数时未使用默认字段名称 ( `_wpnonce`),请指定字段名称。
例如:
```php
check_admin_referer( 'delete-comment_'.$comment_id, 'my_nonce' );
```
### 验证 AJAX 请求中传递的随机数
要验证 AJAX 请求中传递的随机数,请调用[check\_ajax\_referer()](https://developer.wordpress.org/reference/functions/check_ajax_referer/) 并指定表示操作的字符串。例如:
```php
check_ajax_referer( 'process-comment' );
```
此调用检查随机数(但不检查引用者),如果检查失败,则默认情况下它会终止脚本执行。
如果您在创建随机数时未使用默认字段名称(`_wpnonce`或)之一,或者如果您想采取其他操作而不是终止执行,则可以指定其他参数。`_ajax_nonce`详细信息请参见:`check_ajax_referer()`。
验证在其他上下文中传递的随机数
要验证在其他上下文中传递的随机数,请`wp_verify_nonce()`调用指定随机数和表示操作的字符串。
例如:
```php
wp_verify_nonce( $_REQUEST['my_nonce'], 'process-comment'.$comment_id );
```
如果结果为 false,则不再继续处理该请求。相反,采取一些适当的行动。通常的操作是调用`wp_nonce_ays()`,它会向浏览器发送“403 Forbidden”响应。
## 修改nonce系统
您可以通过添加各种操作和过滤器来修改随机数系统。
### 修改随机数生存期
默认情况下,随机数的生命周期为一天。此后,即使随机数与操作字符串匹配,随机数也不再有效。要更改生命周期,请添加一个 nonce\_life 过滤器,指定生命周期(以秒为单位)。
例如,要将生命周期更改为四小时:
```php
add_filter( 'nonce_life', function () { return 4 * HOUR_IN_SECONDS; } );
```
### 执行额外验证
`check_admin_referrer()`要在发现随机数和引荐来源网址有效时执行附加验证,请添加`check_admin_referer`操作。
例如:
```php
function wporg_additional_check ( $action, $result ) {
...
}
add_action( 'check_admin_referer', 'wporg_additional_check', 10, 2 );
```
以同样的方式`check_ajax_referer()`添加一个动作。`check_ajax_referer`
#### 更改错误消息
您可以使用翻译系统更改随机数无效时发送的错误消息。例如:
```php
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_KEY`和`NONCE_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_life`, `nonce_user_logged_out`, `explain_nonce_(verb)-(noun)`,`check_admin_referer`
# 用户角色和能力
# 用户角色和能力
如果您的插件允许用户提交数据(无论是在管理员端还是公共端),它应该检查用户功能。
#### 用户角色和能力
创建高效安全层的最重要步骤是建立用户权限系统。[WordPress 以用户角色和功能](https://developer.wordpress.org/plugins/users/roles-and-capabilities/)的形式提供这一点。
每个登录 WordPress 的用户都会根据其用户角色自动分配特定的用户功能。
**用户角色**只是一种表示用户属于哪个组的奇特方式。每个组都有一组特定的预定义功能。
例如,网站的主要用户将具有管理员的用户角色,而其他用户可能具有编辑者或作者等角色。您可以为一个角色分配多个用户,即一个网站可能有两个管理员。
**用户能力**是您分配给每个用户或用户角色的特定权限。
例如,管理员具有“manage\_options”功能,允许他们查看、编辑和保存网站的选项。另一方面,编辑者缺乏这种能力,这将阻止他们与选项进行交互。
然后在管理中的各个点检查这些功能。取决于分配给角色的能力;菜单、功能和 WordPress 体验的其他方面可能会被添加或删除。
**当您构建插件时,请确保仅在当前用户具有必要的功能时才运行您的代码。**
#### 等级制度
用户角色越高,用户拥有的能力越多。每个用户角色都会继承层次结构中先前的角色。
例如,“管理员”是单个站点安装中的最高用户角色,它继承以下角色及其功能:“订阅者”、“贡献者”、“作者”和“编辑者”。
#### 例子
#### 无限制
下面的示例在前端创建一个链接,该链接提供了垃圾帖子的功能。因为此代码不检查用户能力,所以**它允许该网站的任何访问者删除帖子!**
```php
/**
* 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 . ' ' . esc_html__( 'Delete Post', 'wporg' ) . '';
}
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`,只有编辑者或以上人员才具有:
```php
/**
* 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 . '
' . esc_html__( 'Delete Post', 'wporg' ) . '';
}
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[妈妈的功绩](https://xkcd.com/327/)
强化主题抵御 SQL 注入的第一条规则是:当有 WordPress 功能时,使用它。
但有时您需要执行 API 中未考虑到的复杂查询。如果是这种情况,请始终使用这些 [`$wpdb `功能](https://developer.wordpress.org/reference/classes/wpdb/)。这些是专门为保护您的数据库而构建的。
SQL查询中的所有数据在执行SQL查询之前都必须进行SQL转义,以防止SQL注入攻击。用于 SQL 转义的最佳函数是`$wpdb->prepare()`同时支持类似[sprintf()和类似](http://secure.php.net/sprintf)[vsprintf()](http://secure.php.net/vsprintf)的语法。
```php
$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 漏洞。由于主题的主要职责是输出内容,因此主题应 根据内容的类型使用适当的功能[转义动态内容。](https://developer.wordpress.org/themes/theme-security/data-sanitization-escaping/)
转义功能之一的示例是从用户配置文件中转义 URL。
```php
; ?>)
```
可以对包含 HTML 实体的内容进行清理,以仅允许指定的 HTML 元素。
```php
$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 的表单提交,请使用 [随机数](https://developer.wordpress.org/themes/theme-security/using-nonces/) 来保证用户打算执行操作。
```php
```
#### 保持最新状态
及时了解潜在的安全漏洞非常重要。以下资源提供了一个良好的起点:
- [WordPress 安全白皮书](https://wordpress.org/about/security/)
- [WordPress 安全发布](https://wordpress.org/news/category/security/)
- [开放 Web 应用程序安全项目 (OWASP) 前 10 名](https://www.owasp.org/index.php/OWASP_Top_Ten_Cheat_Sheet)
# 例子
#
例子
使用功能检查、数据验证、安全输入、安全输出和随机数的完整示例:
```php
/**
* 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 . '
' . esc_html__( 'Delete Post', 'wporg' ) . '';
}
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' );
}
}
```