Vulnerable WordPress Plugin: SendGrid – https://wordpress.org/plugins/sendgrid-email-delivery-simplified/
Researcher: Prashant Baldha (prashant@prashantwp.com)
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.
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
- Setup a WordPress MultiSite Network.
- Install the SendGrid Plugin from https://wordpress.org/plugins/sendgrid-email-delivery-simplified/ and activate it network-wide.
- SignUp to the SendGrid on the https://sendgrid.com/.
- Go to https://app.sendgrid.com/settings/sender_auth and verify your email as a single sender.
- Go to https://app.sendgrid.com/settings/api_keys and make SendGrid full API access key.
- Open the wp-config.php and write below contestants:
define( 'SENDGRID_API_KEY', 'XXX' );
define( 'SENDGRID_SEND_METHOD', 'api' );
define( 'SENDGRID_FROM_NAME', 'Prashant' );
define( 'SENDGRID_FROM_EMAIL', 'prashant@prashantwp.com' );
define( 'SENDGRID_REPLY_TO', 'prashant@prashantwp.com' );
- 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=bc060d13cfand 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_fuctionrenders 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();
}
Discover more from Prashant Baldha on WordPress
Subscribe to get the latest posts sent to your email.

Appreciate the time you put into this — it’s packed with value.
Your writing style is engaging and easy to follow. Keep it up!
Could you expand on the third tip? I’d love to know more.
This article is well-researched and clearly written. Appreciate the effort!
Informative and well-referenced. Do you have a resource list?
Loved the conversational tone — made learning enjoyable.
Clear, concise, and useful — exactly what I needed today.
Thoughtful points and well-supported arguments. Good job!
Appreciate the time you put into this — it’s packed with value.
I appreciate you sharing this blog post. Thanks Again. Cool.
Please write more about the challenges you mentioned — curious for solutions.
Thanks for the breakdown — the comparisons were particularly useful.
Nice post. I learn something totally new and challenging on websites
Döner dürüm istedim, etin lezzeti, ekmeğin tazeliği, sosun dengesi tam olması gerektiği gibiydi. Rize’de fast food zincirlerine alternatif, kaliteli bir lezzet durağı.
I very delighted to find this internet site on bing, just what I was searching for as well saved to fav
Very well presented. Every quote was awesome and thanks for sharing the content. Keep sharing and keep motivating others.
I appreciate the honesty in your assessment — refreshing to see.
This cleared up a lot of uncertainty — I feel more confident now.
Helpful post — could you also share examples for beginners?
I am truly thankful to the owner of this web site who has shared this fantastic piece of writing at at this place.
I love the practical tips in this post. Can you recommend further reading?