This article was originally published on this site


themegrill demo importer

The ThemeGrill Demo Importer plugin has 200.000+ active installations and can be used to import ThemeGrill official themes demo content, widgets and theme settings with just one click.

In versions 1.3.4 and above and versions 1.6.1 and below, there is a vulnerability that allows any unauthenticated user to wipe the entire database to its default state after which they are automatically logged in as an administrator.

themegrill demo importer

The prerequisite is that there must be a theme installed and activated that was published by ThemeGrill. In order to be automatically logged in as an administrator, there must be a user called “admin” in the database. Regardless of this condition, the database will still be wiped to its default state.

Based on the SVN commit history, this issue has existed in the code for roughly 3 years, since version 1.3.4.

Technical Details

Once the plugin detects that a ThemeGrill theme is installed and activated, it loads the file /includes/class-demo-importer.php which hooks reset_wizard_actions into admin_init on line 44.

The admin_init hook runs not only in the admin environment but also on calls to /wp-admin/admin-ajax.php which does not require a user to be authenticated.

The function reset_wizard_actions looks a bit like the following (irrelevant code removed):

       public function reset_wizard_actions() {
                global $wpdb, $current_user;

                if ( ! empty( $_GET['do_reset_wordpress'] ) ) {

                        ///

                        if ( 'admin' != $current_user->user_login ) {
                                $user = get_user_by( 'login', 'admin' );
                        }

                        if ( empty( $user->user_level ) || $user->user_level < 10 ) {
                                $user = $current_user;
                        }

                        // Drop tables.
                        $drop_tables = $wpdb->get_col( sprintf( "SHOW TABLES LIKE '%s%%'", str_replace( '_', '_', $wpdb->prefix ) ) );
                        foreach ( $drop_tables as $table ) {
                                $wpdb->query( "DROP TABLE IF EXISTS $table" );
                        }

                        // Installs the site.
                        $result = wp_install( $blogname, $user->user_login, $user->user_email, $blog_public );

                        // Updates the user password with a old one.
                        $wpdb->update(
                                $wpdb->users,
                                array(
                                        'user_pass'           => $user->user_pass,
                                        'user_activation_key' => '',
                                ),
                                array( 'ID' => $result['user_id'] )
                        );

                        // Set up the Password change nag.
                        $default_password_nag = get_user_option( 'default_password_nag', $result['user_id'] );
                        if ( $default_password_nag ) {
                                update_user_option( $result['user_id'], 'default_password_nag', false, true );
                        }

                        ///

                        // Update the cookies.
                        wp_clear_auth_cookie();
                        wp_set_auth_cookie( $result['user_id'] );

                        // Redirect to demo importer page to display reset success notice.
                        wp_safe_redirect( admin_url( 'themes.php?page=demo-importer&browse=all&reset=true' ) );
                        exit();
                }
        }

Here we see that there is no authentication check and only the do_reset_wordpress parameter needs to be present in the URL on any “admin” based page of WordPress, including /wp-admin/admin-ajax.php.

If we are currently not logged in, it will retrieve the “admin” user object from WordPress and then drop all WordPress tables that start with the defined WordPress database prefix.

Once all tables have been dropped, it will populate the database with the default settings and data after which it will set the password of the “admin” user to its previously known password.

However, this does not matter since we are automatically logged in as “admin” near the end of the function. If the “admin” user does not exist in the database then the users’ table will remain empty and you will not be automatically logged in as any user.

The Patch

The patch can be found here which shows that they added a current_user_can( ‘manage_options’ ) check to the reset_wizard_actions method.

This is a serious vulnerability and can cause a significant amount of damage. Since it requires no suspicious-looking payload just like our previous finding in InfiniteWP, it is not expected for any firewall to block this by default and a special rule needs to be created to block this vulnerability.

Timeline

06-02-2020 – Discovery of the issue and released a patch to all WebARX customers.
06-02-2020 – Reported the issue to the developer of the plugin.
11-02-2020 – Second attempt to reach out to the developer.
14-02-2020 – Received email from developer, resent the issue to them.
16-02-2020 – Developer published a new version which fixes the issue.

We are closely monitoring the ThemeGrill Demo Importer vulnerability and will update the article over-time with potential IOC and important threat information.

If you wish to stay updated about the vulnerabilities via social media, consider joining our Facebook community and follow us on Twitter.

WordPress security