One Million Sites Affected: Four Severe Vulnerabilities Patched in Ninja Forms

On January 20, 2021, our Threat Intelligence team responsibly disclosed four vulnerabilities in Ninja Forms, a WordPress plugin used by over one million sites. One of these flaws made it possible for attackers to redirect site administrators to arbitrary locations. The second flaw made it possible for attackers with subscriber level access or above to install a plugin that could be used to intercept all mail traffic. The third flaw made it possible for attackers with subscriber level access to to retrieve the Ninja Form OAuth Connection Key that could be used to establish a connection with the Ninja Forms central management dashboard. The final flaw made it possible for attackers to disconnect a site’s OAuth Connection if they could trick a site’s administrator into performing an action. These flaws could be used to take over a WordPress site and redirect site owners to malicious sites.

We initially reached out to Saturday Drive, the plugin’s parent company, on January 20, 2021 through their responsible disclosure email contact and provided the full disclosure details at the time of reporting. Just a few days later, on January 25, 2021, Ninja Forms released a patch for 3 out of the 4 vulnerabilities. We followed up to let them know that one of the vulnerabilities was still present. They released a final patch on February 8, 2021.

We consider these to be severe vulnerabilities that could ultimately lead to complete site takeover, therefore, we highly recommend updating to the fully patched version, 3.4.34.1, immediately.

Wordfence Premium users received a firewall rule to protect against any exploits targeting these vulnerabilities on January 20, 2021. Sites still using the free version of Wordfence will receive the same protection on February 19, 2021.


Description: Authenticated SendWP Plugin Installation and Client Secret Key Disclosure
Affected Plugin: Ninja Forms Contact Form – The Drag and Drop Form Builder for WordPress
Plugin Slug: ninja-forms
Affected Versions: <= 3.4.33
CVE ID: CVE-2021-24163
CVSS Score: 9.9 (Critical)
CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
Fully Patched Version: 3.4.34

Ninja Forms is one of the most popular intuitive form creation plugins in the WordPress plugin repository. It provides users with the ability to create forms using drag and drop capabilities, making the design process much more simple for WordPress users.

As part of the plugin’s functionality it offers the ability to install “Add-Ons,” some of which offer services. One of these services is SendWP, which is an email delivery and logging service intended to make mail handling with WordPress simpler. From the Ninja Form plugin’s Addon dashboard, it offers the ability to set up this service with just a few clicks. In order to provide this functionality, the plugin registers the AJAX action wp_ajax_ninja_forms_sendwp_remote_install.

 add_action( 'wp_ajax_ninja_forms_sendwp_remote_install', 'wp_ajax_ninja_forms_sendwp_remote_install_handler' );

