Patching an Arbitrary User Creation Security Bug in "thecartpress" Plugin

Published 12 December 2022
Updated 12 July 2023
Robert Rowley
Author at Patchstack
Table of Contents

When people come together, contribute to a like-minded goal. Great things can happen. Community is inherent in any successful open source project. The good news is, connecting with others is something humans are good at doing. The bad news is, not all open source projects benefit from this.

Connection and community are powerful tools for humanity, and open source benefits from this. Without connection or community, then many open source projects run a high risk of failure. I have seen it happen, and that is what these "Last Patch" posts are all about.

Projects take time, effort, and commitment. This can overload solo developers, maybe not immediately, but eventually. Many projects are abandoned due to a lack of support. You can think of this as the developer not supporting their users, or the users not supporting the developer.

This is a frank reminder to support those who support you. And an introduction to Patchstack's "Last Patch". This is a short series of blog posts where we will be discussing and patching unpatched security bugs in abandoned open source projects. With an initial focus on plugins found in the WordPress.org plugin repository

The troubling truth is, some open source projects come to an end. Abandoned projects will not receive patches, especially not security patches.

We do not fault the developers for the lack of a patch. We understand life's priorities can change, but we also want to help. If we can't help, then I hope we can learn something in the process.

This post will be discussing the abandoned WordPress plugin: thecartpress.

The plugin had a critical security bug found in it by third party security researchers. This bug could have led to arbitrary user creation. The plugin was removed from the WordPress plugin repository on October 5th, 2021 due to a security bug, likely this one.

Considering the last release pushed for thecartpress was January, 2017. We can assume the plugin was abandoned, which makes it a great candidate for one last patch.

TL;DR Just Share the Patch

For site owners

You need to find an alternative shopping cart solution ASAP. This product is unsupported and if you are running an e-commerce website, you must choose shopping cart software that is supported by the developers.

If you are just curious to see the vulnerable code then it exists in the file: thecartpress/modules/LoginRegister.class.php

On lines 224-226

224
225            $roles         = isset( $_REQUEST['tcp_role'] ) ? explode( ',', $_REQUEST['tcp_role'] ) : array( 'customer' );
226            if ( ! is_array( $roles ) ) $roles = array( $roles );

The code would be more secure to hard code the value of $roles (which is later users to add the new user to various roles.)

224  // Last Patch provided by Patchstack (RR)
225            $roles = array( 'customer' );
226  // End Last Patch

For hosting providers

The following mod_security rule will block any requests to the tcp_register_and_login_ajax AJAX endpoint in WordPress if the request also includes a variable named tcp_role.

SecRule ARGS:action tcp_register_and_login_ajax "deny,id:11115,chain"
SecRule ARGS_NAMES "tcp_role" "chain"
SecRule REQUEST_FILENAME "/wp-admin/admin-ajax\.php$"

To understand how and why this works, please read on.

Last Patch: thecartpress

Verifying the exploit

The proof of concept for this security bug has been publicly available since October 2021. Written by someone with the moniker "spacehen" according to exploitDB Was this some evil hacker? No, with a little digging around I found out spacehen was a student when they found this security bug. They have since graduated University with a degree in Electrical and Computer engineering. "Spacehen" was a student applying their knowledge of secure coding practices to the world. They proved they could identify insecure PHP code, and I do hope they continue with secure coding practice in their career as a software developer and/or hardware engineer.

Nevertheless, the exploit code has been public for over a year so let us see how it works:

First I list my site's users using wp-cli.

# sudo -u www-data wp user list
+----+---------------+--------------+----------------------+---------------------+---------------+
| ID | user_login    | display_name | user_email           | user_registered     | roles         |
+----+---------------+--------------+----------------------+---------------------+---------------+
| 1  | admin         | admin        | admin@localhost | 2022-02-24 20:05:51 | administrator |
+----+---------------+--------------+----------------------+---------------------+---------------+

Now I run the PoC code (after manually confirming it is not malicious in any way)

# python3 thecartpress_admin_creator.py http://target_host/
TheCartPress <= 1.5.3.6 - Unauthenticated Privilege Escalation
Author -> space_hen (www.github.com/spacehen)
Inserting admin...
Success!
Now login at /wp-admin/

And now I list the users again. I can see a newly added administrator user named admin_09.

# sudo -u www-data wp user list
+----+---------------+--------------+----------------------+---------------------+--------------------------+
| ID | user_login    | display_name | user_email           | user_registered     | roles                    |
+----+---------------+--------------+----------------------+---------------------+--------------------------+
| 1  | admin         | admin        | admin@localhost | 2022-02-24 20:05:51 | administrator            |
| 4  | admin_09      | admin_09     | testing@the_proof_of_concept.com     | 2022-12-06 20:33:21 | subscriber,administrator |
+----+---------------+--------------+----------------------+---------------------+--------------------------+

Not a good thing to have running on any public website. Unauthenticated requests can result in administrators being created!

So, let's find out what to patch.

Finding what to patch

When I first reviewed the proof of concept's python code, I spotted the name of the insecure ajax action being called as tcp_register_and_login_ajax. The proof of concept python script then sets a few key values (like username, password, and the new user's role) via POST parameters, but this tcp_register_and_login_ajax AJAX action tells me where to look in the plugin's source code.

        ajax_action = 'tcp_register_and_login_ajax'
        admin = '/wp-admin/admin-ajax.php';

...

        data = {
        "tcp_new_user_name" : "admin_09",
        "tcp_new_user_pass" : "admin1234",
        "tcp_repeat_user_pass" : "admin1234",
        "tcp_new_user_email" : "testing@the_proof_of_concept.com",
        "tcp_role" : "administrator"
        }

Deciphering the code

First, we need to locate the insecure AJAX action tcp_register_and_login_ajax. This AJAX action is created in the file modules/LoginRegister.class.php on line 42, notice the "nopriv" which identifies this AJAX action is accessible without being logged in to the website.

42            add_action( 'wp_ajax_nopriv_tcp_register_and_login_ajax', array( $this, 'tcp_register_and_login_ajax' ) );

The tcp_register_and_login_ajax AJAX action calls the function named tcp_register_and_login_ajax() which is defined on lines 196-201, but really extends all the way to line 269 as the function calls tcp_register_and_login_ajax() which itself calls _tcp_register_and_login().

The security bug exists on lines 225-226 and 245-246 where they add the user to any role named in the variable $_REQUEST['tcp_role']

225            $roles         = isset( $_REQUEST['tcp_role'] ) ? explode( ',', $_REQUEST['tcp_role'] ) : array( 'customer' );
226            if ( ! is_array( $roles ) ) $roles = array( $roles );
227 - 244 ...
245                        foreach( $roles as $role )
246                            $user->add_role( $role );

Writing the patch

This bug was caused because the code allows a user controlled value ($_REQUEST['tcp_role']) to determine which role(s) to assign the new user to. Attackers who set tcp_role to "administrator" get a new administrator user in just one request.

We can patch this by not allowing user controlled values to choose roles. We can see on line 225 if $_REQUEST['tcp_role'] is not set, then new accounts should be added to a 'customer' role, so let's make this default role value to unchangeable and always use the default 'customer' role.

Lines 225-226 should look like the following:

224  // Last Patch provided by Patchstack (RR)
225            $roles = array( 'customer' );
226  // End Last Patch

Now, when the proof of concept from earlier the value of tcp_role is ignored and the user will be added to the default 'customer' role.

# sudo -u www-data wp user list
+----+---------------+--------------+----------------------+---------------------+--------------------------+
| ID | user_login    | display_name | user_email           | user_registered     | roles                    |
+----+---------------+--------------+----------------------+---------------------+--------------------------+
| 1  | admin         | admin        | admin@localhost | 2022-02-24 20:05:51 | administrator      
| 4  | admin_09      | admin_09     | testing@the_proof_of_concept.com     | 2022-12-06 20:33:21 | subscriber,administrator |
| 6  | admin_11      | admin_11     | testing@blah.com     | 2022-12-06 21:21:28 | subscriber,customer      |
+----+---------------+--------------+----------------------+---------------------+--------------------------+

Writing the WAF rule (mod_security)

Writing a WAF rule is simple and similar to the above. We do not want to trust requests to the tcp_register_and_login_ajax AJAX endpoint if it has the $_REQUEST['tcp_role'] variable set to arbitrary values. We can easily write use the following rule to blocks these requests.

SecRule ARGS:action tcp_register_and_login_ajax "deny,id:11115,chain"
SecRule ARGS_NAMES "tcp_role" "chain"
SecRule REQUEST_FILENAME "/wp-admin/admin-ajax\.php$"

With this rule in place, the proof of concept for this exploit will fail. However, any request that lacks a "tcp_role" value will still go through.

# python3 thecartpress_admin_creator.py http://target_host/
TheCartPress <= 1.5.3.6 - Unauthenticated Privilege Escalation
Author -> space_hen (www.github.com/spacehen)
Inserting admin...
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access this resource.</p>
<hr>
<address>Apache (Ubuntu) Server at target_host Port 80</address>
</body></html>

Conclusions

In the process of reviewing and writing this patch, I learned to never trust user controlled values to assign a new account's "role" for sure. But, we may be missing a more important point.

Shopping cart software is nothing to skimp on. I cannot recommend anyone continue using thecartpress due to the abandonment. But, I hope we learned something today. Something about secure development practices, and something about the importance of supported software.

Take the time to find a reputable vendor for your e-commerce websites. Vendors you can trust, so your customers trust you. Don't forget to support your developers too, so they can support you and your website's needs if any security bugs come up.

The latest in Security Advice

Looks like your browser is blocking our support chat widget. Turn off adblockers and reload the page.
crossmenu