Last active
November 21, 2025 23:18
-
-
Save clip1492/4a4f8f757b8fcb3c0e26326108cb44e6 to your computer and use it in GitHub Desktop.
Unofficial temporary Security patch for LFI vulnerability in Favorites 2.3.6
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * Plugin Name: Favorites Hardening (MU) | |
| * Description: Security hardening for the Favorites plugin — clamps the ?tab parameter in admin settings and validates public AJAX calls. | |
| * Author: clip1492 | |
| * Version: 1.1 | |
| */ | |
| if ( ! defined('ABSPATH') ) { exit; } | |
| /** | |
| * -------------------------------------------------------------------------- | |
| * 1) ADMIN AREA: Clamp the ?tab parameter in Settings > Favorites | |
| * -------------------------------------------------------------------------- | |
| * | |
| * Path: /wp-admin/options-general.php?page=simple-favorites&tab=... | |
| * | |
| * Prevents Local File Inclusion (LFI) attempts such as: | |
| * ?tab=../../wp-config | |
| */ | |
| add_action('admin_init', function () { | |
| if ( ! is_admin() ) return; | |
| // Known slugs for the Favorites settings page | |
| $possible_pages = array('simple-favorites','favorites','favorites-settings'); | |
| $page = isset($_GET['page']) ? (string) $_GET['page'] : ''; | |
| if ( ! $page || ! in_array($page, $possible_pages, true) ) return; | |
| // Strict allowlist of valid tabs | |
| $allowed_tabs = array('general','users','display'); | |
| $raw = isset($_GET['tab']) ? (string) $_GET['tab'] : 'general'; | |
| // Normalize and sanitize the slug | |
| $tab = sanitize_key( wp_unslash( $raw ) ); | |
| if ( ! in_array( $tab, $allowed_tabs, true ) ) { | |
| $tab = 'general'; | |
| } | |
| // Force a safe value so the plugin cannot include arbitrary files | |
| $_GET['tab'] = $tab; | |
| $_REQUEST['tab'] = $tab; | |
| // --- Optional logging (disabled by default) --- | |
| /* | |
| $log = WP_CONTENT_DIR . '/favorites-tab-audit.log'; | |
| @file_put_contents($log, sprintf("[%s] %s raw=%s -> %s URI=%s\n", | |
| gmdate('c'), $_SERVER['REMOTE_ADDR'] ?? '-', $raw, $tab, $_SERVER['REQUEST_URI'] ?? '' | |
| ), FILE_APPEND); | |
| */ | |
| }, 1); | |
| /** | |
| * -------------------------------------------------------------------------- | |
| * 2) FRONTEND / PUBLIC: Harden admin-ajax.php calls made by the Favorites plugin | |
| * -------------------------------------------------------------------------- | |
| * | |
| * This version only validates requests if the AJAX "action" belongs | |
| * to the Favorites plugin. It does NOT scan all ajax bodies globally, | |
| * avoiding false positives with other plugins such as YITH Reviews. | |
| * | |
| * It blocks traversal patterns (../, %2e%2e, \, %5c, etc.) | |
| * and sanitizes critical parameters before reaching plugin code. | |
| */ | |
| add_action('init', function () { | |
| $uri = $_SERVER['REQUEST_URI'] ?? ''; | |
| if ( strpos($uri, 'admin-ajax.php') === false ) return; | |
| $action = isset($_REQUEST['action']) ? (string) $_REQUEST['action'] : ''; | |
| // Restrict protection to Favorites-related AJAX actions | |
| $fav_actions = array( | |
| 'favorites_favorite', | |
| 'favorites_list', | |
| 'favorites_clear', | |
| 'favorites_array', | |
| 'favorites_cookie_consent' | |
| ); | |
| // Exit early for unrelated plugins (e.g. YITH Reviews) | |
| if ( ! in_array( $action, $fav_actions, true ) ) return; | |
| // Traversal detection regex (matches ../, %2e%2e, \, %5c) | |
| // NOTE: '/' and '%2f' are NOT blocked to allow legitimate URLs in other params | |
| $traversal_rx = '/(\.\.|%2e%2e|%5c|\\\\)/i'; | |
| // Validate only critical Favorites parameters | |
| foreach ( array('tab','postid','siteid','include_links','include_buttons','include_thumbnails','include_excerpt') as $key ) { | |
| if ( isset($_REQUEST[$key]) && preg_match($traversal_rx, (string)$_REQUEST[$key]) ) { | |
| // --- Optional logging (disabled by default) --- | |
| /* | |
| @file_put_contents( WP_CONTENT_DIR . '/favorites-ajax-audit.log', | |
| sprintf("[%s] %s TRAVERSAL key=%s uri=%s\n", | |
| gmdate('c'), $_SERVER['REMOTE_ADDR'] ?? '-', $key, $uri | |
| ), FILE_APPEND | |
| ); | |
| */ | |
| status_header(403); | |
| wp_die('Forbidden', 'Forbidden', array('response' => 403)); | |
| } | |
| } | |
| // Normalize numeric parameters | |
| foreach (array('postid','siteid') as $int_key) { | |
| if ( isset($_REQUEST[$int_key]) ) { | |
| $_REQUEST[$int_key] = $_POST[$int_key] = max(0, intval($_REQUEST[$int_key])); | |
| } | |
| } | |
| // Reject invalid post IDs in favorites_favorite | |
| if ( $action === 'favorites_favorite' ) { | |
| $postid = isset($_REQUEST['postid']) ? (int) $_REQUEST['postid'] : 0; | |
| if ( $postid <= 0 ) { | |
| // --- Optional logging (disabled by default) --- | |
| /* | |
| @file_put_contents( WP_CONTENT_DIR . '/favorites-ajax-audit.log', | |
| sprintf("[%s] %s INVALID_POSTID action=%s uri=%s\n", | |
| gmdate('c'), $_SERVER['REMOTE_ADDR'] ?? '-', $action, $uri | |
| ), FILE_APPEND | |
| ); | |
| */ | |
| status_header(400); | |
| wp_die('Bad Request', 'Bad Request', array('response' => 400)); | |
| } | |
| } | |
| // Normalize boolean flags in favorites_list | |
| if ( $action === 'favorites_list' ) { | |
| $bool_keys = array('include_links','include_buttons','include_thumbnails','include_excerpt'); | |
| foreach ($bool_keys as $k) { | |
| if ( isset($_REQUEST[$k]) ) { | |
| $_REQUEST[$k] = $_POST[$k] = ((string)$_REQUEST[$k] === 'true') ? 'true' : 'false'; | |
| } | |
| } | |
| } | |
| }, 1); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment