Skip to content

Instantly share code, notes, and snippets.

@clip1492
Last active November 21, 2025 23:18
Show Gist options
  • Select an option

  • Save clip1492/4a4f8f757b8fcb3c0e26326108cb44e6 to your computer and use it in GitHub Desktop.

Select an option

Save clip1492/4a4f8f757b8fcb3c0e26326108cb44e6 to your computer and use it in GitHub Desktop.
Unofficial temporary Security patch for LFI vulnerability in Favorites 2.3.6
<?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