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=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();
}