This AJAX action is tied to the function wp_ajax_ninja_forms_sendwp_remote_install_handler, that checks to see if the SendWP plugin is installed and activated. If the plugin is not currently installed, then it performs the installation and activation of the SendWP plugin.

 function wp_ajax_ninja_forms_sendwp_remote_install_handler () {

    $all_plugins = get_plugins();
    $is_sendwp_installed = false;
    foreach(get_plugins() as $path => $details ) {
        if(false === strpos($path, '/sendwp.php')) continue;
        $is_sendwp_installed = true;
        activate_plugin( $path );
        break;
    }

    if( ! $is_sendwp_installed ) {

        $plugin_slug = 'sendwp';

        include_once ABSPATH . 'wp-admin/includes/plugin-install.php';
        include_once ABSPATH . 'wp-admin/includes/file.php';
        include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
        
        /*
        * Use the WordPress Plugins API to get the plugin download link.
        */
        $api = plugins_api( 'plugin_information', array(
            'slug' => $plugin_slug,
        ) );
        if ( is_wp_error( $api ) ) {
            ob_end_clean();
            echo json_encode( array( 'error' => $api->get_error_message(), 'debug' => $api ) );
            exit;
        }
        
        /*
        * Use the AJAX Upgrader skin to quietly install the plugin.
        */
        $upgrader = new Plugin_Upgrader( new WP_Ajax_Upgrader_Skin() );
        $install = $upgrader->install( $api->download_link );
        if ( is_wp_error( $install ) ) {
            ob_end_clean();
            echo json_encode( array( 'error' => $install->get_error_message(), 'debug' => $api ) );
            exit;
        }
        
        /*
        * Activate the plugin based on the results of the upgrader.
        * @NOTE Assume this works, if the download works - otherwise there is a false positive if the plugin is already installed.
        */
        $activated = activate_plugin( $upgrader->plugin_info() );

Once the plugin has been installed successfully, the function will return the registration url, along with the client_name, client_secret, register_url, and client_url. This is used to show users the sign-up page and easily connect their WordPress instance with SendWP.

 echo json_encode( array(
        'partner_id' => 16,
        'register_url' => esc_url(sendwp_get_server_url() . '_/signup'),
        'client_name' => esc_attr( sendwp_get_client_name() ),
        'client_secret' => esc_attr( sendwp_get_client_secret() ),
        'client_redirect' => esc_url(sendwp_get_client_redirect()),
        'client_url' => esc_url( sendwp_get_client_url() ),
    ) );
    exit;

Unfortunately, this AJAX action did not have a capability check on it, nor did it have any nonce protection, therefore making it possible for low-level users, such as subscribers, to install and activate the SendWP plugin and retrieve the client_secret key needed to establish the SendWP connection.

How could this affect my WordPress site?
Due to the fact that the client_secret key is returned with the AJAX request, attackers with low-level access to a vulnerable WordPress site could establish a SendWP connection with their own SendWP account, thus making sites with open-registration particularly vulnerable. Once that connection is established, all mail from the WordPress site would be routed through and logged in the attackers SendWP account. At that point they can monitor all data emailed which could range from user Personally Identifiable Information (PII) from form submissions to reports generated on your site.

Further, an attacker could trigger a password reset for an administrative user account, if they could discover the username for an account. The password reset email with the password reset link would get logged in the attackers SendWP account, which they could then use to reset an administrator’s password and gain administrative access to a site. This could ultimately lead to remote code execution and site takeover by modifying theme/plugin files or uploading a malicious theme/plugin.

The SendWP service does cost $9 a month per site, with a $1 14-day trial. As such, it is less likely to be widely exploited. However, it would be a very valuable entry point for attackers seeking to compromise high-value targets.


Description: Authenticated OAuth Connection Key Disclosure
Affected Plugin: Ninja Forms Contact Form – The Drag and Drop Form Builder for WordPress
Plugin Slug: ninja-forms
Affected Versions: <= 3.4.34
CVE ID: CVE-2021-24164
CVSS Score: 7.7 (High)
CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:N
Fully Patched Version: 3.4.34.1

Another feature of Ninja Forms is the ability to connect to the Ninja Forms “Add-on Manager” service, a centralized dashboard that allows you to manage all purchased Ninja Forms Add-Ons to provision them to a WordPress site remotely. Just like with the SendWP service, it offers capabilities to set up this service with just a few clicks from the Ninja Form plugin’s Addon dashboard. In order to provide this functionality, the plugin registers the AJAX action wp_ajax_nf_oauth which is used to retrieve the connection_url that contains the information necessary, like the client_secret, to establish an OAuth connection with the Ninja Forms Add-On Management portal.

 public function setup() {
   add_action( 'wp_ajax_nf_oauth', function(){
     wp_die( json_encode( [
       'data' => [
         'connected' => ( $this->client_id ),
         'connect_url' => self::connect_url(),
       ]
     ] ) );
   });

Unfortunately, there was no capability check on this function. Low-level users, such as subscribers, were able to trigger the action and retrieve the connection url needed to establish a connection. They could also retrieve the client_id for an already established OAuth connection.

This meant that attackers could potentially establish an OAuth Connection for a vulnerable WordPress site with their own account. However, there would be some social engineering involved as the attacker would need to trick the site administrator into clicking a link to update the client_id in the database with the nf_oauth_connect AJAX action for the connection to be fully complete. From there, they could install any purchased Add-On plugins.


Description: Administrator Open Redirect
Affected Plugin: Ninja Forms Contact Form – The Drag and Drop Form Builder for WordPress
Plugin Slug: ninja-forms
Affected Versions: <= 3.4.33
CVE ID: CVE-2021-24165
CVSS Score: 4.8 (Medium)
CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N
Fully Patched Version: 3.4.34

As part of the OAuth connection process, the plugin registers an AJAX action, wp_ajax_nf_oauth_connect, that is registered to the function connect() which is used to redirect a site owner back to the WordPress site’s Ninja Forms service page after the user has finished the OAuth connection process.

 public function connect() {
    // Does the current user have admin privileges
    if (!current_user_can('manage_options')) {
      return;
    }

    // wp_verify_nonce( $_REQUEST['nonce'], 'nf-oauth-connect' );

    if( ! isset( $_GET[ 'client_id' ] ) ) return;

    $client_id = sanitize_text_field( $_GET[ 'client_id' ] );
    update_option( 'ninja_forms_oauth_client_id', $client_id );

    if( isset( $_GET[ 'redirect' ] ) ){
      $redirect = sanitize_text_field( $_GET[ 'redirect' ] );
      $redirect = add_query_arg( 'client_id', $client_id, $redirect );
      wp_redirect( $redirect );
      exit;
    }

    wp_safe_redirect( admin_url( 'admin.php?page=ninja-forms#services' ) );
    exit;
  }

This function uses wp_safe_redirect to redirect site owners back to the admin.php?page=ninja-forms#services page by default.However, if the ‘redirect’ parameter is supplied, then it would redirect the site administrator to an arbitrary URL supplied in that parameter.

Fortunately, there was a capability check on this function so that only administrators could use it. However, there is no protection on the redirection URL validating where the redirect goes, nor was there any protection to prevent an attacker from using the function to redirect a site administrator to a malicious location. There was the use of wp_verify_nonce(),however, it was commented out and rendered unusable. This made it possible for attackers to craft a URL with the redirect parameter set to an arbitrary site. If the attacker could trick an administrator into clicking the link, then they could be redirected to an external malicious site which could infect the administrator’s computer amongst other malicious actions.

Open redirect vulnerabilities exploit the inherent trust of the vulnerable domain to assist in getting someone to click on the open redirect link. For example, an attacker could craft a link with the redirect parameter containing a shortened URL and then ask a site owner to check out the link saying that the page was responding weirdly on their site. This would likely cause the site owner to click on the link and check out what the “inquiry” is referring to, and ultimately result in them being redirected to an external and malicious site.


Description: Cross-Site Request Forgery to OAuth Service Disconnection
Affected Plugin: Ninja Forms Contact Form – The Drag and Drop Form Builder for WordPress
Plugin Slug: ninja-forms
Affected Versions: <= 3.4.33
CVE ID: CVE-2021-24166
CVSS Score: 6.1 (Medium)
CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:N/I:L/A:L
Fully Patched Version: 3.4.34

An additional feature of the Ninja Forms Add-Ons Manager was the ability to easily disconnect an established OAuth connection with just a few clicks. In order to provide this functionality, the plugin registered an AJAX action wp_ajax_nf_oauth_disconnect tied to the function disconnect(). The disconnect() function would simply disconnect an established connection by deleting the options associated with the connection settings in the database.

add_action( 'wp_ajax_nf_oauth_disconnect', [ $this, 'disconnect' ] );

Unfortunately, this feature did not have nonce protection. This made it possible for attackers to craft a legitimate request, host it externally, and if successful in tricking an administrator into clicking a link or attachment, send a request to disconnect the current OAuth connection. Though there would be no critical harm being exploited by this vulnerability, it could be a puzzling experience for a site owner.

 public function disconnect() {

  // Does the current user have admin privileges
  if (!current_user_can('manage_options')) {
    return;
  }

  do_action( 'ninja_forms_oauth_disconnect' );

  $url = trailingslashit( $this->base_url ) . 'disconnect';
  $args = [
    'blocking' => false,
    'method' => 'DELETE',
    'body' => [
      'client_id' => get_option( 'ninja_forms_oauth_client_id' ),
      'client_secret' => get_option( 'ninja_forms_oauth_client_secret' )
    ]
  ];
  $response = wp_remote_request( $url, $args );

  delete_option( 'ninja_forms_oauth_client_id' );
  delete_option( 'ninja_forms_oauth_client_secret' );
  wp_die( 1 );
}

Disclosure Timeline

January 20, 2021 – Conclusion of the plugin analysis that led to the discovery of the four vulnerabilities. We develop firewall rules to protect Wordfence customers and release them to Wordfence Premium users. We make our initial contact and send full disclosure via the Security Disclosure contact listed on the Ninja Forms website.
January 21, 2021 – We receive a response confirming that Saturday Drive received our information and will begin working on a fix.
January 25, 2021 – The first patched version of the plugin is released as version 3.4.34
January 26, 2021 – We check to see if the release addresses all reported issues. We discover one endpoint is still vulnerable and follow-up with our contact at Saturday Drive. We receive confirmation the same day that they will send the details to the developers to work on a fix.
February 4, 2021 – We follow up to check on the status of a fix, and we are informed that it should be released in the next couple of days.
February 8, 2021 – A final patched version of the plugin is released as version 3.4.34.1. We verify again that the vulnerabilities have been patched.
February 19, 2021 – Free Wordfence users receive firewall rules.

Conclusion

In today’s post, we detailed four flaws in the Ninja Forms plugin that granted attackers the ability to obtain sensitive information while also allowing them the ability to redirect administrative users. These flaws have been fully patched in version 3.4.34.1. We recommend that users immediately update to the latest version available, which is version 3.5.0 at the time of this publication.

Wordfence Premium users received firewall rules protecting against this vulnerability on January 20, 2021, while those still using the free version of Wordfence will receive the same protection on February 19, 2021.

If you know a friend or colleague who is using this plugin on their site, we highly recommend forwarding this advisory to them to help keep their sites protected as these are considered critical severity issues that can result in remote code execution.

Special thanks to the creators of Ninja Forms, Saturday Drive, for working quickly to quick patches out and for providing a contact for responsible disclosure directly on their website.

Did you enjoy this post? Share it!

Comments

No Comments