Posts tagged ‘form’

One of the easier to understand vulnerabilities is the CSRF. It’s also one of the most common issues we see in plugins and themes, because people rarely think about it.

Imagine that I have a form that takes input, like so:

<form action="http://example.com/example.php" method="GET">
<input type="text" name="demo" />
</form>

Now, that’s a simple form (and missing a submit button to boot), but you get the idea. It takes a text input. Presumably, something on the other end (at /example.php) processes that input, saves it in a database, something like that. Easy.

First question: Is this necessary?

The main question I see asked when this concept is explained to people is “why is this necessary?”. Some people believe that since you have to be logged in to access admin screens in the first place, then you can’t get to the forms and submit them. Why have all this protection and checking for a form submission when the form is hidden behind a login screen?

What you need to understand is the difference between “authority” and “intent“.

Authority

In real world cases where we are processing that input, we generally want to limit who is allowed to submit that form in some way. A plugin will want to only allow admins to change settings. A theme will only want to allow site owners to adjust the display of the site. Things of that nature. For these cases, we use methods of authentication.

There’s several ways to do this, we can check the current_user information. WordPress has capability checks for users to know what they are and are not allowed to do. When we check these, we’re verifying authority. Making sure that the user is allowed to do these things.

But something else that we need to check which most people don’t think about is intent. Did the user actually intend to submit that form, or did their browser submit it for them automatically, perhaps without their knowledge?

Examine that form again, and consider what would happen if you were to visit a webpage, anywhere on the internet, that contains this:

<img src="http://example.com/example.php?demo=pwned" />

Now, you might be thinking that this is a rather contrived example, and you’d be right on that score, but it serves to demonstrate the point. Your browser loads this URL and that is the equivalent action to submitting that form, with “pwned” as the text in question.

Here’s the kicker, all those authority checks do us no good in preventing this. You actually do have the authority to submit that form, and your browser, using your authority, just submitted it for you. Pwned, indeed.

(For those of you thinking “just use POST forms”, consider that javascript can submit POST forms. So that’s really no help.)

Intent

What we need is to verify intent. We need to know that the user submitted that form, and not just the browser doing it for them automatically.

WordPress used to do this (a looong time ago) using the referer. For those who don’t know, referer is a URL passed by your browser to indicate where a user came from. So one could check that the referer says that the form was submitted from the form’s page and not from some other page on the internet. The problem is that referer is not reliable. Some browsers have the ability for script to fake the referer. Firewalls and proxies often strip the referer out, for privacy concerns. And so forth.

Nonces

WordPress now does this using nonces. A nonce is a “number used once” in its purest form. Basically, it’s a one-time password. When we generate the form, we generate a number. When the form is submitted, we check the number. If the number is wrong or missing, we don’t allow the form to be submitted. A script cannot know the number in advance. Other sites cannot guess the number.

Now, technically, WordPress doesn’t use real nonces, because they’re not “used once”. Instead, WordPress nonces revolve on a 12 hour rotating system (where 24 hours are accepted). For any given 12 hour period, the nonce number for a given action will be the same. But it’s close enough to a real nonce to eliminate the issue, but notably it’s only for the issue of verifying intent. Don’t try to use WordPress nonces for anything else. 🙂

So, when we generate a form, we generate a nonce. This nonce is based on five things: site, user, time, the action being performed, and the object that the action is being performed on. Changing any of these gives us a different nonce.

Let’s say I want to delete a post. To do that, I need to know the nonce for deleting that specific post, as me, on my site, within the last 24 hours. Without that nonce, I cannot perform the action. More importantly, in order for somebody to “trick” my browser into doing it for me, they need to get that specific nonce and get my browser to load it within 24 hours. Tough to do. And even if they pull it off, they only have been able to perform that very specific action, the nonce obtained is useless for any other purpose. They don’t get any form of full control via this manner. They can’t make my browser do anything on mysite that they don’t have the nonce for.

Using nonces

So, let’s get down to brass tacks. Generating a nonce in WordPress is easy and can be done in many different ways depending on your particular needs. You might want to protect a simple link, or you might want to protect a form, or you might even need to protect a javascript ajax call.

Protecting a link can be done with wp_nonce_url(). It takes a URL and an action and adds a valid nonce onto that URL. It works like this:

$nonced_url = wp_nonce_url( $url, 'action_'.$object_id );

Here, we’re taking some URL, and adding a nonce onto it for a specific action on some specific object. This is important, actions and objects need to both be specified if there is some object being referred to. An example might be a link to delete a specific post. Such code would look like this:

wp_nonce_url( $url, 'trash-post_'.$post->ID )

The action is “trash-post” and the post being trashed has its ID number appended to that action. Thus, the nonce will let you trash that post and only that post.

On the other hand, maybe we have a form that we need to protect instead. Inside that form, we can add something like this:

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

This is the nonce for deleting a comment. It outputs a couple of form fields, like so:

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

The value for the nonce will be specific to deleting that comment, on that site, by that user.

