Make WordPress Plugins

Updates from Jon Cave Toggle Comment Threads | Keyboard Shortcuts

  • Jon Cave 7:38 pm on April 9, 2013 Permalink | Reply
    Tags: security   

    Review an intentionally vulnerable plugin 

    Imagine that another plugin author has asked you to look at a plugin that is currently in development to check for security flaws and help them fix any that are present. Would you know what to look for and how to fix the problems? Well, a fun challenge has arrived that will test, and hopefully improve, your knowledge in this crucial area of plugin development. I have developed a small, bug ridden plugin that requires a rigorous security review and suggestions for fixes.

    The code is available from https://gist.github.com/joncave/5348689.

    This is an incomplete plugin that aims to log any failed login attempts. Unfortunately, it actually harms the security of a site rather than enhancing it. All of the interesting parts are in vulnerable.php, so you should focus your review there. Please remember not to run this plugin on any server that is accessible to the internet!

    If you spot a vulnerability whilst reviewing the code then make a note of the problem, where it’s located and what the problem is. Then come up with a patch that would solve the problem. It might also be beneficial to create a request that would demonstrate the vulnerability which can then be used to test your fix. I hope that this process will help you understand more about vulnerabilities, what sorts of things to look for when reviewing your own code, how to go about coding securely, and how to fix any problems in your own plugins if a flaw is found.

    If you would like individual feedback on your finding and solutions, and to provide me with some information on which bugs people found and fixed, you can submit them via this survey. Please refrain from posting any spoilers in the comments for now.

    In a week or so I will write another post detailing each of the vulnerabilities present in the code and how to fix them.

    Bonus challenge: with access to a subscriber level account can you find any ways of extracting the data from an option named secret_option?

     
  • Jon Cave 7:42 pm on June 7, 2012 Permalink | Reply
    Tags: rewrite   

    Rewrite endpoints API 

    During the development of WordPress 3.4 I have spent some time working on improving the rewrite API. One of the tickets this involved was #16303: “Improve documentation and usability of WP_Rewrite Endpoint support”. Endpoints are a really cool feature of the rewrite API, but unfortunately also little known and misunderstood. So, with this post my aim is to get more plugin developers to read and understand the new and improved endpoint documentation.

    What are endpoints?

    Using endpoints allows you to easily create rewrite rules to catch the normal WordPress URLs, but with a little extra at the end. For example, you could use an endpoint to match all post URLs followed by “gallery” and display all of the images used in a post, e.g. http://example.com/my-fantastic-post/gallery/.

    A simple case like this is relatively easy to achieve with your own custom rewrite rules. However, the power of endpoints shines for more complex situations. What if you wanted to recognise URLs for posts and pages ending with “gallery”? What if you wanted to be able to catch multiple different archive URLs, e.g. day, month, year and category archives, with “xml” appended in order to output an XML representation of the archive? For these situations endpoints are very useful as they allow you to add a string to the end of multiple rewrite structures with a single function call.

    How to use them

    There is one function for interacting with endpoints: add_rewrite_endpoint(). It takes two parameters $name and $places.

    $name is a string and is, wait for it… the name of the endpoint. $name is what is used in the URL and is the name of the query variable that the endpoint URL will be rewritten to. For example, an endpoint named “print” added to post permalinks would use a URL like http://example.com/my-awesome-post/print/.

    $places is an integer value which represents the locations (places) to which the endpoint will be added, e.g. posts, pages or year achives. To understand $places you need to learn about the endpoint mask constants.

    In wp-includes/rewrite.php (browse wp-includes/rewrite.php on Trac) a number of constants are defined all with names beginning with “EP_”:

    define('EP_NONE', 0);        // 0000000000000
    define('EP_PERMALINK', 1);   // 0000000000001
    define('EP_ATTACHMENT', 2);  // 0000000000010
    define('EP_DATE', 4);        // 0000000000100
    define('EP_YEAR', 8);        // 0000000001000
    // ...
    define('EP_PAGES', 4096);    // 1000000000000
    define('EP_ALL', 8191);      // 1111111111111
    

    These are the endpoint masks which describe sets of URLs; post permalinks are described by EP_PERMALINK, year archives are EP_YEAR, etc. They should be thought of in terms of their binary values (see the comment I’ve added to the end of each line). Every EP_* mask, except for EP_ALL, is a different power of two and so has a different bit set to one. This allows us to build up combinations of endpoint masks by using the bitwise OR operator:

    // all posts or attachments
    EP_PERMALINK | EP_ATTACHMENT // 0000000000011
    
    // all full dates (yyyy/mm/dd), years or pages
    EP_DATE | EP_YEAR | EP_PAGES // 1000000001100
    

    $places should also be thought of as a binary number. It should be set to one of the EP_* constants or a combination of them using the bitwise OR operator. If we wanted to add our endpoint to all post permalinks we would use EP_PERMALINK. For both posts and pages: EP_PERMALINK | EP_PAGES. For posts, pages, and categories: EP_PERMALINK | EP_PAGES | EP_CATEGORIES. There is also a special value to add an endpoint to all URLs that support endpoints: EP_ALL.

    NB: The values of the EP_* constants are not guaranteed to stay the same which is why you must say $places = EP_PERMALINK and not $places = 2. This is particularly important for EP_ALL which will change every time a new endpoint mask is added.

    It’s time to put this information into practise. The running example will be a plugin that adds JSON representations of our content using a new rewrite endpoint called “json”. So, the goal is to get URLs such as http://example.com/about/json/ to return a JSON response that gives information about the “about” page. To add the “json” endpoint to post and page rewrite structures:

    add_rewrite_endpoint( 'json', EP_PERMALINK | EP_PAGES );
    

    This is called in a function hooked into the init action:

    function makeplugins_add_json_endpoint() {
        add_rewrite_endpoint( 'json', EP_PERMALINK | EP_PAGES );
    }
    add_action( 'init', 'makeplugins_add_json_endpoint' );
    

    Now we want to act on requests for JSON content. This is done by hooking into template_redirect. We want to detect appropriate requests and include our custom template for serving up posts and pages in JSON format:

    function makeplugins_json_template_redirect() {
        global $wp_query;
    
        // if this is not a request for json or a singular object then bail
        if ( ! isset( $wp_query->query_vars['json'] ) || ! is_singular() )
            return;
    
        // include custom template
        include dirname( __FILE__ ) . '/json-template.php';
        exit;
    }
    add_action( 'template_redirect', 'makeplugins_json_template_redirect' );
    

    And we’re done. For a full example plugin see https://gist.github.com/2891111.

    How do they work?

    The best way to understand how anything works is to take a look at the source, so let’s do that. Endpoints are added with the add_rewrite_endpoint() function in wp-includes/rewrite.php:

    /**
     * Add an endpoint, like /trackback/.
     *
     * Adding an endpoint creates extra rewrite rules for each of the matching
     * places specified by the provided bitmask. For example:
     *
     * <code>
     * add_rewrite_endpoint( 'json', EP_PERMALINK | EP_PAGES );
     * </code>
     *
     * will add a new rewrite rule ending with "json(/(.*))?/?$" for every permastruct
     * that describes a permalink (post) or page. This is rewritten to "json=$match"
     * where $match is the part of the URL matched by the endpoint regex (e.g. "foo" in
     * "/json/foo/").
     *
     * A new query var with the same name as the endpoint will also be created.
     *
     * When specifying $places ensure that you are using the EP_* constants (or a
     * combination of them using the bitwise OR operator) as their values are not
     * guaranteed to remain static (especially EP_ALL).
     *
     * Be sure to flush the rewrite rules - flush_rewrite_rules() - when your plugin gets
     * activated and deactivated.
     *
     * @since 2.1.0
     * @see WP_Rewrite::add_endpoint()
     * @global object $wp_rewrite
     *
     * @param string $name Name of the endpoint.
     * @param int $places Endpoint mask describing the places the endpoint should be added.
     */
    function add_rewrite_endpoint( $name, $places ) {
          global $wp_rewrite;
          $wp_rewrite->add_endpoint( $name, $places );
    }
    

    So this is just a wrapper for the add_endpoint() method of the WP_Rewrite class. Although the (excellent!) documentation gives us some clues as to what it does we’ll have to dig deeper to find the how:

    /**
     * Add an endpoint, like /trackback/.
     *
     * See {@link add_rewrite_endpoint()} for full documentation.
     *
     * @see add_rewrite_endpoint()
     * @since 2.1.0
     * @access public
     * @uses WP::add_query_var()
     *
     * @param string $name Name of the endpoint.
     * @param int $places Endpoint mask describing the places the endpoint should be added.
     */
    function add_endpoint($name, $places) {
        global $wp;
        $this->endpoints[] = array ( $places, $name );
        $wp->add_query_var($name);
    }
    

    Another very short and simple function. All it does is append the two parameters passed to it to the private $endpoints property of the WP_Rewrite class and also add a new query variable using WP::add_query_var().

    Okay, so that’s still not useful for a full understanding of endpoints. All we know is that the arguments you pass to add_rewrite_endpoint() are stored in a private array of the $wp_rewrite global. To find out more we’ll have to search wp-includes/rewrite.php for “>endpoints” (i.e. code accessing the WP_Rewrite::$endpoints property). There are only three references to this: WP_Rewrite::add_endpoint() we have seen, WP_Rewrite::init() is boring (initialising the array), and the third is WP_Rewrite::generate_rewrite_rules():

    $ep_query_append = array ();
    foreach ( (array) $this->endpoints as $endpoint) {
        //match everything after the endpoint name, but allow for nothing to appear there
        $epmatch = $endpoint[1] . '(/(.*))?/?$';
        //this will be appended on to the rest of the query for each dir
        $epquery = '&' . $endpoint[1] . '=';
        $ep_query_append[$epmatch] = array ( $endpoint[0], $epquery );
    }
    
    // ... a lot of code removed ...
    
    foreach ( (array) $ep_query_append as $regex => $ep) {
        //add the endpoints on if the mask fits
        if ( $ep[0] & $ep_mask || $ep[0] & $ep_mask_specific )
            $rewrite[$match . $regex] = $index . '?' . $query . $ep[1] . $this->preg_index($num_toks + 2);
    }
    

    In the code above the first foreach is looping through the defined endpoints and building a new array called $ep_query_append. This new array uses regular expressions that match a specific endpoint as keys and the values are the endpoint $places and $epquery which is a partial query string to append to a full query. So, for our JSON endpoint example we would get:

    $ep_query_append[ 'json(/(.*))?/?$' ] = array( EP_PERMALINK | EP_PAGES, '&json=' );
    

    The second loop generates the final rewrite rules for our endpoint. It loops through $ep_query_append checking if the current permastructure being generated has an endpoint mask, $ep_mask, that matches any of the endpoints. If the bitwise AND produces a non-zero value then there’s a match and the endpoint rewrite rules should be added to this permastructure.

    For our JSON example, if WP_Rewrite::generate_rewrites_rules() has been called for the posts permalink structure then $ep_mask = EP_PERMALINK and $ep[0] = EP_PERMALINK | EP_PAGES. The bitwise AND of these values produces 1, therefore a new entry is added to $rewrite. Assuming that the post permalink structure is “/%postname%/” it would look something like:

    $rewrite[ '([^/]+)/json(/(.*))?/?$' ] = 'index.php?name=$1&json=$3'
    

    This is the final rewrite rule for our JSON endpoint applied to post permalinks. It matches a request for “post-slug/json/” and sets up the appropriate query variables “name” and “json”. Our template_redirect hook now picks this up and produces the required response.

    And you made it to the end, phew! Time for a drink…

    Conclusion

    I hope that after all of that you understand how to use endpoints and how they work. If you have any questions please don’t hesitate to ask them in the comments. Always remember that the best way to understand a function is to look at the source and follow its execution.

     
    • Tareq Hasan 7:49 pm on June 10, 2012 Permalink | Reply

      Suppose I’ve a post url like http://example.com/sample-post. Now I’ve added a rewrite endpoint “edit”. So the url becomes http://example.com/sample-post/edit. Now whats the right way to print the url? Should it be like:

      <?php echo get_permalink() . '/edit'; ?>

      or is there any prefered way? What happens when pretty url is not turned on?

      • Jon Cave 3:33 pm on June 18, 2012 Permalink | Reply

        That’s fine to build the URLs like that. If pretty permalinks are not enabled then endpoints are not going to work. This is because endpoints rely on WordPress’s internal rewrite system which is disabled for the default links. You can use the using_permalinks() method of the WP_Rewrite global to determine if pretty URLs are enabled.

        • Merianos Nikos 9:52 am on November 12, 2012 Permalink | Reply

          Just wow !!!!!

          Sorry for repling here, but comment’s are closed.

          This is an amazing article, and now I can clearly see that WordPress come with endless options to extend it in any direction.

          It’s true the situation that in the past, epmask was not so clear. That article is just amazing :)

          Thanks a lot :)

    • rilwis 1:47 am on June 15, 2012 Permalink | Reply

      This is a great post! I’ve been using generate_rewrite_rules for adding some simple virtual pages. It’s quite handy and requires lots of code. I think enpoints would do the same job better. Thanks for a very detailed post.

    • Marko Heijnen 11:35 am on June 18, 2012 Permalink | Reply

      Hey, If I get it right and you want a endpoint for a specific post type you use EP_PERMALINK and then check in the filter ‘template_redirect’ if it is the correct post type and otherwise load the 404 template?

      • Jon Cave 3:30 pm on June 18, 2012 Permalink | Reply

        That sounds fine.

        If it’s a post type which you’re registering then you can provide a custom endpoint mask in the register_post_type() call (ep_mask key of the rewrite argument). This means that the query variable will only be set when the rewrite rule is matched which should only be true if it’s a request for an item of a custom type.

    • SGr33n 11:14 am on June 22, 2012 Permalink | Reply

      Hi,

      I’m trying your code but i see that there is the same problem (is it a bug?) that occurs on my old way using generate_rewrite_rules.

      If I use this code, then, calling a page like http://www.wpsite.com/page-title/json/variable/unusefulstring/anotherunusefulstring/etc/ it will not return 404 but even the same page.

      In my opinion http://www.wpsite.com/page-title/json/variable/unusefulstring/ should return 404, http://www.wpsite.com/page-title/json/variable/ should return the correct page… right?

    • Jayce53 6:18 pm on September 16, 2012 Permalink | Reply

      Am I missing something here? If endpoints aren’t going to work if pretty permalinks aren’t enabled, then my plugins are always going to have to have an alternative way of processing my intended “endpoint URL”structure. And if I have to provide the logic to process it manually anyway, what’s the point of endpoints?

      • Jeremy Clarke 5:08 pm on September 21, 2012 Permalink | Reply

        If the endpoint is important you always want to be able to support both pretty and raw URLs. Raw URLs are easy, just define the query var and use it in the URL (?edit=1). Endpoints make it easier to get the variable out of a URL when pretty permalinks are enabled.

    • Rarst 6:00 pm on October 13, 2012 Permalink | Reply

      Is it possible to use pagination after endpoint? I am getting `’endpoint_name’ => ‘page/2′` and it’s not recognized as pagination.

      Trying to implement custom posts page for this question http://wordpress.stackexchange.com/questions/67732/setting-a-custom-sub-path-for-blog-without-using-pages

      • Jon Cave 3:24 pm on October 14, 2012 Permalink | Reply

        Endpoint rules do not have pagination rules applied to them as well. So pagination on endpoints would have to be done by yourself, e.g. mapping a value matching page/[0-9]+ to the paged query variable.

        It seems to me that it would be more suitable to use custom rewrite rules instead of endpoints for the problem in linked question.

    • Mohammed Atif 7:50 pm on October 25, 2012 Permalink | Reply

      How do we handle posts requests on the endpoint?

    • silentshin 4:17 pm on November 16, 2012 Permalink | Reply

      Ensure it works, flush_rewrite_rules() should be used after add_rewrite_endpoint().

    • jazbek 6:17 pm on January 13, 2013 Permalink | Reply

      I’m trying to create a ‘sfw’ endpoint for an art blog, that will remove all posts tagged nsfw from post listings.

      It works fine on the first page of posts, but but pagination is broken. The next page link becomes /sfw/page/2/ and the ‘sfw’ query var is now set to [sfw] => ‘page/2′, so the page is removed from the query vars. Is there any way to get this working without hacking it with my own function? Here is my code:

      https://gist.github.com/4525380

    • marijn 9:25 am on January 30, 2013 Permalink | Reply

      i created a custom endpoint, which includes my custom template-file. Only thing is that my wp_title() gives ‘page not found’, so i’m guessing after including my template-file, somehow i get a 404, but the correct template displays.

      Any suggestions?

    • tomas.k.7 3:21 am on April 4, 2013 Permalink | Reply

      I’ve seen ERROR 404 until I opened wp-admin/options-permalink.php (loading without any save it did some change)… I guess it’s some kind of bug… (I’m using multisite with domain mapping)

      • tomas.k.7 3:44 am on April 4, 2013 Permalink | Reply

        found it – it should be mentioned in the article as well as in the codex documentation:

        you need to “flush” new rewrite endpoint…

        $wp_rewrite->flush_rules();

c
compose new post
j
next post/next comment
k
previous post/previous comment
r
reply
e
edit
o
show/hide comments
t
go to top
l
go to login
h
show/hide help
shift + esc
cancel