Plugin Directory

Changeset 3321915


Ignore:
Timestamp:
07/03/2025 04:48:42 PM (8 months ago)
Author:
zapier
Message:

Security improvements for CVE-2025-50010

Location:
zapier
Files:
2 edited
1 copied

Legend:

Unmodified
Added
Removed
  • zapier/tags/1.5.3/zapier.php

    r3257975 r3321915  
    44 * Plugin Name:       Zapier for WordPress
    55 * Description:       Zapier enables you to automatically share your posts to social media, create WordPress posts from Mailchimp newsletters, and much more. Visit https://zapier.com/apps/wordpress/integrations for more details.
    6  * Version:           1.5.2
     6 * Version:           1.5.3
    77 * Author:            Zapier
    88 * Author URI:        https://zapier.com
     
    9292    }
    9393
     94    /**
     95     * Verify user has proper authorization for webhook management
     96     *
     97     * @param WP_REST_Request $request
     98     * @return bool|WP_Error
     99     */
     100    private function verify_webhook_authorization($request)
     101    {
     102        // Check if user is logged in
     103        if (!is_user_logged_in()) {
     104            return new WP_Error(
     105                'not_logged_in',
     106                'You are not logged in',
     107                array('status' => 401)
     108            );
     109        }
     110
     111        // Check if user has proper capabilities - only administrators should manage webhooks
     112        if (!current_user_can('manage_options')) {
     113            return new WP_Error(
     114                'insufficient_permissions',
     115                'You do not have sufficient permissions to manage webhooks',
     116                array('status' => 403)
     117            );
     118        }
     119
     120                // CSRF protection: Different approaches for different auth methods
     121        $is_jwt_auth = isset($_SERVER['HTTP_X_ZAPIER_AUTH']) && isset($_SERVER['HTTP_USER_AGENT']) && $_SERVER['HTTP_USER_AGENT'] === 'Zapier';
     122
     123        if ($is_jwt_auth) {
     124            // For JWT: Verify the token includes proper origin validation
     125            // JWT tokens should only be used by Zapier's servers, not browsers
     126            // The User-Agent check provides additional CSRF protection
     127            if (!isset($_SERVER['HTTP_USER_AGENT']) || $_SERVER['HTTP_USER_AGENT'] !== 'Zapier') {
     128                return new WP_Error(
     129                    'invalid_user_agent',
     130                    'Invalid request source',
     131                    array('status' => 403)
     132                );
     133            }
     134        } else {
     135            // For browser-based requests: Use WordPress nonce
     136            $nonce = $request->get_header('X-WP-Nonce');
     137            if (!$nonce || !wp_verify_nonce($nonce, 'wp_rest')) {
     138                return new WP_Error(
     139                    'invalid_nonce',
     140                    'Invalid security token',
     141                    array('status' => 403)
     142                );
     143            }
     144        }
     145
     146        return true;
     147    }
     148
     149    /**
     150     * Verify user authorization for read-only operations
     151     *
     152     * @param WP_REST_Request $request
     153     * @return bool|WP_Error
     154     */
     155    private function verify_read_authorization($request)
     156    {
     157        // Check if user is logged in
     158        if (!is_user_logged_in()) {
     159            return new WP_Error(
     160                'not_logged_in',
     161                'You are not logged in',
     162                array('status' => 401)
     163            );
     164        }
     165
     166        // For read operations, we can allow users with edit_posts capability
     167        if (!current_user_can('edit_posts')) {
     168            return new WP_Error(
     169                'insufficient_permissions',
     170                'You do not have sufficient permissions to access this resource',
     171                array('status' => 403)
     172            );
     173        }
     174
     175        return true;
     176    }
     177
     178
     179
    94180    public function add_api_routes()
    95181    {
     
    103189            'methods' => "GET",
    104190            'callback' => array($this, 'get_custom_type_supports'),
    105             'permission_callback' => '__return_true'
     191            'permission_callback' => array($this, 'check_read_permission')
    106192        ));
    107193
     
    109195            'methods' => "GET",
    110196            'callback' => array($this, 'get_roles'),
    111             'permission_callback' => '__return_true'
     197            'permission_callback' => array($this, 'check_read_permission')
    112198        ));
    113199
     
    115201            'methods' => "POST",
    116202            'callback' => array($this, 'add_webhook'),
    117             'permission_callback' => '__return_true'
     203            'permission_callback' => array($this, 'check_webhook_permission')
    118204        ));
    119205
     
    121207            'methods' => "DELETE",
    122208            'callback' => array($this, 'remove_webhook'),
    123             'permission_callback' => '__return_true'
     209            'permission_callback' => array($this, 'check_webhook_permission')
    124210        ));
     211    }
     212
     213    /**
     214     * Permission callback for webhook operations
     215     */
     216    public function check_webhook_permission($request)
     217    {
     218        $auth_result = $this->verify_webhook_authorization($request);
     219        return !is_wp_error($auth_result);
     220    }
     221
     222        /**
     223     * Permission callback for read operations
     224     */
     225    public function check_read_permission($request)
     226    {
     227        // Check if user is logged in (Application Password authentication should work here)
     228        if (!is_user_logged_in()) {
     229            return false;
     230        }
     231
     232        // For read operations, allow users with edit_posts capability (editors and admins)
     233        if (!current_user_can('edit_posts')) {
     234            return false;
     235        }
     236
     237        return true;
    125238    }
    126239
     
    161274    public function get_custom_type_supports($request)
    162275    {
    163 
    164         if(!is_user_logged_in()) {
    165             return new WP_Error(
    166                 'not_logged_in',
    167                 'You are not logged in',
    168                 array(
    169                     'status' => 401,
    170                 )
    171             );
    172         }
    173 
     276        // Authorization is handled by permission_callback
    174277        $type = $request['type'];
    175278        $types = get_post_types(array());
     
    184287            );
    185288        }
    186        
     289
    187290        return array('supports' => get_all_post_type_supports($type));
    188291    }
    189292
    190     public function get_roles()
    191     {
    192         if(!is_user_logged_in()) {
    193             return new WP_Error(
    194                 'not_logged_in',
    195                 'You are not logged in',
    196                 array(
    197                     'status' => 401,
    198                 )
    199             );
    200         }
    201        
     293    public function get_roles($request = null)
     294    {
     295        // Authorization is handled by permission_callback
    202296        $roles = array();
    203297        foreach (wp_roles()->roles as $key => $role) {
     
    209303
    210304    public function add_webhook($request) {
    211 
    212         if(!is_user_logged_in()) {
    213             return new WP_Error(
    214                 'not_logged_in',
    215                 'You are not logged in',
    216                 array(
    217                     'status' => 401,
    218                 )
    219             );
     305        // Authorization is handled by permission_callback
     306        $auth_result = $this->verify_webhook_authorization($request);
     307        if (is_wp_error($auth_result)) {
     308            return $auth_result;
    220309        }
    221310
     
    245334        }
    246335
     336        // Enhanced URL validation
     337        if (!$this->is_safe_url($endpoint_url)) {
     338            return new WP_Error(
     339                'unsafe_endpoint_url',
     340                'The provided endpoint URL is not allowed for security reasons',
     341                array(
     342                    'status' => 400,
     343                )
     344            );
     345        }
     346
    247347        $option_key = "zapier_hooks_$action";
    248348
     
    258358
    259359    public function remove_webhook($request) {
    260 
    261         if(!is_user_logged_in()) {
    262             return new WP_Error(
    263                 'not_logged_in',
    264                 'You are not logged in',
    265                 array(
    266                     'status' => 401,
    267                 )
    268             );
     360        // Authorization is handled by permission_callback
     361        $auth_result = $this->verify_webhook_authorization($request);
     362        if (is_wp_error($auth_result)) {
     363            return $auth_result;
    269364        }
    270365
     
    286381        $option_key = "zapier_hooks_wp_update_user";
    287382        $hooks = get_option($option_key, []);
    288    
     383
    289384        foreach ($hooks as $hook) {
    290385            // Validate the URL
     
    292387                continue; // Skip unsafe URLs
    293388            }
    294    
     389
    295390            // Use wp_safe_remote_post to ensure secure requests
    296391            $response = wp_safe_remote_post($hook, [
     
    303398    public function updated_post($post_id, $post_after, $post_before) {
    304399        $option_key = "zapier_hooks_post_updated";
    305        
     400
    306401        $rest_base = get_post_type_object($post_after->post_type)->rest_base;
    307402        $changed_properties = $this->compareObjects($post_after, $post_before);
    308        
     403
    309404        $hooks = get_option($option_key, []);
    310405
  • zapier/trunk/zapier.php

    r3257975 r3321915  
    44 * Plugin Name:       Zapier for WordPress
    55 * Description:       Zapier enables you to automatically share your posts to social media, create WordPress posts from Mailchimp newsletters, and much more. Visit https://zapier.com/apps/wordpress/integrations for more details.
    6  * Version:           1.5.2
     6 * Version:           1.5.3
    77 * Author:            Zapier
    88 * Author URI:        https://zapier.com
     
    9292    }
    9393
     94    /**
     95     * Verify user has proper authorization for webhook management
     96     *
     97     * @param WP_REST_Request $request
     98     * @return bool|WP_Error
     99     */
     100    private function verify_webhook_authorization($request)
     101    {
     102        // Check if user is logged in
     103        if (!is_user_logged_in()) {
     104            return new WP_Error(
     105                'not_logged_in',
     106                'You are not logged in',
     107                array('status' => 401)
     108            );
     109        }
     110
     111        // Check if user has proper capabilities - only administrators should manage webhooks
     112        if (!current_user_can('manage_options')) {
     113            return new WP_Error(
     114                'insufficient_permissions',
     115                'You do not have sufficient permissions to manage webhooks',
     116                array('status' => 403)
     117            );
     118        }
     119
     120                // CSRF protection: Different approaches for different auth methods
     121        $is_jwt_auth = isset($_SERVER['HTTP_X_ZAPIER_AUTH']) && isset($_SERVER['HTTP_USER_AGENT']) && $_SERVER['HTTP_USER_AGENT'] === 'Zapier';
     122
     123        if ($is_jwt_auth) {
     124            // For JWT: Verify the token includes proper origin validation
     125            // JWT tokens should only be used by Zapier's servers, not browsers
     126            // The User-Agent check provides additional CSRF protection
     127            if (!isset($_SERVER['HTTP_USER_AGENT']) || $_SERVER['HTTP_USER_AGENT'] !== 'Zapier') {
     128                return new WP_Error(
     129                    'invalid_user_agent',
     130                    'Invalid request source',
     131                    array('status' => 403)
     132                );
     133            }
     134        } else {
     135            // For browser-based requests: Use WordPress nonce
     136            $nonce = $request->get_header('X-WP-Nonce');
     137            if (!$nonce || !wp_verify_nonce($nonce, 'wp_rest')) {
     138                return new WP_Error(
     139                    'invalid_nonce',
     140                    'Invalid security token',
     141                    array('status' => 403)
     142                );
     143            }
     144        }
     145
     146        return true;
     147    }
     148
     149    /**
     150     * Verify user authorization for read-only operations
     151     *
     152     * @param WP_REST_Request $request
     153     * @return bool|WP_Error
     154     */
     155    private function verify_read_authorization($request)
     156    {
     157        // Check if user is logged in
     158        if (!is_user_logged_in()) {
     159            return new WP_Error(
     160                'not_logged_in',
     161                'You are not logged in',
     162                array('status' => 401)
     163            );
     164        }
     165
     166        // For read operations, we can allow users with edit_posts capability
     167        if (!current_user_can('edit_posts')) {
     168            return new WP_Error(
     169                'insufficient_permissions',
     170                'You do not have sufficient permissions to access this resource',
     171                array('status' => 403)
     172            );
     173        }
     174
     175        return true;
     176    }
     177
     178
     179
    94180    public function add_api_routes()
    95181    {
     
    103189            'methods' => "GET",
    104190            'callback' => array($this, 'get_custom_type_supports'),
    105             'permission_callback' => '__return_true'
     191            'permission_callback' => array($this, 'check_read_permission')
    106192        ));
    107193
     
    109195            'methods' => "GET",
    110196            'callback' => array($this, 'get_roles'),
    111             'permission_callback' => '__return_true'
     197            'permission_callback' => array($this, 'check_read_permission')
    112198        ));
    113199
     
    115201            'methods' => "POST",
    116202            'callback' => array($this, 'add_webhook'),
    117             'permission_callback' => '__return_true'
     203            'permission_callback' => array($this, 'check_webhook_permission')
    118204        ));
    119205
     
    121207            'methods' => "DELETE",
    122208            'callback' => array($this, 'remove_webhook'),
    123             'permission_callback' => '__return_true'
     209            'permission_callback' => array($this, 'check_webhook_permission')
    124210        ));
     211    }
     212
     213    /**
     214     * Permission callback for webhook operations
     215     */
     216    public function check_webhook_permission($request)
     217    {
     218        $auth_result = $this->verify_webhook_authorization($request);
     219        return !is_wp_error($auth_result);
     220    }
     221
     222        /**
     223     * Permission callback for read operations
     224     */
     225    public function check_read_permission($request)
     226    {
     227        // Check if user is logged in (Application Password authentication should work here)
     228        if (!is_user_logged_in()) {
     229            return false;
     230        }
     231
     232        // For read operations, allow users with edit_posts capability (editors and admins)
     233        if (!current_user_can('edit_posts')) {
     234            return false;
     235        }
     236
     237        return true;
    125238    }
    126239
     
    161274    public function get_custom_type_supports($request)
    162275    {
    163 
    164         if(!is_user_logged_in()) {
    165             return new WP_Error(
    166                 'not_logged_in',
    167                 'You are not logged in',
    168                 array(
    169                     'status' => 401,
    170                 )
    171             );
    172         }
    173 
     276        // Authorization is handled by permission_callback
    174277        $type = $request['type'];
    175278        $types = get_post_types(array());
     
    184287            );
    185288        }
    186        
     289
    187290        return array('supports' => get_all_post_type_supports($type));
    188291    }
    189292
    190     public function get_roles()
    191     {
    192         if(!is_user_logged_in()) {
    193             return new WP_Error(
    194                 'not_logged_in',
    195                 'You are not logged in',
    196                 array(
    197                     'status' => 401,
    198                 )
    199             );
    200         }
    201        
     293    public function get_roles($request = null)
     294    {
     295        // Authorization is handled by permission_callback
    202296        $roles = array();
    203297        foreach (wp_roles()->roles as $key => $role) {
     
    209303
    210304    public function add_webhook($request) {
    211 
    212         if(!is_user_logged_in()) {
    213             return new WP_Error(
    214                 'not_logged_in',
    215                 'You are not logged in',
    216                 array(
    217                     'status' => 401,
    218                 )
    219             );
     305        // Authorization is handled by permission_callback
     306        $auth_result = $this->verify_webhook_authorization($request);
     307        if (is_wp_error($auth_result)) {
     308            return $auth_result;
    220309        }
    221310
     
    245334        }
    246335
     336        // Enhanced URL validation
     337        if (!$this->is_safe_url($endpoint_url)) {
     338            return new WP_Error(
     339                'unsafe_endpoint_url',
     340                'The provided endpoint URL is not allowed for security reasons',
     341                array(
     342                    'status' => 400,
     343                )
     344            );
     345        }
     346
    247347        $option_key = "zapier_hooks_$action";
    248348
     
    258358
    259359    public function remove_webhook($request) {
    260 
    261         if(!is_user_logged_in()) {
    262             return new WP_Error(
    263                 'not_logged_in',
    264                 'You are not logged in',
    265                 array(
    266                     'status' => 401,
    267                 )
    268             );
     360        // Authorization is handled by permission_callback
     361        $auth_result = $this->verify_webhook_authorization($request);
     362        if (is_wp_error($auth_result)) {
     363            return $auth_result;
    269364        }
    270365
     
    286381        $option_key = "zapier_hooks_wp_update_user";
    287382        $hooks = get_option($option_key, []);
    288    
     383
    289384        foreach ($hooks as $hook) {
    290385            // Validate the URL
     
    292387                continue; // Skip unsafe URLs
    293388            }
    294    
     389
    295390            // Use wp_safe_remote_post to ensure secure requests
    296391            $response = wp_safe_remote_post($hook, [
     
    303398    public function updated_post($post_id, $post_after, $post_before) {
    304399        $option_key = "zapier_hooks_post_updated";
    305        
     400
    306401        $rest_base = get_post_type_object($post_after->post_type)->rest_base;
    307402        $changed_properties = $this->compareObjects($post_after, $post_before);
    308        
     403
    309404        $hooks = get_option($option_key, []);
    310405
Note: See TracChangeset for help on using the changeset viewer.