Sometimes we just need to generate the nonce directly, in no specific format. One case might be for an AJAX type call, where the data is being submitted by jQuery. In such a case, you can use the wp_create_nonce function to get just that nonce value, like so:

wp_create_nonce( 'action_'.$object_id );

For AJAX requests, you’ll want to include that nonce value in the submitted data with a name of “_ajax_nonce”. Why that particular name? Because it’s what WordPress checks when verifying the nonce. Speaking of verification:

Verifying nonces

Generating these numbers is no good if you don’t check them as well. Fortunately, WordPress makes this easy. There’s two functions to verify incoming nonces.

check_admin_referer( 'action_'.$object_id );

The name of the function refers back to the time before nonces, when this function call was checking the referer value from the browser. Nowadays, it checks nonces instead. If the _wpnonce sent back in the form does not match the action and ID here, then this function stops further processing. This is the cause of the “Are you sure you want to do this?” screen that is sometimes reported by users. To avoid getting this screen, the nonce being checked has to match.

An alternative to checking forms or links is checking ajax requests, which is why we have this function:

check_ajax_referer( 'action_'.$object_id );

This performs the same basic check, but if it fails, it returns a simple “-1” response and then halts processing. Your AJAX javascript code can recognise that response and take appropriate action based on it.

In either case, if the nonce fails, the script exits. No action is taken. The form is not processed, the post not deleted. That’s the sort of check you need to prevent CSRF attacks.

Bottom Line

If you have a plugin or a theme or any type of code that “does something” in WordPress, then you need to protect that action with a nonce. If you’re not protecting it with a nonce, then it’s possible for somebody else to trick your browser into performing that action on your behalf.

Also, note that it’s not enough to just name the action. You generally are taking action on some specific “thing”, and the ID of that thing needs to be included in your nonce as well. The more specific the action, the better.

Any form, any action, no matter how much “authentication” you have on checking it, can be exploited, because you’re not really authenticating the “user”, you’re authenticating that it’s coming from “the user’s browser”. You need to have something else that changes regularly, so that you can verify that the user did indeed load that particular form and submit it relatively recently, and thus probably intended to perform that action.

Nonces are easy to implement. So do it already. We have enough plugins not doing it that this clearly needs to be said. 🙂

Shortlink:

Google came out with an experimental specification for websites to provide “hints” on forms, to allow things like autocomplete to work better and be more standardized. Seems useful.

Here’s a quick plugin snippet you can use to make your comments form use this specification. Only Chrome 15 and up is using this at the moment, but in the long run I think most browsers will be implementing something similar to this, since filling out forms is just one of those endless pieces of drudgery that we could all stand to do away with.

Note that your theme will need to be using the comment_form() function call for this to work, otherwise you’ll just have to edit your comment form in the theme manually.

<?php
/*
Plugin Name: Add X-Autocomplete Fields to Comment Form
*/
add_filter('comment_form_default_fields','add_x_autocompletetype');
function add_x_autocompletetype($fields) {
	$fields['author'] = str_replace('<input', '<input x-autocompletetype="name-full"', $fields['author']);
	$fields['email'] = str_replace('<input', '<input x-autocompletetype="email"', $fields['email']);
	return $fields;
}

Simple little bit of code, really. Should work with any theme using the normal comment_form() function call.

Shortlink:

WordPress 3.0 has something very handy that I want theme authors to start implementing as soon as possible.

To show exactly why it’s so useful, I modified my own theme to start using it.

Demonstration

So, here’s a big hunk of code I pulled out of my current theme’s comments.php. This hunk of code has only one purpose: To display the form area where people can leave a comment:

<?php if ('open' == $post->comment_status) : ?>

<div id="respond">

<h3><?php comment_form_title( 'Leave a Reply', 'Leave a Reply to %s' ); ?></h3>

<div class="cancel-comment-reply">
	<small><?php cancel_comment_reply_link(); ?></small>
</div>

<?php if ( get_option('comment_registration') && !$user_ID ) : ?>
<p>You must be <a href="<?php echo get_option('siteurl'); ?>/wp-login.php?redirect_to=<?php echo urlencode(get_permalink()); ?>">logged in</a> to post a comment.</p>
<?php else : ?>

<form action="<?php echo get_option('siteurl'); ?>/wp-comments-post.php" method="post" id="commentform">

<?php if ( $user_ID ) : ?>

<p>Logged in as <a href="<?php echo get_option('siteurl'); ?>/wp-admin/profile.php"><?php echo $user_identity; ?></a>. <a href="<?php echo wp_logout_url(get_permalink()); ?>" title="Log out of this account">Log out &raquo;</a></p>

<?php else : ?>

<p><input type="text" name="author" id="author" value="<?php echo $comment_author; ?>" size="22" tabindex="1" />
<label for="author"><small>Name <?php if ($req) echo "(required)"; ?></small></label></p>

<p><input type="text" name="email" id="email" value="<?php echo $comment_author_email; ?>" size="22" tabindex="2" />
<label for="email"><small>Mail (will not be published) <?php if ($req) echo "(required)"; ?></small></label></p>

