monorepo/native/wordpress/maple-code-blocks/includes/class-security-fixes.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');
}
));
});