• The issue lies within the file widget/logic.php. Replace the contents of that entire file with the code from the second post. The following code is the culprit behind the security issue/alert:

    set_error_handler('widget_logic_error_handler'); // phpcs:ignore -- we have mode for debugging for admins

    try {
    $show_widget = eval($logic); // @codingStandardsIgnoreLine - widget can't work without eval
    } catch (Error $e) {
    trigger_error($e->getMessage(), E_USER_WARNING);
    $show_widget = false;
    }

    restore_error_handler();

    return $show_widget;

    In paricular, the eval($logic) line is what’s causing the security concern. The fix I’ve posted below is in use on 13 sites for my day job, as well as one of my sites, and is working beautifully, without any errors.

    The fix (below) changes the eval() function to only allow specific WordPress () functions. In addition to that, you can whitelist additional allowable functions (post after the one below).

    As always, with any end-user provided coding changes, please exercise due diligence and test in a controlled environment. While I will do everything I can to ensure the safety and security, as well as the integrity of any code I provide is safe, viable and useable, not everyone has the same approach.

    Thanks!

Viewing 7 replies - 1 through 7 (of 7 total)
  • Thread Starter Douglas “BearlyDoug” Hazard

    (@bearlydoug)

    REPLACE the entire contents of widget/logic.php with the following:

    <?php
    if (!defined('ABSPATH')) exit; // Exit if accessed directly

    /**
    * Evaluate widget logic safely.
    * Supports basic && and || operations with a whitelist of WP conditional functions,
    * and allows extension via filter hook for custom functions.
    *
    * @param string|bool $logic The widget logic string or boolean.
    * @return bool True if widget should display, false otherwise.
    */
    function widget_logic_check_logic($logic)
    {
    $logic = trim((string) $logic);

    // Allow external override (must return true/false)
    $logic = apply_filters('widget_logic_eval_override', $logic);

    if (is_bool($logic)) {
    return $logic;
    }

    if ($logic === '') {
    return true;
    }

    // Split by OR (||) operators
    $or_parts = preg_split('/\s*\|\|\s*/', $logic);
    foreach ($or_parts as $or_part) {
    // Split by AND (&&) operators
    $and_parts = preg_split('/\s*&&\s*/', $or_part);
    $and_result = true;
    foreach ($and_parts as $and_part) {
    $and_part = trim($and_part);
    if (!evaluate_condition($and_part)) {
    $and_result = false;
    break;
    }
    }
    if ($and_result) {
    return true;
    }
    }

    return false;
    }

    /**
    * Evaluate a single condition string.
    *
    * @param string $condition Single condition like is_home() or current_user_can('edit_posts').
    * @return bool
    */
    function evaluate_condition($condition)
    {
    // Match function name and optional args: functionName() or functionName('arg')
    if (!preg_match('/^([a-zA-Z_][a-zA-Z0-9_]*)\s*(\(.*\))?$/', $condition, $matches)) {
    return false;
    }

    $func = $matches[1];
    $args = isset($matches[2]) ? $matches[2] : '';

    // Whitelist allowed functions — filterable so themes/plugins can add more
    $allowed_funcs = array(
    'is_home',
    'is_front_page',
    'is_single',
    'is_page',
    'is_category',
    'is_tag',
    'is_archive',
    'is_author',
    'is_search',
    'is_404',
    'is_user_logged_in',
    'current_user_can',
    'is_active_sidebar',
    );

    $allowed_funcs = apply_filters('widget_logic_allowed_functions', $allowed_funcs);

    if (!in_array($func, $allowed_funcs, true)) {
    return false;
    }

    if ($args === '') {
    return function_exists($func) ? $func() : false;
    }

    // Process args string to array
    $args = trim($args, '() ');
    if ($args === '') {
    return function_exists($func) ? $func() : false;
    }

    // Split arguments respecting commas
    $argList = preg_split('/\s*,\s*/', $args);

    // Trim quotes and whitespace from each arg
    $argList = array_map(function ($arg) {
    return trim($arg, "'\" ");
    }, $argList);

    if (!function_exists($func)) {
    return false;
    }

    return call_user_func_array($func, $argList);
    }

    /**
    * Custom error handler for widget logic errors.
    *
    * @param int $errno
    * @param string $errstr
    * @return bool
    */
    function widget_logic_error_handler($errno, $errstr)
    {
    global $wl_options;

    $show_errors = !empty($wl_options['widget_logic-options-show_errors']) && current_user_can('manage_options');

    if ($show_errors) {
    echo 'Invalid Widget Logic: ' . esc_html($errstr);
    }

    return true;
    }

    /**
    * Retrieve widget logic string for a widget ID.
    *
    * @param string $widget_id
    * @return string
    */
    function widget_logic_by_id($widget_id)
    {
    global $wl_options;

    if (preg_match('/^(.+)-(\d+)$/', $widget_id, $m)) {
    $widget_class = $m[1];
    $widget_i = $m[2];

    $info = get_option('widget_' . $widget_class);
    if (empty($info[$widget_i])) {
    return '';
    }

    $info = $info[$widget_i];
    } else {
    $info = (array) get_option('widget_' . $widget_id, array());
    }

    if (isset($info['widget_logic'])) {
    $logic = $info['widget_logic'];
    } elseif (isset($wl_options[$widget_id])) {
    $logic = stripslashes($wl_options[$widget_id]);
    widget_logic_save($widget_id, $logic);

    unset($wl_options[$widget_id]);
    update_option('widget_logic', $wl_options);
    } else {
    $logic = '';
    }

    return $logic;
    }

    /**
    * Save widget logic string for a widget ID.
    *
    * @param string $widget_id
    * @param string $logic
    * @return void
    */
    function widget_logic_save($widget_id, $logic)
    {
    global $wl_options;

    if (preg_match('/^(.+)-(\d+)$/', $widget_id, $m)) {
    $widget_class = $m[1];
    $widget_i = $m[2];

    $info = get_option('widget_' . $widget_class);
    if (!is_array($info[$widget_i])) {
    $info[$widget_i] = array();
    }

    $info[$widget_i]['widget_logic'] = $logic;
    update_option('widget_' . $widget_class, $info);
    } elseif (
    isset($_POST['widget_logic_nonce'])
    && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['widget_logic_nonce'])), 'widget_logic_save')
    ) {
    $info = (array) get_option('widget_' . $widget_id, array());
    $info['widget_logic'] = $logic;
    update_option('widget_' . $widget_id, $info);
    }
    }
    Thread Starter Douglas “BearlyDoug” Hazard

    (@bearlydoug)

    If you need to whitelist a custom function you’ve created (I’ve created “is_tree()”, which checks if a page is a child page of a parent page, by checking against the parent page’s ID (similar to is_page()), then the following function in your theme’s functions.php file (or a custom plugin) can be used. Change “is_tree” with your function’s name.

    As always, EXERCISE CAUTION when using this code!

    add_filter('widget_logic_allowed_functions', function($allowed_funcs) {
    $allowed_funcs[] = 'is_tree';
    return $allowed_funcs;
    });
    Thread Starter Douglas “BearlyDoug” Hazard

    (@bearlydoug)

    I need to note that my fix above does not address the (VERY VALID) external JavaScript/remote image concern others have noted, though I have scrubbed that “feature” from the version of the plugin I’m using.

    The JS issue is absolutely an issue that must be addressed. I am hopeful that the WordPress plugin team will get that addressed for us, once and for all.

    Hi

    After adding the code still got the error on the cpanel

    Widget Logic <= 6.0.5 – Remote Code Execution Vulnerability Fix

    Thanks

    @wizardlopes, keep in mind my code does NOT address the javascript issue… only the actual PHP vulnerability.

    I’m not sure if I’m allowed to provide a link to a zip file containing the scrubbed javascript code or not. Last thing I want to do is step on any toes, and as a fellow WordPress plugin developer, break any rules. I’ve reported my own post to ask for clarification on this issue.

    That being said, I’m considering releasing my own version of Widget Logic, but need to give the WL folks time to get their issue addressed.

    Moderator Yui

    (@fierevere)

    永子

    The proper way should be if plugin author will accept the fix and release updated version.
    Links to forks/alternative downloads are not acceptable. Even if they are benevolent.
    The only one exception is when plugin authors offer beta version of their plugin to test if they have fixed some issue.

    If plugin authors wont cp;;anorate on a fix – you can report to plugins team
    https://developer.ww.wp.xz.cn/plugins/wordpress-org/plugin-security/reporting-plugin-security-issues/
    Maybe it will be possible to fork or adopt this plugin if its abandoned. In any case its plugins team area to make decisions.

    Thank you, @fierevere! I appreciate you weighing in on this. 🙂

Viewing 7 replies - 1 through 7 (of 7 total)

The topic ‘Widget Logic <= 6.0.5 – Remote Code Execution Vulnerability Fix’ is closed to new replies.