Archives: Talks

  • WP Elevator

    WP Elevator

    Summary

    This was my first CTF and 1 of the challenges provided by Patchstack as part of their September WCUS CTF.

    Challenge

    The Challenge was to find a flag (a text file) in a WordPress Website. We begin with no backend authorisation, just a the following information:

    Asked my freelance developer friend to write me an authorization plugin so I can share knowledge with selected members. He is still working on it but gave me an early version. I don’t know how it works but will talk with him once he finishes.

    Note: fully whitebox challenge, no need to do massive bruteforce

    Along with this description, we were provided with a .zip file which contained the WordPress Plugin which is installed on the target site.

    The target site itself appeared to be a fresh Twenty-Twenty-Four WordPress installation with registration by default disabled.

    Recon

    The WordPress Plugin

    on inspection, the code contained a plugin called P-member-manager. There weren’t many files which needed inspecting, and I could see straight away the root file p-member-manager.php looked to contain some leaks. As I moved towards the end of the plugin file, I found a useful looking function:

    function flagger_request_callback()
    {
        // Validate nonce
        $nonce = isset($_REQUEST["nonce"])
            ? sanitize_text_field($_REQUEST["nonce"])
            : "";
        if (!wp_verify_nonce($nonce, "get_latest_posts_nonce")) {
            wp_send_json_error("Invalid nonce.");
            return;
        }
        $user = wp_get_current_user();
        $allowed_roles = ["administrator", "subscriber"];
        if (array_intersect($allowed_roles, $user->roles)) {
            $value = file_get_contents('/flag.txt');
            wp_send_json_success(["value" => $value]);
        } else {
            wp_send_json_error("Missing permission.");
        }
    }
    function create_user_via_api($request)
    {
        $parameters = $request->get_json_params();
    
        $username = sanitize_text_field($parameters["username"]);
        $email = sanitize_email($parameters["email"]);
        $password = wp_generate_password();
    
        // Create user
        $user_id = wp_create_user($username, $password, $email);
    
        if (is_wp_error($user_id)) {
            return new WP_Error(
                "user_creation_failed",
                __("User creation failed.", "text_domain"),
                ["status" => 500]
            );
        }
    
        // Add user role
        $user = new WP_User($user_id);
        $user->set_role("subscriber");
    
        return [
            "message" => __("User created successfully.", "text_domain"),
            "user_id" => $user_id,
        ];
    }

    Well that was easy, we found the flag! This function flagger_request_callback will allow us to retrieve the contents of our flag.txt file as a JSON value. But before we start celebrating, in order to get the value we will need to hold either an administrator or subscriber role with an associated (and valid) nonce.

    This makes our first priority to be gaining some sort of access to the site necessary to escalate to either an administrator or subscriber role before moving to the next step.

    Creating an account

    adadd_action("rest_api_init", "register_user_creation_endpoint");
    
    function register_user_creation_endpoint()
    {
        register_rest_route("user/v1", "/create", [
            "methods" => "POST",
            "callback" => "create_user_via_api",
            "permission_callback" => "__return_true", // Allow anyone to access this endpoint
        ]);
    }
    
    add_action("wp_ajax_reset_key", "reset_password_key_callback");
    add_action("wp_ajax_nopriv_reset_key", "reset_password_key_callback");

    The plugin registers an endpoint at the route /wp-json/json/user/v1/create. a simple post request gives us a subscriber account. There is no authentication required, so we can go ahead and register a user.

    Post Request to the vulnerable WordPress endpoint to create the user

    At this point we had registered ourselves on the site, but this particular instance had email disabled, meaning we couldn’t just reset our password in the usual WordPress Forgot password manner.

    Resetting the User Password

    There was a function get_password_reset_key2 which was generating a new password reset key. Brute force usually isn’t my style, but as you can see the generated key was set at a 1 character string only.

    Since it’s set to false, the combination is only one of the following:

    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789

    That presents us a total characters of either 26 (lowercase) + 26 (uppercase) + 10 (digits) = 62 characters

    // Generate something random for a password reset key.
        $key = wp_generate_password(1, false);
        /**
         * Fires when a password reset key is generated.
         *
         * @since 2.5.0
         *
         * @param string $user_login The username for the user.
         * @param string $key        The generated password reset key.
         */
        do_action("retrieve_password_key", $user->user_login, $key);

    After iterating through the 62 possible characters and resetting our password, we are logged in as a subscriber.

    Obtaining the WordPress Nonce

    Now we just need a nonce to get access to reading the flag.txt. Here we are able to post to the get_latest_posts action which will give us the nonce which we need to read our flag.txt. In order to do so, we have to prove that we are a Subscriber, and obtain the session cookie via Network > Dev Tools.

    add_action("wp_ajax_get_latest_posts", "get_latest_posts_callback");
    
    function get_latest_posts_callback()
    {
        // Check if the current user has the subscriber role
        if (!current_user_can("subscriber")) {
            wp_send_json_error("Unauthorized access.");
            return;
        }
    
        // Generate nonce
        $nonce = wp_create_nonce("get_latest_posts_nonce");
    
        // Get latest 5 posts
        $args = [
            "posts_per_page" => 5,
            "post_status" => "publish",
            "orderby" => "date",
            "order" => "DESC",
        ];
    
        $latest_posts = get_posts($args);
    
        // Prepare posts data
        $posts_data = [];
        foreach ($latest_posts as $post) {
            $posts_data[] = [
                "title" => $post->post_title,
                "content" => $post->post_content,
                "link" => get_permalink($post),
            ];
        }
    
        // Send response with nonce and posts data
        wp_send_json_success(["nonce" => $nonce, "posts" => $posts_data]);
    }
    
    post request to get the token CTF

    We add the Key: Action Value: Get_latest_posts and the success response will provide our nonce.

    Reading The Flag

    We have one thing left to do, remember that function at the start? Well lets send a POST request with the recently acquired nonce and see what we get.

    Sending the POST request to get the WordPress CTF


    We got the flag! Unfortunately I rushed to send it through that I forgot to save it. So let’s just pretend it was CTF{I_fOrGot_tO_saVE_it}

    Summary

    My first WordPress CTF (and CTF in general) was fun but tough. I over complicated things, and need to spend more time carefully auditing the code. Ironically, I also tried a second task called ‘Link Manager’ and spent far more time on it getting very close and gaining access to the site, but not being able to retrieve the flag. All in all, I will continue to do more CTF’s and will post my reviews on the site afterwards.

    Patchstack Capture The Flag WCUS Dashboard
  • WooCommerce to Shopify

    WooCommerce to Shopify

    If you’re planning on launching your own e-commerce site, then chances are you are already familiar with WooCommerce or Shopify.

    We recently performed a migration from a self hosted WordPress WooCommerce store to the Shopify platform for a well-known household products company which used Thai as the primary language. During the technical planning, we noticed some hinderances which add unforeseen time and work to the scope at the final hurdle of the migration. I thought i’d share this along with a few solutions to help website agencies and developers work around the limitations of dealing with non-ASCII characters in Shopify platform.

    Skip to the limitations

    Why Would a Site Owner Migrate from WooCommerce to Shopify?

    Every company has their own reasons to switch from one CMS or platform to another. There are various possible reasons why this might occur, but usually it would fall under one of the following categories:

    Shopify Has Fewer Site Maintenance Requirements

    Shopify is the ultimate e-commerce platform in many respects. Your basic Shopify subscription is an all-in-one solution, where you don’t need to worry about server configuration, costs, theme/plugin/environment updates, security and more. Therefore the usual site stagnation we sometimes see in self-hosted websites which have unintentionally been left on set and forget will not occur, as your generous monthly subscription ensures that Shopify has your back.

    WooCommerce is Overwhelming

    Assuming you are running a self-hosted WordPress environment, there are so many key variables which you need to look after before you have even got to managing your shop. WooCommerce is powerful, but with great power comes great responsibility, and your care and technical attention will be required to ensure your shop remains, efficient, secure and continues to look enticing.

    Reducing Server/hosting Costs

    With Shopify, your costs are bundled into their convenient all-in-one plan. Depending on the VPS plan or hosting package which has been chosen for the WordPress site, it may very well be more cost effective to use a Shopify, especially if the site owner is paying for other services like a CDN which would be included out-the-box with Shopify packages.

    It’s worth noting, that while the costs may seem lower, after the site owner starts recreating new and current functionality which is present in the WordPress site, there may be quite a few new recurring costs for premium Shopify apps.

    Key Limitations

    Although the actual process is not covered in this article, its fairly straightforward and uncomplicated to migrate online stores from WordPress-WooCommerce to Shopify. There are various methods to allow you to map your data across, be it exporting/importing via .csv or using a platform like Matrixify to channel your data across using the REST API.

    However, as you may have guessed from the title, there are some key limitations which can cause extra headaches at the very tail-end of the process.

    Shopify does not fully support non-ASCII characters.

    While it can pass almost all non-ASCII characters into usual fields and meta data, there are some exceptions which for a site owner or developer, can be very frustrating.

    Scenario 1: URL Redirects

    Shopify has a feature which allows bulk url redirects, which is ideal for a smooth transition once you switch your site from it’s current platform over to Shopify.

    URLs can only be sent over the Internet using the ASCII character-set. If a URL contains characters outside the ASCII set, the URL has to be converted. URL encoding converts non-ASCII characters into a format that can be transmitted over the Internet. URL encoding replaces non-ASCII characters with a “%” followed by hexadecimal digits. URLs cannot contain spaces. URL encoding normally replaces a space with a plus (+) sign, or %20.

    Shopify URL redirects (manual or bulk) have a 255 character limit. Herein lies the issue, as the encoding process necessary to display the URL with non-ASCII characters has the potential to push URLs containing non-ASCII characters well above the 255 character limit, meaning you will be unable to place a url redirect for your product/post/page.

    shopify redirect error due to Non-ASCII characters. Target is too long (maximum 255 characters)

    I cross-checked this with Shopify support, and it is confirmed that currently, Shopify does not fully support non-ASCII characters in URL redirects.

    Solution

    This is a frustrating limitation, the most obvious solution at this point is to go one step higher for the URL redirects, and redirect at DNS level. Cloudflare offers redirects as part of their free and pro plans, meaning you can set such redirects at this higher level, but may need to pay the price if you have 20+ redirects.

    Scenario 2: Image File Names

    When importing or uploading images manually via the Shopify platform, the media retains the same filename. This is useful when mapping a huge amount of products for example, you can import the mapping sheet with corresponding image name, followed by bulk importing your media file and there will be perfect synchronisation.

    However, this is only the case for ASCII characters, as non-ASCII characters in the filename is assigned with a random string as the filename instead. This results in a broken mapping process.

    Uploading media files with non-ascii characters in shopify

    So for example if you import a set of blog posts from a Thai website whereby the post images have Thai characters within the filenames, there will be no connection between the <img src="URL"> and the media file within your Shopify site, thus resulting in a broken image link on the post/page/product.

    Solution

    You will need to create a small program which renames both image and filename to use ASCII characters prior to importing the mapping sheet and images. Alternatively you can manually update both- Yikes!


    This blog was written in June, 2023, so i’ve tagged the Shopfy development log below incase these issues become redundant in a later update.

    https://shopify.dev/changelog

  • WordCamp Asia

    WordCamp Asia

    After approximately 6 years working with WordPress on a commercial level, I decided to attend the first WordCamp Asia 2023 event held in Bangkok.

    Given that WordPress powers approximately 43% of the web (at the time of writing), this was a humbling experience to be around some of the greatest minds powering a large share of functionality driving internet forward.

    Although i’d connected with many of the engineers over discussion forums, Twitter and the Gutenberg Repo already, it was fantastic to meet in real life after the WordCamp flagship event had been postponed the previous year.

    Here were a few of my favourite sessions: