Unauthenticated function injection vulnerability fixed in 15 WordPress themes.

Fifteen WordPress themes were prone to a critical unauthenticated function injection vulnerability, among other issues.

Vulnerable Themes

  • Shapely <=1.2.7 (60,000+ installations). Fixed in v1.2.9.
  • NewsMag <=2.4.1 (20,000+ installations). Fixed in v2.4.2.
  • Activello <=1.4.0 (10,000+ installations). Fixed in v1.4.2.
  • Illdy <=2.1.4 (10,000+ installations). Fixed in v2.1.7.
  • Allegiant <=1.2.2 (6,000+ installations). Fixed in v1.2.6.
  • Newspaper X <=1.3.1 (2,000+ installations). Fixed in v1.3.2.
  • Pixova Lite <=2.0.5 (2,000+ installations). Fixed in v2.0.7.
  • Brilliance <=1.2.7 (1,000+ installations). Fixed in v1.3.0.
  • MedZone Lite <=1.2.4 (1,000+ installations). Fixed in v1.2.6.
  • Regina Lite <=2.0.4 (1,000+ installations). Fixed in v2.0.6.
  • Transcend <=1.1.8 (900+ installations). Fixed in v1.2.0.
  • Affluent <1.1.0 (500+ installations) Fixed in v1.1.2.
  • Bonkers <=1.0.4 (100+ installations). Fixed in v1.0.6.
  • Antreas <=1.0.2 (70+ installations). Fixed in v1.0.7.
  • NatureMag Lite <=1.0.4. Partially fixed in v1.0.5, however the theme is no longer available in the wordpress.org repo.

Unauthenticated Function Injection

Depending on the theme and the version, the vulnerability can affect different scripts and three different WordPress AJAX actions:

  • epsilon_framework_ajax_action
  • epsilon_dashboard_ajax_callback
  • welcome_screen_ajax_callback

Some themes include up to two vulnerable actions. They are all almost identical, with just a few differences. In this advisory we’ll see the epsilon_framework_ajax_action action found in the “inc/libraries/epsilon-framework/class-epsilon-framework.php” script from the NewsMag theme v2.4.1 and a slightly different version from the Allegiant theme v1.2.2 found in the “includes/libraries/epsilon-framework/classes/class-epsilon-ajax-controller.php” script.

The plugin registers the epsilon_framework_ajax_action AJAX action and makes it accessible to all users, authenticated or not, with the WordPress wp_ajax_* and wp_ajax_nopriv_* hooks:

    add_action( 'wp_ajax_epsilon_framework_ajax_action', array(
       $this,
         'epsilon_framework_ajax_action',
      ) );
      add_action( 'wp_ajax_nopriv_epsilon_framework_ajax_action', array(
       $this,
       'epsilon_framework_ajax_action',
    ) );

The function takes three POST user input, assigns them to the $class, $method and $args variables and calls the corresponding class and method with optional arguments:

  public function epsilon_framework_ajax_action() {
      if ( 'epsilon_framework_ajax_action' !== $_POST['action'] ) {
         wp_die(
            json_encode(
               array(
                  'status' => false,
                  'error'  => 'Not allowed',
               )
            )
         );
      }
   
      if ( count( $_POST['args']['action'] ) !== 2 ) {
         wp_die(
            json_encode(
               array(
                  'status' => false,
                  'error'  => 'Not allowed',
               )
            )
         );
      }
   
      if ( ! class_exists( $_POST['args']['action'][0] ) ) {
         wp_die(
            json_encode(
               array(
                  'status' => false,
                  'error'  => 'Class does not exist',
               )
            )
         );
      }
   
      $class  = $_POST['args']['action'][0];
      $method = $_POST['args']['action'][1];
      $args   = $_POST['args']['args'];
   
      $response = $class::$method( $args );

Although it should be accessible to an administrator only, it lacks a capability check and a security nonce and thus could lead to a critical unauthenticated function injection attack.

A variant of this code can be found in a few other themes such as Allegiant: a security nonce was added to the epsilon_framework_ajax_action method:

public function epsilon_framework_ajax_action() {
   if ( isset( $_POST['args'], $_POST['args']['nonce'] ) && ! wp_verify_nonce( sanitize_key( $_POST['args']['nonce'] ), 'epsilon_nonce' ) ) {
      wp_die(
         wp_json_encode(
            array(
               'status' => false,
               'error'  => esc_html__( 'Not allowed', 'epsilon-framework' ),
            )
         )
       );
   }
   ...
   ...

However, the nonce is checked only if it is set. If it isn’t, the request will not be rejected.

Unauthenticated Plugin Activation/Deactivation

Brilliance, Activello and Newspaper X are affected by this vulnerability. Although they use different function names, the vulnerability and the way to exploit it are identical. In this advisory, we’ll see Activello v1.4.0.

In the “inc/welcome-screen/class-activello-welcome.php” script, the activello_activate_plugin and activello_deactivate_plugin functions are registered via the admin_init hook in the __construct method:

add_action( 'admin_init', array( $this, 'activello_activate_plugin' ) );
add_action( 'admin_init', array( $this, 'activello_deactivate_plugin' ) );

The functions are used to activate or deactive WordPress plugins:

public function activello_activate_plugin() {
   if ( ! empty( $_GET ) ) {
      /**
       * Check action
       */
      if ( ! empty( $_GET['action'] ) && ! empty( $_GET['plugin'] ) && 'activate_plugin' === $_GET['action'] ) {
         $active_tab = $_GET['tab'];
         $url        = self_admin_url( 'themes.php?page=activello-welcome&tab=' . $active_tab );
         activate_plugin( $_GET['plugin'], $url );
      }
   }
}

public function activello_deactivate_plugin() {
   if ( ! empty( $_GET ) ) {
      /**
       * Check action
       */
      if ( ! empty( $_GET['action'] ) && ! empty( $_GET['plugin'] ) && 'deactivate_plugin' === $_GET['action'] ) {
         $active_tab = $_GET['tab'];
         $url        = self_admin_url( 'themes.php?page=activello-welcome&tab=' . $active_tab );
         $current    = get_option( 'active_plugins', array() );
         $search     = array_search( $_GET['plugin'], $current );
         if ( array_key_exists( $search, $current ) ) {
            unset( $current[ $search ] );
         }
         update_option( 'active_plugins', $current );
      }
   }
}

They lack capability checks and security nonces. Because the admin_init hook can be triggered by anyone, an unauthenticated user can activate or deactivate any plugin on the blog. An attacker would likely exploit this vulnerability to deactivate a security or antispam plugin, for instance.

Recommendations

Update any of the above themes to the latest available version.
If you are using our web application firewall for WordPress, NinjaFirewall WP Edition (free) and NinjaFirewall WP+ Edition (premium), you are protected against this vulnerability since October 2019.

Timeline

The vulnerabilities were discovered and reported to the authors between October and December 2019, and then escalated to the wordpress.org themes team on March and August 2020.

Stay informed about the latest vulnerabilities