<p><input type="text" name="url" id="url" value="<?php echo $comment_author_url; ?>" size="22" tabindex="3" />
<label for="url"><small>Website</small></label></p>

<?php endif; ?>

<!--<p><small><strong>XHTML:</strong> You can use these tags: <code><?php echo allowed_tags(); ?></code></small></p>-->

<p><textarea name="comment" id="comment" cols="100%" rows="10" tabindex="4"></textarea></p>

<p><input name="submit" type="submit" id="submit" tabindex="5" value="Submit Comment" />
<?php comment_id_fields(); ?>
</p>
<?php do_action('comment_form', $post->ID); ?>

</form>

<?php endif; // If registration required and not logged in ?>
</div>
<?php endif; // if you delete this the sky will fall on your head ?>

Nasty, eh? It’s a mess of if/else statements. It handles cases where the user is logged in or not, where the comments are open or closed, whether registration is required, etc. It’s confusing, difficult to modify, poor for CSS referencing…

Here’s what I replaced all that code with:

<?php comment_form(); ?>

Now then, that’s much better, isn’t it?

The comment_form function is new to 3.0. Basically, it standardizes the comments form. It makes it wonderful for us plugin authors, since now we can easily modify the comments form with various hooks and things. I’ve already modified Simple Facebook Connect and Simple Twitter Connect to support this new approach; if you’re using a theme with this, then the user won’t have to modify it to have their buttons appear on the comments form.

Customizing

Since theme authors love to customize things, the comments form is also extremely customizable. Doing it, however, can be slightly confusing.

Inside the comments_form function, we find some useful hooks to let us change things around.

The first hook is comment_form_default_fields. This lets us modify the three main fields: author, email, and website. It’s a filter, so we can change things as they pass through it. The fields are stored in an array which contains the html that is output. So it looks sorta like this:

array(
	'author' => '<p class="comment-form-author">...',
	'email'  => '<p class="comment-form-email">...',
	'url'    => '<p class="comment-form-url">...'
);

I truncated it for simplicity. But what this means is that code like this can modify the fields:

function my_fields($fields) {
$fields['new'] = '<p>Some new input field here</p>';
return $fields;
}
add_filter('comment_form_default_fields','my_fields');

That sort of thing lets us add a new input field, or modify the existing ones, etc…

But fields aren’t the only thing we can change. There’s a comment_form_defaults filter too. It gets a lot of the surrounding text of the comments form. The defaults look sorta like this:

$defaults = array(
	'fields'               => apply_filters( 'comment_form_default_fields', $fields ),
	'comment_field'        => '<p class="comment-form-comment">...',
	'must_log_in'          => '<p class="must-log-in">...',
	'logged_in_as'         => '<p class="logged-in-as">...',
	'comment_notes_before' => '<p class="comment-notes">...',
	'comment_notes_after'  => '<dl class="form-allowed-tags">...',
	'id_form'              => 'commentform',
	'id_submit'            => 'submit',
	'title_reply'          => __( 'Leave a Reply' ),
	'title_reply_to'       => __( 'Leave a Reply to %s' ),
	'cancel_reply_link'    => __( 'Cancel reply' ),
	'label_submit'         => __( 'Post Comment' ),
);

All the various pieces of html that are displayed as part of the comment form section are defined here. So those can be modified as you see fit. However, unlike the fields, adding new bits here won’t help us at all. The fields get looped through for displaying them, these are just settings that get used at various times.

But filters are not the only way to modify these. The comment_form function actually can take an array of arguments as the first parameter, and those arguments will modify the form. So if we wanted a simple change, like to change the wording of “Leave a Reply”, then we could do this:

<?php comment_form(array('title_reply'=>'Leave a Reply, Stupid')); ?>

This gives us a simple and easy way to make changes without all the trouble of filters. Nevertheless, those filters can be very useful for more complex operations.

But wait, there’s more!

As the comments form is being created, there’s a ton of action hooks being called, at every stage. So if you want to insert something into the form itself, there’s easy ways to do it.

A quick list of the action hooks. Most of them are self-explanatory.

  • comment_form_before
  • comment_form_must_log_in_after
  • comment_form_top
  • comment_form_logged_in_after
  • comment_notes_before
  • comment_form_before_fields
  • comment_form_field_{$name} (a filter on each and every field, where {$name} is the key name of the field in the array)
  • comment_form_after_fields
  • comment_form_field_comment (a filter on the “comment_field” default setting, which contains the textarea for the comment)
  • comment_form (action hook after the textarea, for backward compatibility mainly)
  • comment_form_after
  • comment_form_comments_closed

CSS and other extras

Let’s not forget styling. All parts of the comments form have nice classes and id’s and such. Take a look at the resulting HTML source and you’ll find all the styling capabilities you like. Also, everything is properly semantic, using label tags and aria-required and so forth. All the text is run through the translation system for core translations.

So theme authors should start modifying their themes to use this instead of the existing big-ugly-comment-form code. Your users will thank you for it. Plugin authors will thank you for it. And really, it’s about time we made WordPress themes more about design and less about the nuts and bolts of the programming, no?

Shortlink: