This blog post is about the WP Time Capsule plugin vulnerability. If you’re a WP Time Capsule plugin user, please update to at least version 1.22.21.
All paid Patchstack users are protected from this vulnerability. Sign up for the free Community account first, to scan for vulnerabilities and apply protection for only $5 / site per month with Patchstack.
For plugin developers, we have security audit services and Enterprise API for hosting companies.
About the Backup and Staging by WP Time Capsule
Plugin
Backup and Staging by WP Time Capsule is a WordPress plugin and has more than 20,000 active installations. It is described as a plugin that was created to ensure peace of mind with WP updates and put the fun back into WordPress. It uses the cloud apps’ native file versioning system to detect changes and backs up just the changed files and database entries to your account.
The security vulnerability
This plugin with version 1.22.20 and lower contains an unauthenticated broken authentication and privilege escalation vulnerability. As a result, it allows any unauthenticated user to login into the site as an administrator with a single request. The only pre-requisite is that someone has setup the plugin with a connection to the wptimecapsule.com site.
This vulnerability exists because of a logical mistake in the code (1.22.19), allowing us to bypass certain if statements to ultimately call a function that logs us in as an administrator. Version 1.22.20 still contained a vulnerability to bypass the patch through different means.
We will break it down into the order of execution in the vulnerable file wptc-cron-functions.php
- __construct() -> parse_request()
- parse_request() -> decode_server_request_wptc()
- decode_server_request_wptc() -> is_valid_wptc_request()
- is_valid_wptc_request() -> wptc_decode_auth_token()
- plugins_loaded > init_admin_login()
- wptc_login_as_admin()
1. __construct() -> parse_request()
The __construct function is called on all page loads, which then calls parse_request.
2. parse_request() -> decode_server_request_wptc()
The parse_request function will then call the decode_server_request_wptc function to determine if raw POST data is sent following the proper format.
3. decode_server_request_wptc() -> is_valid_wptc_request()
The decode_server_request_wptc function will base64_decode and then json_decode the raw POST data and will pass this JSON decoded data to the is_valid_wptc_request function.
4. is_valid_wptc_request() -> wptc_decode_auth_token()
The is_valid_wptc_request function will make sure that the authorization and source parameters were passed and will then call the wptc_decode_auth_token function with the authorization parameter value.
The wptc_decode_auth_token function explodes on a dot and takes the second index of the resulting array, which is then base64 decoded and JSON decoded and returned, if it is not empty.
This value is then compared using != against a local option value, which we can bypass by passing a boolean true value.
5. plugins_loaded > init_admin_login()
By bypassing this != check, the request_data private variable will be filled after which the init_admin_login function is called (hooked to plugins_loaded).
This in turn calls should_enable_admin_login in which we can bypass a check by passing a type parameter to the raw POST payload set to a particular value. By doing this, we finally reach the wptc_login_as_admin function which will log us in as an administrator.
The patch
The developers of the Backup and Staging by WP Time Capsule plugin applied a (partial) patch on July 3rd, 2024, within 6 hours of informing them about the vulnerability.
The patch is as simple as changing the appID comparison to use !== instead of !=, which will perform a strict check of variables for value and data type. This partially fixes the initial vulnerability, however in theory it’s still easy to bypass this check by guessing the (relatively short) appID value.
Version 1.22.21 properly fixes this by comparing with an additional hash.
Conclusion
We always recommend applying proper access control and authorization checks when writing a function that involves setting the authorization of a request based on user input variables.
When working with JSON decoded values and comparisons, make sure you compare using === or !== as booleans in JSON strings will be cast to the PHP boolean data type when the json_decode function is executed against it.
Timeline
Help us make the Internet a safer place
Making the WordPress ecosystem more secure is a team effort, and we believe that plugin developers and security researchers should work together.
- If you’re a plugin developer, join our mVDP program that makes it easier to report, manage and address vulnerabilities in your software.
- If you’re a security researcher, join Patchstack Alliance to report vulnerabilities & earn rewards.