WordPress Security Validation, Sanitization, Escaping and Nonces

Summary: Learning about WordPress Security, Learning about different Validation, Sanitization and Escaping functions in WordPress.

Validation

Validating input is the process of testing data against a predefined pattern (or patterns) with a definitive result: valid or invalid. Validation is a more specific approach when compared to sanitization, but both have their roles.

Validation Philosophies

Safelist: Accept data only from a finite list of known and trusted values.

$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' );
}

$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' );
}

$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' );
}


Blocklist: Reject data from a finite list of known untrusted values. Rarely recommended.

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

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

Format Correction: Accept most any data, but remove or alter the dangerous pieces.

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

Available Validation Functions

  • balanceTags( $html ) or force_balance_tags( $html ): Ensures HTML tags are balanced.
  • count(): Checks the number of items in an array.
  • in_array(): Checks whether something exists in an array.
  • is_email(): Validates an email address.
  • is_array(): Checks if something is an array.
  • mb_strlen() or strlen(): Checks the length of a string.
  • preg_match(), strpos(): Checks for occurrences of certain strings within other strings.
  • sanitize_html_class( $class, $fallback ): Sanitizes a HTML classname.
  • tag_escape( $html_tag_name ): Sanitizes an HTML tag name.
  • term_exists(): Checks if a tag, category, or taxonomy term exists.
  • username_exists(): Checks if a username exists.
  • validate_file(): Validates that an entered file path is a real path (not whether the file exists).

Sanitizing

Sanitizing input is the process of securing/cleaning/filtering input data. Validation is preferred over sanitization because validation is more specific. But when “more specific” isn’t possible, sanitization is the next best thing.

<input id="title" type="text" name="title">
$title = sanitize_text_field( $_POST['title'] );
update_post_meta( $post->ID, 'title', $title );

Process of sanitization

  • Checks for invalid UTF-8
  • Converts single less-than characters (<) to entity
  • Strips all tags
  • Removes line breaks, tabs, and extra white space
  • Strips octets

Sanitization Functions

  • sanitize_email(): Sanitizes an email address.
  • sanitize_file_name(): Sanitizes a file name.
  • sanitize_hex_color(): Sanitizes a hex color code with the hash (#).
  • sanitize_hex_color_no_hash(): Sanitizes a hex color code without the hash (#).
  • sanitize_html_class(): Sanitizes an HTML class name.
  • sanitize_key(): Sanitizes a key, converting it to a valid key string.
  • sanitize_meta(): Sanitizes metadata for a given object type.
  • sanitize_mime_type(): Sanitizes a MIME type.
  • sanitize_option(): Sanitizes an option value based on the option’s type.
  • sanitize_sql_orderby(): Sanitizes an SQL ORDER BY clause.
  • sanitize_term(): Sanitizes a taxonomy term.
  • sanitize_term_field(): Sanitizes a specific field of a taxonomy term.
  • sanitize_text_field(): Strips tags, removes line breaks, tabs, extra whitespace, and octets from a string.
  • sanitize_textarea_field(): Sanitizes content from a textarea input.
  • sanitize_title(): Sanitizes a string into a valid title.
  • sanitize_title_for_query(): Sanitizes a string into a valid title for queries.
  • sanitize_title_with_dashes(): Sanitizes a string into a valid title with dashes.
  • sanitize_user(): Sanitizes a username.
  • sanitize_url(): Sanitizes a URL.
  • wp_kses(): Filters content through a list of allowed HTML tags and attributes.
  • wp_kses_post(): Filters content allowed in post content and user-generated content.

Escaping

Escaping output is the process of securing output data by stripping out unwanted data, like malformed HTML or script tags. This process helps secure your data prior to rendering it for the end user. 

Escaping Functions

esc_html()

Use anytime an HTML element encloses a section of data being displayed. This will remove HTML.

<h4><?php echo esc_html( $title ); ?></h4>

esc_js()

Use for inline JavaScript.

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

esc_url()

Use on all URLs, including those in the src and href attributes of an HTML element.

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

esc_url_raw()

Use when storing a URL in the database or in other cases where non-encoded URLs are needed.

$stored_url = esc_url_raw( $url );

esc_xml()

Use to escape XML block.

<loc><?php echo esc_xml( $xml_data ); ?></loc>

esc_attr()

Use on everything else that’s printed into an HTML element’s attribute.

<ul class="<?php echo esc_attr( $stored_class ); ?>">

esc_textarea()

Use this to encode text for use inside a textarea element.

<textarea><?php echo esc_textarea( $data ); ?></textarea>

wp_kses()

Use to safely escape for all non-trusted HTML (post text, comment text, etc.). This preserves HTML.

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

wp_kses_post()

Alternative version of wp_kses() that automatically allows all HTML that is permitted in post content.

<?php echo wp_kses_post( $post_content ); ?>

wp_kses_data()

Alternative version of wp_kses() that allows only the HTML permitted in post comments.

<?php echo wp_kses_data( $comment_content ); ?>

Helper Functions That Combine Localization and Escaping

WordPress provides several helper functions that combine localization and escaping to ensure that your translated strings are safe and secure. Here are some examples of each function and how to use them:

esc_html__()

This function combines __() (for localization) with esc_html() (for escaping HTML). It returns a translated string with HTML characters escaped.

<?php
$translated_string = esc_html__( 'Hello World', 'text_domain' );
echo '<h4>' . $translated_string . '</h4>';
?>

esc_html_e()

This function combines _e() (for localization) with esc_html() (for escaping HTML). It directly echoes a translated string with HTML characters escaped.

<?php esc_html_e( 'Hello World', 'text_domain' ); ?>

esc_html_x()

This function combines _x() (for context-based localization) with esc_html() (for escaping HTML). It returns a translated string with context and HTML characters escaped.

<?php
$translated_string = esc_html_x( 'Hello', 'greeting', 'text_domain' );
echo '<h4>' . $translated_string . '</h4>';
?>

esc_attr__()

This function combines __() (for localization) with esc_attr() (for escaping attributes). It returns a translated string with attribute characters escaped.

<?php
$translated_string = esc_attr__( 'Hello World', 'text_domain' );
echo '<input type="text" value="' . $translated_string . '">';
?>

esc_attr_e()

This function combines _e() (for localization) with esc_attr() (for escaping attributes). It directly echoes a translated string with attribute characters escaped.

<?php esc_attr_e( 'Hello World', 'text_domain' ); ?>

esc_attr_x()

This function combines _x() (for context-based localization) with esc_attr() (for escaping attributes). It returns a translated string with context and attribute characters escaped.

<?php
$translated_string = esc_attr_x( 'Hello', 'greeting', 'text_domain' );
echo '<input type="text" value="' . $translated_string . '">';
?>

Nonces

A nonce (number used once) helps protect URLs and forms from certain types of misuse, malicious or otherwise. WordPress nonces are a hash made up of numbers and letters, used to protect against several types of attacks, including CSRF. However, they do not protect against replay attacks and should not be relied on for authentication, authorization, or access control. Always use current_user_can() for those purposes.

Why Use a Nonce?

Nonces prevent unauthorized actions. For example, a URL to trash a post might look like:

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

Without a nonce, an attacker could exploit this URL by embedding it in a hidden element, causing unintended actions. Adding a nonce to the URL prevents such misuse:

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

Creating a Nonce

You can create a nonce and add it to a URL, form, or use it in other contexts. Nonces are unique to the current user’s session and expire after a set period.

Adding a nonce to a URL:

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

You can specify a different field name:

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

Adding a nonce to a form:

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

This generates two hidden fields:

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

You can customize the field name and other parameters:

wp_nonce_field( 'delete-comment_'.$comment_id, 'my_nonce', false, true );

Creating a nonce for other uses:

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

Verifying a Nonce

Verifying a nonce from an admin screen:

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

Specify the field name if not using the default:

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

Verifying a nonce in an AJAX request:

check_ajax_referer( 'process-comment' );

Verifying a nonce in other contexts:

if ( ! wp_verify_nonce( $_REQUEST['my_nonce'], 'process-comment'.$comment_id ) ) {
    wp_nonce_ays( 'process-comment' );
}
«
»

Leave a Reply

Your email address will not be published. Required fields are marked *