WordPress Plugin Development Tutorial Manual - Plugin Security
Congratulations, your code passed the proficiency test, but is the plugin code secure? How do plugins protect users from attacks if their site is targeted by hackers? The plugins in the WordPress.org Plugin Catalog have done a lot of work on the security side of things to ensure that users' information is safe.
Remember that your code may run on millions of WordPress sites, so security is critical. In this chapter, we'll cover how to check user capabilities, validate input, clean up input, clean up output, and create and validate random numbers.
Quick Reference
ferret out Full Examples of Security Best Practices for WordPress Plugins and ThemesThe
External resources
- Jon Cave:How to Fix Vulnerable Plugins
- Mark Jaquith: Theme and plugin security
Checking user capacity
If your plugin allows users to submit data (either admins or visitors), be sure to check user permissions.
User roles and competencies
Create an efficientsafety protectionOne of the important steps is to create a system of user capabilities, which WordPress provides in the form of user roles and capabilities.
Every user who logs into WordPress is automatically assigned a corresponding capability based on their role.
user roleIt's actually a visualization of user groupings, each of which has some specific predefined capabilities.
For example, the main user of the site will have the role of administrator, and other users will have the role of "editor" or "author". We can assign multiple users to a single role, which means that there can be multiple administrators for a WordPress site.
user capabilitiesare specific permissions assigned to each user or user role.
For example, an administrator has the "manage_options" capability, which gives the administrator permission to view, edit, and save site options. Editors or other users who do not have this ability cannot perform these actions.
WordPress checks user capabilities in various locations in the backend based on the capabilities assigned to the role, and menus, features, and other parts of WordPres are added or removed based on the results of these checks.
user hierarchy
The higher the user's character, the more abilities they have, and each character inherits all the abilities of the character one level lower in the hierarchy.
For example, the most powerful "Administrator Role" in a WordPress single site will automatically have all the capabilities of "Subscriber", "Contributor", "Author" and "Editor". "Author" and "Editor".
typical example
blanket
The following example creates a link on the front end that allows users to move articles to the recycle bin. Because there is no check for user capabilities, the code below allows all users who visit the site to use this link to move the site's articles to the recycle bin.
<?php
/**
* 在前端创建一个删除文章的链接
*/
function wporg_generate_delete_link($content)
{
// 只在单文章页面运行
if (is_single() && in_the_loop() && is_main_query()) {
// 添加查询参数: action, post
$url = add_query_arg(
[
'action' => 'wporg_frontend_delete',
'post' => get_the_ID(),
],
home_url()
);
return $content . ' <a href=' . esc_url($url) . '>' . esc_html__('Delete Post', 'wporg') . '</a>';
}
return null;
}
/**
* 处理用户请求
*/
function wporg_delete_post()
{
if (isset($_GET['action']) && $_GET['action'] === 'wporg_frontend_delete') {
// 检查请求中是否有文章参数
$post_id = (isset($_GET['post'])) ? ($_GET['post']) : (null);
// 检查请求的文章是否存在
$post = get_post((int)$post_id);
if (empty($post)) {
return;
}
// 删除文章
wp_trash_post($post_id);
// 跳转到文章管理界面
$redirect = admin_url('edit.php');
wp_safe_redirect($redirect);
// 退出
die;
}
}
/**
* 添加删除文章的链接到文章内容后面
*/
add_filter('the_content', 'wporg_generate_delete_link');
/**
* WordPress 初始化时注册请求处理函数
*/
add_action('init', 'wporg_delete_post');
Limit to specified capacity
The above example allows any user who visits the site to click on the "Delete" link to move the article to the recycle bin. What we need is that only users with editing power can see the link to allow deletion of the article, even if other users are aware of the link and access it manually, it's useless.
Let's modify the code above slightly to check if the user has the "edit_others_posts" capability when displaying the link, which is only available to users with the role of editor or above in WordPress' default role capability system.
<?php
if (current_user_can('edit_others_posts')) {
/**
* Add a link to the deleted post to be followed by the post content
*/
add_filter('the_content', 'wporg_generate_deleted_link');
/**
* Register the request handler function when WordPress is initialized.
*/
add_action('init', 'wporg_delete_post');
}
data validation
Data validation is the process of analyzing data according to one or more predefined rules, and there are only two results of data validation, valid or invalid. Data validation is often used to process externally transferred in data, such as user input and web service data via API calls.
Simple example of data validation:
- Check if the mandatory field is empty
- Check that the entered phone number contains only numbers and symbols
- Check that the zip code is a valid postal code
- Check if the quantity field is greater than 0
- Check if Email is a valid Email address
Data validation should be implemented as early as possibleThat is, we have to verify the validity of the data before performing other operations.
Validation Data
In WordPress, we have at least 3 ways to validate data: PHP built-in functions, WordPress core functions and functions you write yourself.
Built-in PHP Functions
Basic data validation can be performed using a number of PHP built-in functions, including:
- isset() and empty() can be used to check if a variable exists.
- mb_strlen() or strlen() can be used to check if a string is as long as required.
- preg_match(), strpos() are used to check if a string contains certain specified strings
- count() is used to check how many elements are in the array
- in_array() is used to check if an element exists in an array
WordPress core validation functions
WordPress provides us with many functions to help us validate various types of data, such as:
- is_email() Verifies that the e-mail address is valid.
- term_exists() Checks if the categorized item exists.
- username_exists() checks if a username exists
- validate_file() checks if the input file path is a real path (not checking if the file exists)
We can find out more about this in the WordPress Code ReferenceSearch for similar *_exists()
(math.) genus*_validate()
peace is_*()
Names like these are used to find other data validation functions. Not all functions that contain these names are data validation functions, but a large percentage of them can help us validate data.
Customizing PHP and JavaScript Functions
We can write our own PHP and JavaScript functions and include them in our themes or plugins. When writing custom validation functions, we can name them according to semanticization rules, such as is_phone, is_avaliable, is_zipcode, and so on.
These validation functions should return a boolean value, either true or fase depending on whether the validation passes or not, and we can use these functions directly as if conditions.
Example 1
Suppose we need to verify that a user submitted the correct US zip code.
<input id=wporg_zip_code type=text maxlength=10 name=wporg_zip_code>
The text field above allows up to 10 characters, there are no restrictions on the types of characters that can be entered, the user can enter something valid like 1234567890, or something else that is invalid or unsuspecting.
The maxlength validation attribute of input is enforced by the browser, if the browser doesn't support this attribute, this validation condition will not be enforced or the user can make some changes to the data before it reaches the server. So, even if the validation is performed on the front-end, we still need to validate the length of the data on the server.
By validating, we can ensure that only valid US zip codes are accepted. First, we need to write a function to validate US ZIP codes.
10) {
return false; }
}
// Verify 3: the data is formatted correctly
if (!preg_match('/^\d{5}(\-? \d{4})?$/', $zip_code)) {
return false;
}
// Validate if, return true
return true; }
}
When processing the form, our code should first check the correctness of the wporg_zip_code field and then perform the action based on the validation result.
if (isset($_POST['wporg_zip_code']) && is_us_zip_code($_POST['wporg_zip_code'])) {
// Action to be performed
}
Example 2
Suppose we need to query some articles in a database and can allow the user to sort the query results.
The following sample code checks that the incoming value is sortable by using PHP's built-in function in_array to compare the incoming sort key (stored in the order_by parameter) with an array of keys that allow sorting, which prevents users from passing in malicious data to compromise the site.
Before comparing it to an array of keys, we'll use WordPress' built-in function sanitize_key to handle user input, which ensures that keys are lowercase (the in_array function is case-sensitive).
Setting "true" as the third argument to in_array tells the in_array
Performs strict type checking, comparing not only the value but also the type of the value, which ensures that what is passed in is a string and not some other data type.
<?php
$allowed_keys = ['author', 'post_author', 'date', 'post_date'];
$orderby = sanitize_key($_POST['orderby']);
if (in_array($orderby, $allowed_keys, true)) {
// Modify the query to sort by the order_by key
}
Secure Input
Ensuring secure input is the process of cleaning (purifying, filtering) the data entered by the user. We can use data sanitization measures if we don't know the exact type of data or if we don't want strict data validation.
Cleaning of data
The easiest way to clean up your data is to use WordPress' built-in functions.
WordPress provides us with some sanitize_*() helper functions to help us make sure that we end up with safe data, and using these functions we can easily sanitize the data.
- sanitize_email()
- sanitize_file_name()
- sanitize_html_class()
- sanitize_key()
- sanitize_meta()
- sanitize_mime_type()
- sanitize_option()
- sanitize_sql_orderby()
- sanitize_text_field()
- sanitize_title()
- sanitize_title_for_query()
- sanitize_title_with_dashes()
- sanitize_user()
- esc_url_raw()
- wp_filter_post_kses()
- wp_filter_nohtml_kses()
typical example
Suppose we have an input field named title.
<input id=title type=text name=title>
We can use the sanitize_text_field() function cleans up the input data:
$title = sanitize_text_field($_POST['title']);
update_post_meta($post->ID, 'title', $title);
Behind the scenes, sanitize_text_field() performs the following actions:
- Check for invalid UTF-8 characters
- Convert greater-than and less-than characters (><) to entities
- Remove all HTML tags
- Remove line breaks, tabs and extra whitespace characters
- Delete all 8-byte characters
safe output
Secure output is the process of escaping data output.
Escaping also means removing unwanted data, such as badly formatted HTML or script tags.
Whenever you render data, make sure you escape it; escaping the output prevents XSS (cross-site scripting) attacks.
transferred meaning
Escaping helps to protect our data before it is finally presented to the user, and WordPress provides several helper functions that can help us handle most situations that require escaping.
- esc_html() - Use this function when displaying HTML.
- esc_url() - Use this function when outputting URLs, including in the
src
respond in singinghref
URL in the attribute. esc_js()
- Use this function for inline JavaScript.- esc_attr() - This capability is used when setting data as an HTML element attribute.
Using Localization Functions
Instead of using echo to output data, we should make more use of WordPress's localization capabilities, such as _e() maybe __()
The following localization function esc_html_e function integrates the ability to escape data.
esc_html_e( 'Hello World', 'text_domain' );
// Equivalent to
echo esc_html( __( 'Hello World', 'text_domain' ) );
Functions that combine localization and escaping capabilities are:
Customized Escape
In cases where the output needs to be escaped in a special way, we we can use the function wp_kses() to implement custom escaping capabilities, this function ensures that only specified HTML elements, attributes, and attribute values appear in the output, and normalizes HTML entities.
$allowed_html = [
'a' => [
'href' => [],
'title' => [],
],
'br' => [],
'em' => [],
'strong' => [], .
]; echo wp_content
echo wp_kses( $custom_content, $allowed_html );
wp_kses_post() is a wrapper for the wp_kses function, where$allowed_html
is a set of rules used to display the content of an article.
echo wp_kses_post( $post_content );
random number verification
For security purposes, random number validation helps us verify the source and intent of a request, and each random number can only be used once.
If our plugin allows a user to submit data, either an administrator or a visitor, we must ensure that the user is the one performing the action and that they have the ability to perform that action, and the two together validate to ensure that the data only changes if the user wants it to.
Use of random numbers
existChecked the capacity of usersAfter the example, the next step in enhancing the security of user submission data is to use random number validation. The user capability check ensures that only users with the ability to delete an article can do so, but what if someone tricked you into clicking that link? You have the ability to delete the article, but you did so without intent, but without realizing it.
We can use random numbers to check whether the action that the current user intends to perform is the true intention of the user. When generating links, we can use the wp_create_nonce() function adds a random number to the link, the argument passed to the function ensures that the random number created is unique for the operation.
Then, when processing the deletion request, we can check that the random number matches what we expect.
For more information on random numbers, take a look at Mark Jaquith's article on WordPress Random Numbers The article , which is a good resource.
Full Example
Below is a complete example of using competency checking, data validation, secure input, safe input, and random number validation.
<?php
/**
* 在前端创建一个删除文章的链接
*/
function wporg_generate_delete_link($content){
// 只在单文章页面运行
if (is_single() && in_the_loop() && is_main_query()) {
// 添加查询参数: action, post
$url = add_query_arg(
[
'action' => 'wporg_frontend_delete',
'post' => get_the_ID(),
'nonce' => wp_create_nonce('wporg_frontend_delete'),
],
home_url()
);
return $content . ' <a href=' . esc_url($url) . '>' . esc_html__('Delete Post', 'wporg') . '</a>';
}
return null;
}
/**
* 处理用户请求
*/
function wporg_delete_post(){
if (
isset($_GET['action']) &&
isset($_GET['nonce']) &&
$_GET['action'] === 'wporg_frontend_delete' &&
wp_verify_nonce($_GET['nonce'], 'wporg_frontend_delete')
) {
// 检查请求中是否有文章参数
$post_id = (isset($_GET['post'])) ? ($_GET['post']) : (null);
// 检查请求的文章是否存在
$post = get_post((int)$post_id);
if (empty($post)) {
return;
}
// 删除文章
wp_trash_post($post_id);
// 跳转到文章管理界面
$redirect = admin_url('edit.php');
wp_safe_redirect($redirect);
// 退出
die;
}
}
if (current_user_can('edit_others_posts')) {
/**
* 添加删除文章的链接到文章内容后面
*/
add_filter('the_content', 'wporg_generate_delete_link');
/**
* WordPress 初始化时注册请求处理函数
*/
add_action('init', 'wporg_delete_post');
}