385 lines
13 KiB
PHP
385 lines
13 KiB
PHP
<?php
|
|
/**
|
|
* Critical Security Fixes and Plugin Compatibility
|
|
* Implementation of audit recommendations
|
|
*/
|
|
|
|
// Prevent direct access
|
|
if (!defined('WPINC')) {
|
|
die('Direct access not permitted.');
|
|
}
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
class MCB_Security_Fixes {
|
|
|
|
/**
|
|
* Initialize security fixes and compatibility patches
|
|
*/
|
|
public static function init() {
|
|
// Fix 1: SSRF DNS Rebinding Protection
|
|
add_filter('mcb_validate_api_url', array(__CLASS__, 'validate_api_url_dns'), 10, 2);
|
|
|
|
// Fix 2: Wordfence Compatibility
|
|
add_action('init', array(__CLASS__, 'wordfence_compatibility'));
|
|
|
|
// Fix 3: Error Suppression in Production
|
|
add_action('init', array(__CLASS__, 'suppress_errors_production'));
|
|
|
|
// Fix 4: Token Autoloading Fix
|
|
add_filter('mcb_update_option', array(__CLASS__, 'disable_autoload'), 10, 3);
|
|
|
|
// Fix 5: User Agent Hashing
|
|
add_filter('mcb_log_user_agent', array(__CLASS__, 'hash_user_agent'));
|
|
|
|
// Fix 6: Plugin Conflict Detection
|
|
add_action('admin_notices', array(__CLASS__, 'compatibility_notices'));
|
|
|
|
// Fix 7: Performance Optimization
|
|
add_action('init', array(__CLASS__, 'optimize_loading'));
|
|
}
|
|
|
|
/**
|
|
* CRITICAL FIX: SSRF DNS Rebinding Protection
|
|
* Prevents accessing internal network resources
|
|
*/
|
|
public static function validate_api_url_dns($valid, $url) {
|
|
$parsed = parse_url($url);
|
|
if (!$parsed || !isset($parsed['host'])) {
|
|
return false;
|
|
}
|
|
|
|
$host = $parsed['host'];
|
|
|
|
// Whitelist of allowed API hosts
|
|
$allowed_hosts = array(
|
|
'api.github.com',
|
|
'gitlab.com',
|
|
'api.bitbucket.org',
|
|
'codeberg.org'
|
|
);
|
|
|
|
if (!in_array($host, $allowed_hosts, true)) {
|
|
return false;
|
|
}
|
|
|
|
// Get IP address of the host
|
|
$ip = gethostbyname($host);
|
|
|
|
// If resolution failed, block
|
|
if ($ip === $host) {
|
|
error_log('GCV Security: DNS resolution failed for ' . $host);
|
|
return false;
|
|
}
|
|
|
|
// Check for private and reserved IP ranges
|
|
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
|
|
error_log('GCV Security: Blocked private/reserved IP ' . $ip . ' for host ' . $host);
|
|
return false;
|
|
}
|
|
|
|
// Additional check for IPv6
|
|
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
|
// Check for IPv6 private ranges
|
|
$private_ranges = array(
|
|
'fc00::/7', // Unique local addresses
|
|
'fe80::/10', // Link-local addresses
|
|
'::1/128', // Loopback
|
|
'::/128', // Unspecified
|
|
'::ffff:0:0/96' // IPv4-mapped addresses
|
|
);
|
|
|
|
foreach ($private_ranges as $range) {
|
|
if (self::ip_in_range($ip, $range)) {
|
|
error_log('GCV Security: Blocked private IPv6 ' . $ip);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Wordfence Compatibility Layer
|
|
*/
|
|
public static function wordfence_compatibility() {
|
|
if (!defined('WORDFENCE_VERSION')) {
|
|
return;
|
|
}
|
|
|
|
// Add API URLs to Wordfence whitelist
|
|
if (class_exists('wfConfig')) {
|
|
$whitelisted = wfConfig::get('whitelisted');
|
|
$whitelist_urls = array(
|
|
'api.github.com',
|
|
'gitlab.com',
|
|
'api.bitbucket.org',
|
|
'codeberg.org'
|
|
);
|
|
|
|
foreach ($whitelist_urls as $url) {
|
|
if (strpos($whitelisted, $url) === false) {
|
|
$whitelisted .= "\n" . $url;
|
|
}
|
|
}
|
|
|
|
wfConfig::set('whitelisted', $whitelisted);
|
|
}
|
|
|
|
// Disable plugin rate limiting if Wordfence is active
|
|
if (has_filter('mcb_enable_rate_limiting')) {
|
|
add_filter('mcb_enable_rate_limiting', '__return_false');
|
|
}
|
|
|
|
// Add Wordfence compatibility headers
|
|
add_action('mcb_before_api_request', function() {
|
|
if (function_exists('wfUtils::doNotCache')) {
|
|
wfUtils::doNotCache();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Suppress errors in production environments
|
|
*/
|
|
public static function suppress_errors_production() {
|
|
// Only in production (non-debug mode)
|
|
if (defined('WP_DEBUG') && WP_DEBUG) {
|
|
return;
|
|
}
|
|
|
|
// Suppress PHP errors in AJAX handlers
|
|
if (wp_doing_ajax()) {
|
|
@error_reporting(0);
|
|
@ini_set('display_errors', '0');
|
|
@ini_set('display_startup_errors', '0');
|
|
|
|
// Set custom error handler
|
|
set_error_handler(function($errno, $errstr, $errfile, $errline) {
|
|
// Log to error log but don't display
|
|
if (WP_DEBUG_LOG) {
|
|
error_log(sprintf(
|
|
'GCV Error: %s in %s on line %d',
|
|
$errstr,
|
|
$errfile,
|
|
$errline
|
|
));
|
|
}
|
|
return true; // Prevent default error handler
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fix token autoloading issue
|
|
*/
|
|
public static function disable_autoload($value, $option, $autoload) {
|
|
// Disable autoload for token options
|
|
if (strpos($option, 'mcb_') === 0 && strpos($option, '_token') !== false) {
|
|
return 'no';
|
|
}
|
|
return $autoload;
|
|
}
|
|
|
|
/**
|
|
* Hash user agents for privacy
|
|
*/
|
|
public static function hash_user_agent($user_agent) {
|
|
if (empty($user_agent)) {
|
|
return 'unknown';
|
|
}
|
|
|
|
// Use a consistent salt for hashing
|
|
$salt = wp_salt('auth');
|
|
|
|
// Create a hash that's consistent but not reversible
|
|
return substr(hash('sha256', $salt . $user_agent), 0, 16);
|
|
}
|
|
|
|
/**
|
|
* Show compatibility notices
|
|
*/
|
|
public static function compatibility_notices() {
|
|
$notices = array();
|
|
|
|
// Wordfence compatibility notice
|
|
if (defined('WORDFENCE_VERSION')) {
|
|
$notices[] = array(
|
|
'type' => 'warning',
|
|
'message' => __('Git Code Viewer: Wordfence detected. Please ensure the following URLs are whitelisted in Wordfence settings: api.github.com, gitlab.com, api.bitbucket.org, codeberg.org', 'maple-code-blocks'),
|
|
'dismissible' => true
|
|
);
|
|
}
|
|
|
|
// WooCommerce compatibility (informational)
|
|
if (class_exists('WooCommerce')) {
|
|
// No issues, just log for debugging
|
|
error_log('GCV: WooCommerce detected - compatibility mode active');
|
|
}
|
|
|
|
// LearnDash compatibility (informational)
|
|
if (defined('LEARNDASH_VERSION')) {
|
|
// No issues, just log for debugging
|
|
error_log('GCV: LearnDash detected - compatibility mode active');
|
|
}
|
|
|
|
// Memory limit warning
|
|
$memory_limit = ini_get('memory_limit');
|
|
if ($memory_limit && self::convert_to_bytes($memory_limit) < 67108864) { // 64MB
|
|
$notices[] = array(
|
|
'type' => 'warning',
|
|
'message' => __('Git Code Viewer: Low memory limit detected. Consider increasing memory_limit to at least 64MB for optimal performance.', 'maple-code-blocks'),
|
|
'dismissible' => true
|
|
);
|
|
}
|
|
|
|
// Display notices
|
|
foreach ($notices as $notice) {
|
|
$dismissible = $notice['dismissible'] ? 'is-dismissible' : '';
|
|
printf(
|
|
'<div class="notice notice-%s %s"><p>%s</p></div>',
|
|
esc_attr($notice['type']),
|
|
esc_attr($dismissible),
|
|
esc_html($notice['message'])
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Optimize plugin loading
|
|
*/
|
|
public static function optimize_loading() {
|
|
// Hook into admin_init for screen-specific optimizations
|
|
if (is_admin()) {
|
|
add_action('admin_init', function() {
|
|
// Check screen only after it's available
|
|
add_action('current_screen', function($screen) {
|
|
if ($screen && $screen->id !== 'settings_page_maple-code-blocks') {
|
|
remove_action('admin_enqueue_scripts', array('Maple_Code_Blocks', 'enqueue_scripts'));
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Optimize transient cleanup
|
|
if (!wp_next_scheduled('mcb_cleanup_transients')) {
|
|
wp_schedule_event(time(), 'daily', 'mcb_cleanup_transients');
|
|
}
|
|
|
|
add_action('mcb_cleanup_transients', function() {
|
|
global $wpdb;
|
|
|
|
// Use WordPress function instead of direct query
|
|
if (function_exists('delete_expired_transients')) {
|
|
delete_expired_transients();
|
|
} else {
|
|
// Fallback for older WordPress versions
|
|
$wpdb->query(
|
|
"DELETE FROM {$wpdb->options}
|
|
WHERE option_name LIKE '_transient_timeout_mcb_%'
|
|
AND option_value < UNIX_TIMESTAMP()"
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Helper: Check if IP is in range
|
|
*/
|
|
private static function ip_in_range($ip, $range) {
|
|
if (strpos($range, '/') === false) {
|
|
$range .= '/32';
|
|
}
|
|
|
|
list($subnet, $bits) = explode('/', $range);
|
|
|
|
// IPv4
|
|
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) &&
|
|
filter_var($subnet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
|
$ip = ip2long($ip);
|
|
$subnet = ip2long($subnet);
|
|
$mask = -1 << (32 - $bits);
|
|
$subnet &= $mask;
|
|
return ($ip & $mask) == $subnet;
|
|
}
|
|
|
|
// IPv6
|
|
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) &&
|
|
filter_var($subnet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
|
$ip_bin = inet_pton($ip);
|
|
$subnet_bin = inet_pton($subnet);
|
|
$bytes = $bits / 8;
|
|
$remainder = $bits % 8;
|
|
|
|
if ($bytes > 0) {
|
|
if (substr($ip_bin, 0, $bytes) !== substr($subnet_bin, 0, $bytes)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ($remainder > 0 && $bytes < 16) {
|
|
$mask = 0xFF << (8 - $remainder);
|
|
$ip_byte = ord($ip_bin[$bytes]);
|
|
$subnet_byte = ord($subnet_bin[$bytes]);
|
|
if (($ip_byte & $mask) !== ($subnet_byte & $mask)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Helper: Convert memory string to bytes
|
|
*/
|
|
private static function convert_to_bytes($val) {
|
|
$val = trim($val);
|
|
$last = strtolower($val[strlen($val)-1]);
|
|
$val = (int)$val;
|
|
|
|
switch($last) {
|
|
case 'g':
|
|
$val *= 1024;
|
|
case 'm':
|
|
$val *= 1024;
|
|
case 'k':
|
|
$val *= 1024;
|
|
}
|
|
|
|
return $val;
|
|
}
|
|
}
|
|
|
|
// Initialize security fixes
|
|
add_action('plugins_loaded', array('MCB_Security_Fixes', 'init'), 5);
|
|
add_action('rest_api_init', function() {
|
|
register_rest_route('maple-code-blocks/v1', '/health', array(
|
|
'methods' => 'GET',
|
|
'callback' => function() {
|
|
$health = array(
|
|
'status' => 'healthy',
|
|
'version' => '2.0.0',
|
|
'php_version' => PHP_VERSION,
|
|
'wordpress_version' => get_bloginfo('version'),
|
|
'memory_usage' => memory_get_usage(true),
|
|
'memory_limit' => ini_get('memory_limit'),
|
|
'wordfence_active' => defined('WORDFENCE_VERSION'),
|
|
'woocommerce_active' => class_exists('WooCommerce'),
|
|
'learndash_active' => defined('LEARNDASH_VERSION'),
|
|
'cache_size' => count(get_transient('mcb_security_logs') ?: array()),
|
|
'rate_limit_active' => !defined('WORDFENCE_VERSION')
|
|
);
|
|
|
|
return new WP_REST_Response($health, 200);
|
|
},
|
|
'permission_callback' => function() {
|
|
return current_user_can('manage_options');
|
|
}
|
|
));
|
|
});
|