An in-depth guide on using Ajax with WordPress

Data Validation in WordPress

Data validation in WordPress is extremely important. It’s a matter of principle that you must escape and sanitize all data going to and from the database. Furthermore, you shouldn’t trust GET and POST variables no matter the source.

Here’s a motto that I will repeat here: “Do not trust anyone, especially yourself.”

In this chapter, I’ll go over the numerous ways you can protect yourself from data from third-party sources and also data that you may have entered.

Data Sanitization

Data sanitization is when you want to sanitize user input before storing it. Whether you retrieved the data from an API or via a form, it’s very important to sanitize the data.

Fortunately, WordPress has quite a few helper functions to sanitize input. We’ll look at a couple in detail, but here’s a list:

  • sanitize_email: strips out all characters not allowed in an email.
  • sanitize_file_name: ensures a file name is valid.
  • sanitize_html_class: ensures an HTML class name is valid.
  • sanitize_key: sanitizes a string key.
  • sanitize_meta: sanitizes a meta value.
  • sanitize_mime_type: ensures a valid mime type.
  • sanitize_option: sanitizes an option value.
  • sanitize_sql_orderby: ensures a string is a valid ‘order by’ attribute for SQL queries.
  • sanitize_text_field: sanitizes a string from a user or from the database.
  • sanitize_title: ensures the string is returned in a title (slug) format.
  • sanitize_title_for_query: like sanitize_title, but for a query context.
  • sanitize_title_with_dashes: returns a title with dashes for whitespace and special characters.
  • sanitize_user: ensures a username is valid before storing it in the database.
  • esc_url_raw: ensures a valid URL and preserves query strings.
  • wp_filter_post_kses: useful when sanitizing HTML input.
  • wp_filter_nohtml_kses: strips out all HTML.

My personal favorite from the above sanitization methods is sanitize_text_field. I simply use it all the time. Here’s an example of storing text in an option.

$url = add_query_arg(
	array(
		'nonce' => wp_create_nonce( 'store-text-option' ),
		'text'  => 'this is a test',
	)
);
?>
<a href="<?php echo esc_url( $url ); ?>">Click Me</a>
<?php
if ( isset( $_REQUEST['text'] ) ) {
	$nonce = sanitize_text_field( filter_input( INPUT_GET, 'nonce' ) );
	if ( ! wp_verify_nonce( $nonce, 'store-text-option' ) ) {
		wp_die( 'Nonce not verified' );
	}
	$text = sanitize_text_field( filter_input( INPUT_GET, 'text' ) );

	// Store text as an option.
	update_option( 'my_option_name', $text );
	echo esc_html( $text );
}

In the above example, we’re using two escaping functions that we will cover in the next section: esc_url (which ensures a valid URL) and esc_html (strips out all HTML). We’re also using filter_input to retrieve the GET variables. Don’t trust this data. Always sanitize and escape everything you receive from POST or GET variables, which is shown in the above example.

Here’s that same example with sanitize_title if we were to use the text to update a slug.

$url = add_query_arg(
	array(
		'nonce' => wp_create_nonce( 'store-text-option' ),
		'text'  => 'this is a test',
	)
);
?>
<a href="<?php echo esc_url( $url ); ?>">Click Me</a>
<?php
if ( isset( $_REQUEST['text'] ) ) {
	$nonce = sanitize_text_field( filter_input( INPUT_GET, 'nonce' ) );
	if ( ! wp_verify_nonce( $nonce, 'store-text-option' ) ) {
		wp_die( 'Nonce not verified' );
	}
	$text = sanitize_title( filter_input( INPUT_GET, 'text' ) );

	// Store text as an option.
	update_option( 'my_option_name', $text );
	echo esc_html( $text );
}

The output for $text would be: this-is-a-test.

What if someone were to send you some HTML in a GET variable? We’d either want to escape it or sanitize it before displaying the output. Here’s an example of what not to do and demonstrates a severe vulnerability:

<form method="POST">
	<input type="text" name="shady_text" />
	<input type="submit" value="Submit" />
</form>
<?php
if ( isset( $_REQUEST['shady_text'] ) ) {
	$text = wp_unslash( $_POST['shady_text'] );
	echo $text;
}

What if I placed the following into the input field?

<strong>this is text</strong><script>alert('fail');</script>

You’d receive an alert, wouldn’t you? This is what’s considered a Cross Site Scripting (XSS) vulnerability. Let’s clean it up and make sure the output is valid.

<form method="POST">
	<?php wp_nonce_field( 'form-field-validation', 'form_field' ); ?>
	<input type="text" name="shady_text" />
	<input type="submit" value="Submit" />
</form>
<?php
if ( isset( $_REQUEST['shady_text'] ) ) {
	$nonce = sanitize_text_field( filter_input( INPUT_POST, 'form_field' ) );
	if ( ! wp_verify_nonce( $nonce, 'form-field-validation' ) ) {
		wp_die( 'Nonce not verified' );
	}
	$text = sanitize_text_field( filter_input( INPUT_POST, 'shady_text' ) );
	echo esc_html( $text );
}

Now if I enter the JavaScript injection, and enter HTML, it just outputs: this is text.

What if we do want HTML to be outputted? Instead of sanitize_text_field, we can use wp_filter_post_kses.

<form method="POST">
	<?php wp_nonce_field( 'form-field-validation', 'form_field' ); ?>
	<input type="text" name="shady_text" />
	<input type="submit" value="Submit" />
