SN4WP: Simple Nonces 4 WordPress

With all of the recent talk about WordPress security and Nonces, I’ve decided to create a plugin that enhances the security.

It is meant to both provide an easy way for some users to disable the referer check without giving away the house, and as a way to provide enhanced security for those that can use the referer check.

Primary Features:

  • Enables/Disables Referer check (Disabled by default)
  • Adds an optional “One IP per Admin Session” check (Disabled by default)
  • Times out sessions after inactivity (10 Minutes by default)
  • Posting – which takes longer – has a different time out period (30 Minutes by default)
  • All features are configurable on a plugin options tab

I’ll probably run my times at 5 & 30 minutes with the IP check on, but I’ll have to do more use testing first.

This is the alpha release and includes no documentation. Just extract it with paths to your plugin directory, activate it and look for the new configurations tab under the plugins directory. That said – if you didn’t know that, you shouldn’t be running the alpha release. This has been tested for one morning on one blog at this point and is my first ever WP plugin. I knew nothing about plugin writing yesterday morning. So, it is probably not ready for prime time. (That said it seems to work fine.)

Here’s the link: http://www.TheCodeCave.com/downloads/plugins/sn4wp-alpha1.zip

So please take a look and tell me what you think. Does it work?

———————-

Notes taken while making this plugin…

What does the referrer check do?
Checks to see if an admin action was initiated from an admin page.

What is protected by the referer check?
Only certain actions are protected by the referer check.
The common thread seems to be that the action must be a single destructive step.
The protected actions include: (An * indicates it was added with verison 2.02)
Categories – Delete
Link Manager – Assign, visiblity, move, add, editlink, delete
Options – Update
Plugins – Activate, Deactivate
Posts – Post*, editattachment*, editpost*, Delete, deletecomment, unapprovecomment
Profile – Post
Themes – Activate, Deactivate
User-edit – Switchposts, update*
Users – Promote, dodelete, delete adduser
Additionally the akismet plugin’s configuration page
Note that these actions consist of the the “final commit” steps. For instance the
“linkedit” action that brings up the form allowing you to edit a link is not protected.
However, the “editlink” action that posts the changes IS protected. Please keep this in
mind when testing this plugin.

Can a referrer be wrong?
Yes, many proxies strip or replace the referer. Additionally referers can be forged,
but the fact that a login cookie is also required makes such attacks difficult. The
referer check, when working CAN protect you from some attacks. That’s why this plug
in does not disable it by default.

Can a referer check be simulated in a plugin? And if so how?
While we cannot check to see if each individual action came from an admin page, we
can ensure that an admin page was recently used by that user. If that user just
accessed an admin page, allow the action to succeed.

Problem: All action links change pages before taking effect. So the page last viewed
by the user by the time the admin referer check hits is ALWAYS an admin page.
Additionaly, the final post action involves multiple pages which would violates any
LastPage tracking scheme used in Nonces (even after you implement it).
Solution: Without access to the name of the action that is about to be attempted, the
nonce needs only indicate the previous page was an admin page. This can be
indicated by whether or not the Nonce is valid.

Problem: An admin could close their admin page without logging out.
Solution: The nonce times out after 5 minutes. They are vulnerable for 5 minutes if
they forget to log out.

Problem: An admin could leave the admin area and go back to their blog without
logging out.
Solution: None – Admins often use one tab to admin their blog, and another to view it.
Likewise, the preview has a non admin context.

