Hacker Targeting Recently Incompletely Fixed Vulnerability in WordPress Plugin Icegram Express
Over the weekend, we had a hacker attempt to exploit a SQL injection vulnerability that turned out to be one fixed recently in the 90,000+ install WordPress plugin Icegram Express on our website. We don’t use the plugin, so the exploitation attempt appears to be part of an untargeted attempt to exploit this.
Upon reviewing the relevant code, we found that it still isn’t properly secured, and neither is other, similarly accessed, code. We have reached out to the developer about that. Based on the continued insecurity, we would recommend not using the plugin unless it has a more thorough security review and all the issues are addressed.
The developer of the plugin credited Wordfence in the changelog for the fixed version. It is rather concerning that Wordfence didn’t make sure the relevant code was properly secured and hasn’t warned about the continued insecurity.
The exploit attempt came from an IP address, 5.253.204.66, belonging to M247, which is a data center operator, which VPN services frequently utilize. The exploit attempt made a request to https://www.pluginvulnerabilities.com/wp-admin/admin-post.php with the following POST data sent with the request:
Array ( [page] => es_subscribers [is_ajax] => 1 [action] => _sent [advanced_filter] => Array ( [conditions] => Array ( [0] => Array ( [0] => Array ( [field] => status=99924)))union(select(sleep(4)))-- [operator] => = [value] => 1111 ) ) ) ) )
A portion of that POST data, “union(select(sleep(4)),” makes it appear that this was an attempt to exploit a SQL injection vulnerability, as that is SQL code to add an additional statement to a query, which pauses running the query for 4 seconds.
With another value in the POST data, “es_subscribers”, the only WordPress plugin we found that referenced it was Icegram Express.
Checking the latest version of the plugin, we found that the request wouldn’t do anything, as it was missing data needed for a security check to be passed. Looking over recent changes, we found that the security check was added in version 5.7.15, which was released on March 28.
The security check added was placed in the function maybe_apply_bulk_actions_on_all_contacts() in the file /lite/admin/class-email-subscribers-admin.php. That function runs during admin_init:
119 | add_action( 'admin_init', array( $this, 'maybe_apply_bulk_actions_on_all_contacts' ) ); |
That makes it accessible to even those not logged in to WordPress, if they access the right URL. That is why the request was to /wp-admin/admin-post.php.
With the exploit attempt, that function makes a call to the function get_subscribers() in the file /lite/includes/classes/class-es-contacts-table.php. From there, the function run() in /lite/includes/classes/class-ig-es-subscriber-query.php is run where the SQL injection occurred.
When the exploit attempt passes through that code, it generated this SQL query:
SELECT subscribers.id FROM wp_ig_contacts AS subscribers LEFT JOIN wp_ig_lists_contacts AS lists_subscribers ON subscribers.id = lists_subscribers.contact_id WHERE 1=1 AND ( ( subscribers.status=99924)))union(select(sleep(4)))— = '1111' ) )
The vulnerability was partially resolved by adding a nonce check (which prevents cross-site request forgery (CSRF) to the function maybe_apply_bulk_actions_on_all_contacts():
1456 | check_admin_referer( 'bulk-' . $contacts_table->_args['plural'] ); |
That restricts the attacker’s access to the rest of the code unless they don’t have a valid nonce.
The SQL injection issue was more directly addressed by limiting the value SQL injection payload is added to by restricting the value to a numeric one. That occurred in the run() function, where the following line was changed:
226 | $sub_cond[] = "lists_subscribers.contact_id IN ( SELECT contact_id FROM {$wpbd->prefix}ig_lists_contacts WHERE list_id IN (" . implode( ',', array_filter( $value, 'is_numeric' ) ) . ") AND status IN( 'subscribed', 'confirmed' ) )"; |
It was changed to this:
226 | $sub_cond[] = "lists_subscribers.contact_id IN ( SELECT contact_id FROM {$wpbd->prefix}ig_lists_contacts WHERE list_id IN (" . implode( ',', array_filter( $value, 'is_numeric' ) ) . ") AND status IN( 'subscribed', 'confirmed' ) ) AND lists_subscribers.list_id IN (" . implode( ',', array_filter( $value, 'is_numeric' ) ) . ')'; |
What is still missing is a capabilities check to make sure only the intended users have access.
In the same file, we found another function registered to be accessed by admin_init, es_save_onboarding_skip(), which does a contain a capabilities check but doesn’t have nonce check still:
769 770 771 772 773 774 775 776 777 778 779 780 | public function es_save_onboarding_skip() { $es_skip = ig_es_get_request_data( 'es_skip' ); $option_name = ig_es_get_request_data( 'option_name' ); if ( '1' == $es_skip && ! empty( $option_name ) ) { /** * If user logged in then only save option. */ $can_access_settings = ES_Common::ig_es_can_access( 'settings' ); if ( $can_access_settings ) { update_option( 'ig_es_ob_skip_' . $option_name, 'yes' ); |
Free Warning
As the SQL injection vulnerability looks to be targeted by hackers, we are adding accurate data on it to the free data that comes with our Plugin Vulnerabilities plugin.