CVE-2021-34629 – SendGrid <= 1.11.8 Authorization Bypass Vulnerability

Vulnerable WordPress Plugin: SendGrid – https://wordpress.org/plugins/sendgrid-email-delivery-simplified/

Researcher: Prashant Baldha ([email protected])

You can find the details publicly disclosed CVE detail on WordFence: https://www.wordfence.com/vulnerability-advisories/#CVE-2021-34629

The SendGrid Plugin was the official SendGrid WordPress Plugin. It is no longer maintained, and It has 100,000+ active installs.


Description: There is an Authorization bypass vulnerability in the SendGrid plugin if we run the plugin in multisite. The Subscriber user role can get SendGrid statistics intended to use only by the Administrator user role. 

Screenshot 2021-07-07 at 9.55.05 PM.png
SendGrid WordPress Plugin Proof of Concept in BurpSuite

Which part of plugin infected: The subscriber user can get SendGrid statistics from the main site of Multisite.
Version: 1.11.8 (Latest Version)

Proof of Concept:

Prerequisite setup
define( 'SENDGRID_API_KEY', 'XXX' );
define( 'SENDGRID_SEND_METHOD', 'api' );
define( 'SENDGRID_FROM_NAME', 'Prashant' );
define( 'SENDGRID_FROM_EMAIL', '[email protected]' );
define( 'SENDGRID_REPLY_TO', '[email protected]' );
  • Make sure that you have changed the API key, name, and from email and reply to email in the above code snippet.
  • Go to http://yoursite.com/wp-admin/network/admin.php?page=sendgrid-settings, scroll down, and send a test mail to you.
  • Go to http://yoursite.com/wp-admin/index.php?page=sendgrid-statistics. Here you can see SendGrid statistics. This statistical data can be get by subscriber users of the main site.
Exploit Vulnerability
  • Register as a Subscriber to the main site of this network multi-site.
  • Login as the subscriber to the admin dashboard, View Page source and find the javascript var sendgrid_vars and copy the sendgrid nonce.
  • Send the request to the URL /wp-admin/admin-ajax.php with subscriber cookie and the given POST parameters action=sendgrid_get_stats&start_date=2021-06-30&end_date=2021-07-07&type=wordpress&sendgrid_nonce=bc060d13cf and you will see SendGrid statistics in JSON format.

Technical details of Vulnerability

  • The plugin author has written the below code for adding action of the admin_enqueue_scripts.In this code snippet, You should notice that the plugin author is enqueueing scripts in multisite and if the current site is the main site. Refer to the “elseif” condition in the below code.
 /**
   * Method that is called to set up the settings menu
   *
   * @return void
   */
  public static function set_up_menu()
  {
    if ( ( ! is_multisite() and current_user_can('manage_options') ) || ( is_multisite() and ! is_main_site() and get_option( 'sendgrid_can_manage_subsite' ) ) ) {
      // Add SendGrid widget in dashboard
      add_action( 'wp_dashboard_setup', array( __CLASS__, 'add_dashboard_widget' ) );

      // Add SendGrid stats page in menu
      add_action( 'admin_menu', array( __CLASS__, 'add_statistics_menu' ) );

      // Add SendGrid javascripts in header
      add_action( 'admin_enqueue_scripts', array( __CLASS__, 'add_headers' ) );

      // Add SendGrid page for get statistics through ajax
      add_action( 'wp_ajax_sendgrid_get_stats', array( __CLASS__, 'get_ajax_statistics' ) );
    } elseif ( is_multisite() and is_main_site() ) {
      // Add SendGrid stats page in menu
      add_action( 'network_admin_menu', array( __CLASS__, 'add_network_statistics_menu' ) );

      // Add SendGrid javascripts in header
      add_action( 'admin_enqueue_scripts', array( __CLASS__, 'add_headers' ) );

      // Add SendGrid page for get statistics through ajax
      add_action( 'wp_ajax_sendgrid_get_stats', array( __CLASS__, 'get_ajax_statistics' ) );
    }
  }
  • The add_header_fuction renders scripts and localize variable in all admin dashboard page:
  /**
   * Method that is called to set up the settings menu
   *
   * @return void
   */
  public static function set_up_menu()
  {
    if ( ( ! is_multisite() and current_user_can('manage_options') ) || ( is_multisite() and ! is_main_site() and get_option( 'sendgrid_can_manage_subsite' ) ) ) {
      // Add SendGrid settings page in the menu
      add_action( 'admin_menu', array( __CLASS__, 'add_settings_menu' ) );
      // Add SendGrid settings page in the plugin list
      add_filter( 'plugin_action_links_' . self::$plugin_directory, array( __CLASS__, 'add_settings_link' ) );
    } elseif ( is_multisite() and is_main_site() ) {
      // Add SendGrid settings page in the network admin menu
      add_action( 'network_admin_menu', array( __CLASS__, 'add_network_settings_menu' ) );
    }
    // Add SendGrid Help contextual menu in the settings page
    add_filter( 'contextual_help', array( __CLASS__, 'show_contextual_help' ), 10, 3 );
    // Add SendGrid javascripts in header
    add_action( 'admin_enqueue_scripts', array( __CLASS__, 'add_headers' ) );
  }
  • AJAX action has no code to check the authorization of the user. The AJAX action code as below:
  /**
   * Get SendGrid stats from API and return JSON response,
   * this function work like a page and is used for ajax request by javascript functions
   *
   * @return void;
   */
  public static function get_ajax_statistics()
  {
    if ( ! isset( $_POST['sendgrid_nonce'] ) || ! wp_verify_nonce( $_POST['sendgrid_nonce'], 'sendgrid-nonce') ) {
      die( 'Permissions check failed' );
    }

    $parameters = array();
    $parameters['apikey']   = Sendgrid_Tools::get_api_key();
    $parameters['data_type'] = 'global';

    if ( array_key_exists( 'days', $_POST ) ) {
      $parameters['days'] = $_POST['days'];
    } else {
      $parameters['start_date'] = $_POST['start_date'];
      $parameters['end_date']   = $_POST['end_date'];
    }

    $endpoint = 'v3/stats';

    if ( isset( $_POST['type'] ) && 'general' != $_POST['type'] ) {
      if( 'wordpress' == $_POST['type'] ) {
        $parameters['categories'] = 'wp_sendgrid_plugin';
      } else {
        $parameters['categories'] = urlencode( $_POST['type'] );
      }
      $endpoint = 'v3/categories/stats';
    }
    echo Sendgrid_Tools::do_request( $endpoint, $parameters );

    die();
  }

Leave a Reply

Prashant Baldha