15 thoughts on “SN4WP: Simple Nonces 4 WordPress”

  1. I haven’t actually run this code, but reading through it, here are my thoughts:

    We’ve discussed on wp-hackers the drawbacks of storing nonces in the database and in cookies. This solution seems to do both, and I understand if you don’t share concerns about performance being affected by admin-only actions. The cookie thing though…

    Suppose you parked a Firefox tab on the Manage Posts page, and while browsing the comments of your site from another tab, you clicked a malicious link. Would the cookie still work, since it’s not being reset by browsing the blog itself?

    If you reset the cookie upon viewing a non-admin page, would that render actions on the Manage Posts page in the other tab inoperable? What prevents a comment shown on the Manage Posts page in the admin from deleting a post?

    Does this code actually check the nonce from the prior page? I see $pagenow in there, but it’s not used to build the nonce, as far as I can tell.

    There aren’t any “Are you sure?” messages in here, which would be useful for when nonces fail (probably mostly due to timeouts) because they could produce a valid nonce and forward the form data on to the correct handler upon confirmation.

    Generally, this is a good effort. There may be provisions for any of the above comments in the code – as I said, I didn’t run/test it, I just read through it. I’m curious how the code addresses these points.

    You might consider adding some javascript to the warning message that does one of two things. Either alert the user more strongly when the timeout period is approaching, or make an Ajax call that grabs a new nonce before the timeout. This warning could apply to any admin page.

  2. Welcome Owen!

    I should say that I share your concerns about optimization. However, in the framework of a plugin, I was limited in the approaches I could take.

    I wanted cookies to be a seperate line of defense from the nonce. So, none of this work is actually done in the cookie. Since, a plugin has no control over the creation of links and no control over what fields are put in the forms, that left ONLY the database as a place to store the nonce and the time it was created. A minor feature I wanted is that this works with two different browsers being used. Since cookies are not involved, that means you can do stuff across multiple browsers. As far as this plugin is concerned, nothing on the client side affects its operation.

    > Suppose you parked a Firefox tab on the Manage Posts page, and
    > while browsing the comments of your site from another tab, you
    > clicked a malicious link. Would the cookie still work,
    > since it’s not being reset by browsing the blog itself?

    I originally wrote this plugin tracking the actual pages used and granting access only to actions take from the same page you are on. Here’s a link to that version: http://www.TheCodeCave.com/downloads/sn4wp_pages.txt

    I found when testing the posting of articles that $pagenow would be reset without regenerating the admin menu. My nonce is created it post.php and suddenly evaluated from the context of inline_uploading.php. That meant I had no way of telling the difference between a valid nonce and an invalid one. I couldn’t find a way around that without editing WP code – and if you’re able to edit the WP code, why use the plugin? So I abandoned that approach.

    Instead, I debounce the nonce creation. If I get a stray request in for an adminstrative activity, it won’t work if I did not already have a valid nonce. That approach does open up some holes. In short, if you are administrating your website and browseing the web at the same time, yes, you are still vulnerable.

    I may have given up the last page stuff to soon, but I didn’t see a way around posting process referencing both post.php and inline_uploading.php and reading through all the code to find other circumstances that did the same thing, would have involved more testing than I have time for ATM. The solution may be as simple as finding these circumstances and writing an if statment that treates “inline_uploading.php” as “post.php”. That seems kindof iffy though…

    I like the idea of an “You must save soon” warning message. I thought about it too, but frankly I don’t how to implement it. Hmmm… Actually I do have code for a count down time. I bet I could use that…

  3. >What style of code review do you prefer?

    LOL don’t know how to answer that one…

    Is this where I say “Be gentle with me?”

  4. Can’t an attacker just make the user GET post.php to establish a previous admin context before POSTing to it (i.e., as a CSRF attack)? If not, then won’t that page be inaccessible through the bookmarklet etc.?

    Similarly, won’t the nonce be correct if the administrator follows a malicious link in a comment from the comment management page, sees an image with a crafted URL in a submitted draft, etc.?

  5. Good catch Sam. I actually figured Paul would be the first to point that out.

    Because a plugin cannot inject the nonce into either a form post, get or even into links (at least I can’t yet see a way for it to be done), the only barrier the plug in can add is to force an admin context. This will block all attacks that involve a single link.

    If the attacker is able to lead you through a sequence of steps that would simulate a normal admin session, it will fool this plugin.

    I’m doing further testing on this right now. I was HOPING that the IP checking feature would block some of this. I’ll let you know what I find out in a few minutes…

  6. Well, if you don’t mind ugly hacks it can be done with some JavaScript run onload, e.g. (untested):

    [js]
    var n = document.createElement(‘input’);
    n.setAttribute(‘name’, ‘_wpnonce’);
    n.setAttribute(‘value’, ”);

    var f = document.getElementsByTagName(‘form’);
    for (var i = 0; i

  7. Well, that comment was a disaster! I can’t figure out this comment system you’ve got here, but the input was supposed to have value set by a PHP function generating the nonce. :)

  8. (Continued Respons to Sam’s first post)
    OK I did my tests.

    I’ve figured out four ways to serve up a CSRF attack.
    1. It turns out that the IP check will block the most complex and powerful attacks. No worries if you can turn on the IP checking feature. These attacks DO get past WP2.0 as it is.
    2. The medium difficulty ones, have no way to get past the referer check and MUST be launched from within the admin area. They can show up as a valid image when viewed normaly and as a broken image (because they attack) when they are viewed from an admin area. This required the values to be submitted.
    3. The third aproach will always show up as a broken image, where ever it is viewed. I’ve just realized that I can filter out this attack. That will be in the next version.
    4. The fourth and most simplistic attack involves links. And that too can be filtered out from the next version.

    Thank you for the ideas.

    RE: the posted Java script . I’d love to see the whole thing, however posts don’t scare me right now. I haven’t found a way to force a post AND have access to the cookies. At the moment, I don’t think it is possible. It is the GETs that bother me. I wonder if your code could be adapted to modify the links on the page and add the nonce in. I’ll have to fool with that some. I’ve done a bunch of hacking already created javascript stuff but haven’t built any for my own purpose yet. It seems it is about time.


    TEST OF JAVA SCRIPT TAG IN THE COMMENTS 

    Syntax:

    OPENBRACKET js CLOSEBRACKET
    Java script code
    OPENBRACKET /js CLOSEBRACKET

    Test:

    [js]
    var n = document.createElement(’input’);
    n.setAttribute(’name’, ‘_wpnonce’);
    n.setAttribute(’value’, ‘’);
    var f = document.getElementsByTagName(’form’);
    [/js]

  9. I’m running late so can’t post a proper reply, but here’s an example plugin which adds dummy nonces to all forms and all links containing ‘wp-admin’. The code should be self-explanatory.

    The specific attack I had in mind was something like:
    User visits example.com/evil.html/
    Page opens an iframe to user’s admin page.
    Page opens an iframe which POSTs a form to user’s admin page.

    Given that all this talk of nonces was prompted by users who can’t use the referer check, that can’t be counted on. Anyway, must dash! Back later.

  10. Interesting… I’ll have to think more on this iframe thing. You seem to be implying that you can load an admin’s page in an iframe (viewed on the admin’s computer thus getting around the cookie issue) and then somehow the submit action inside the iframe? That one I’ve not thought of… I’m not convinced yet. I know how to find the button in question, but I don’t know how to trigger it. If I’d fake it from outside the iframe, I’d lose my cookie context as the form is on another host.

    In any case, that’s a great little routine. Thanks!

    This makes the plugin a whole lot more secure.

  11. document.getElementById(‘my_form’).submit();

    I don’t understand what you mean by “cookie context” — any request sent to site B will contain all cookies that user’s browser has for site B, regardless of where that request originated.

    So, yes and no. You can’t open a page on site B from site A and, with JavaScript from site A, submit a form on site B. But you can have a page on site A which submits a form *to* site B.

  12. >You can’t open a page on site B from site A and, with JavaScript from site A,
    >submit a form on site B.
    Ah, that’s how I was thinking of your iframe

    > But you can have a page on site A which submits a form *to* site B.
    Yes, you’re right on that part. That’s old-old school stuff often used for submission alteration.

    I think the difference with the attack your describing from what I found with the attack I described the other day (here’s one of a dozen places it is) is that my form submission is built entirely on A and not displayed in the user’s browser, lost its cookie context because the submit was essentially coming from the A and not the client. And A has no cookies at all.

  13. Unfortunately, I haven’t had a chance to look deeper. Thank goodness for public development, eh?

    Like I said, an educational opportunity. Now, when I look at the code, I can do so with the eyes of an attacker, according to the previous comment.

    Wrt the GET->POST attack, how does the attacker get my login cookies?

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>