</form>
<?php
if ( isset( $_REQUEST['shady_text'] ) ) {
	$nonce = sanitize_text_field( filter_input( INPUT_POST, 'form_field' ) );
	if ( ! wp_verify_nonce( $nonce, 'form-field-validation' ) ) {
		wp_die( 'Nonce not verified' );
	}
	$text = wp_filter_kses_post( filter_input( INPUT_POST, 'shady_text' ) );
	echo wp_kses_post( $text );
}

Now the output would be: this is textalert(\'fail\');. We’ve succeeded in preventing an XSS attack.

I implore you to explore all of the sanitize-* functions to get a good handle on them. They are extremely useful for the purposes of data sanitization. Let’s move onto data escaping.

Data Escaping

Data escaping in WordPress is kinda a philosophy: escape late, and escape always. So what in the heck does that mean? Any time you are outputting something to the user, make sure the data is escaped and escape right at the moment of output. Let’s move onto some examples to make the issue clearer.

Let’s take a simple input box. It accepts a query variable (similar to how the search function works) and outputs it to the user.

Sample Form Input Field
Sample Form Input Field

Here’s the code for the form input:

<form method="GET">
	<input type="text" name="shady_text" value="<?php echo isset( $_GET['shady_text'] ) ? $_GET['shady_text'] : ''; ?>" />
	<input type="submit" value="Submit" />
</form>

Note that we are outputting directly the contents of the GET variable shady_text. Now I’m going to pass it:

http://localhost/wordpress/sample-page/?shady_text="this is my text"

As a result, I have now broken the input. Let’s use an escaping function to make sure the input is what we expect.

<form method="GET">
	<input type="text" name="shady_text" value="<?php echo isset( $_GET['shady_text'] ) ? esc_attr( wp_unslash( $_GET['shady_text'] ) ) : ''; ?>" />
	<input type="submit" value="Submit" />
</form>

Ideally, we’d do a nonce check here as well. However, notice the presence of a WordPress function called esc_attr. It escapes attributes to ensure the quotes don’t get in the way and prevents any type of injection from an untrusted source. Now our input looks like this:

Sample  Data Validation Form Input Field
Sample Data Validation Form Input Field

Perfect. That is just one of many types of output escaping. Let’s look at a few more.

If you are simply outputting to the world, then esc_html is your friend. It ensures that all output and HTML tags are converted to the proper entities.

<?php echo esc_html( $untrusted_variable ); ?>

Or better yet…

<?php
$untrusted_variable = '<strong>My Text</strong>';
echo esc_html( $untrusted_variable );

// Output would be:
// <strong>My Text</strong>

The next one is called esc_url. It should be used anytime you come across an href or src attribute.

<?php
<img src="<?php echo esc_url( $untrusted_url ); ?>" />
<a href="<?php echo esc_url( $untrusted_url ); ?>">Link Text</a>

The esc_js function is used for inline escaping.

<a href="#" onclick="<?php echo esc_js( $custom_js ); ?>">Click me</a>

The get_search_query function is used for outputting a search query to an input box. Internally, it already runs esc_attr, so we’re covered there.

<input type="text" value="<?php echo get_search_query(); ?>" />

The esc_textarea is useful for outputting a block of text to a textarea element.

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

Finally, there are the translation escape functions:

  • esc_html__
  • esc_html_e
  • esc_html_x
  • esc_attr__
  • esc_attr_e
  • esc_attr_x

I’m not saying don’t trust your translators… okay, yes I am.

If you do need to output HTML, then wp_kses_post will work for you.

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

If you need more fine-grained detail of what HTML you can output, you can customize the wp_kses function.

<?php
$allowed_html = array(
	'a'      => array(
		'href'  => array(),
		'title' => array(),
	),
	'strong' => array(),
);
echo wp_kses( $custom_content, $allowed_html );

The above example above will only output the anchor element with attributes href and title. It also allows for the strong element.

Here’s another example where the only allowed output is an iframe with custom style attributes.

<?php
add_filter( 'safe_style_css', 'safe_css' );
$allowed_tags = array(
	'iframe'   => array(
		'src'    => true,
		'style'  => true,
		'width'  => true,
		'height' => true,
	),
	'noscript' => array(
	),
	'script'   => array(
		'data-cfasync' => true
	),
	'style' => array(
	)
);
echo wp_kses( $html, $allowed_tags );
remove_filter( 'safe_style_css', 'safe_css' );
function safe_css( $css = array() ) {
	$css[] = 'display';
	$css[] = 'visibility';
	return $css;
}

Data Validation

Finally, I’ll cover data validation. It’s useful for ensuring the data is the type of data you’re expecting to receive.

For example, what if we wanted to check if someone submitted a valid email address? We can use the is_email WordPress function.

<?php
if ( is_email( $email_address ) ) {
	// Email is valid. Do stuff here.
}

How about integers? One of my favorite WordPress functions is called absint. It ensures that the data passed will output with a positive absolute integer.

What if we’re accepting usernames from a form and want to alert the user that the username is taken? We can use username_exists.

We can additionally use core PHP functions to perform some data validation:

  • isset
  • empty
  • strlen
  • count
  • is_array
  • in_array
  • is_numeric
  • intval
  • And so much more…

Wrapping Up

In this chapter, we covered data sanitization, data escaping, and finally, data validation. In the next chapter, I’ll go over how to set up your first Ajax request.

Leave a Comment

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

Scroll to Top