- Fix production debug exposure in Zoho admin interface (WP_DEBUG conditional) - Implement secure credential storage with AES-256-CBC encryption - Add file upload size limits (5MB profiles, 2MB logos) with enhanced validation - Fix privilege escalation via PHP Reflection bypass with public method alternative - Add comprehensive input validation and security headers - Update plugin version to 1.0.7 with security hardening Security improvements: ✅ Debug information exposure eliminated in production ✅ API credentials now encrypted in database storage ✅ File upload security enhanced with size/type validation ✅ AJAX endpoints secured with proper capability checks ✅ SQL injection protection verified via parameterized queries ✅ CSRF protection maintained with nonce verification 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
186 lines
No EOL
4.8 KiB
PHP
186 lines
No EOL
4.8 KiB
PHP
<?php
|
|
/**
|
|
* HVAC Secure Storage - Encrypted credential storage
|
|
*
|
|
* @package HVAC_Community_Events
|
|
* @since 1.0.7
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* HVAC_Secure_Storage class
|
|
*
|
|
* Provides encrypted storage for sensitive data like API keys
|
|
*/
|
|
class HVAC_Secure_Storage {
|
|
|
|
/**
|
|
* Encryption method
|
|
*/
|
|
const ENCRYPTION_METHOD = 'AES-256-CBC';
|
|
|
|
/**
|
|
* Get encryption key
|
|
*
|
|
* @return string
|
|
*/
|
|
private static function get_encryption_key() {
|
|
$key = get_option('hvac_encryption_key');
|
|
|
|
if (!$key) {
|
|
// Generate a new key if one doesn't exist
|
|
$key = base64_encode(random_bytes(32));
|
|
update_option('hvac_encryption_key', $key);
|
|
}
|
|
|
|
return base64_decode($key);
|
|
}
|
|
|
|
/**
|
|
* Encrypt data
|
|
*
|
|
* @param string $data Data to encrypt
|
|
* @return string Encrypted data
|
|
*/
|
|
public static function encrypt($data) {
|
|
if (empty($data)) {
|
|
return '';
|
|
}
|
|
|
|
$key = self::get_encryption_key();
|
|
$iv = random_bytes(openssl_cipher_iv_length(self::ENCRYPTION_METHOD));
|
|
|
|
$encrypted = openssl_encrypt($data, self::ENCRYPTION_METHOD, $key, 0, $iv);
|
|
|
|
if ($encrypted === false) {
|
|
return false;
|
|
}
|
|
|
|
return base64_encode($iv . $encrypted);
|
|
}
|
|
|
|
/**
|
|
* Decrypt data
|
|
*
|
|
* @param string $encrypted_data Encrypted data
|
|
* @return string|false Decrypted data or false on failure
|
|
*/
|
|
public static function decrypt($encrypted_data) {
|
|
if (empty($encrypted_data)) {
|
|
return '';
|
|
}
|
|
|
|
$data = base64_decode($encrypted_data);
|
|
if ($data === false) {
|
|
return false;
|
|
}
|
|
|
|
$key = self::get_encryption_key();
|
|
$iv_length = openssl_cipher_iv_length(self::ENCRYPTION_METHOD);
|
|
|
|
if (strlen($data) < $iv_length) {
|
|
return false;
|
|
}
|
|
|
|
$iv = substr($data, 0, $iv_length);
|
|
$encrypted = substr($data, $iv_length);
|
|
|
|
return openssl_decrypt($encrypted, self::ENCRYPTION_METHOD, $key, 0, $iv);
|
|
}
|
|
|
|
/**
|
|
* Store encrypted credential
|
|
*
|
|
* @param string $option_name Option name
|
|
* @param string $value Value to store
|
|
* @return bool Success
|
|
*/
|
|
public static function store_credential($option_name, $value) {
|
|
if (empty($value)) {
|
|
delete_option($option_name);
|
|
return true;
|
|
}
|
|
|
|
$encrypted = self::encrypt($value);
|
|
if ($encrypted === false) {
|
|
return false;
|
|
}
|
|
|
|
return update_option($option_name, $encrypted);
|
|
}
|
|
|
|
/**
|
|
* Retrieve encrypted credential
|
|
*
|
|
* @param string $option_name Option name
|
|
* @param string $default Default value
|
|
* @return string Decrypted value
|
|
*/
|
|
public static function get_credential($option_name, $default = '') {
|
|
$encrypted = get_option($option_name, '');
|
|
|
|
if (empty($encrypted)) {
|
|
return $default;
|
|
}
|
|
|
|
$decrypted = self::decrypt($encrypted);
|
|
return $decrypted !== false ? $decrypted : $default;
|
|
}
|
|
|
|
/**
|
|
* Migrate existing plaintext credentials to encrypted storage
|
|
*
|
|
* @return array Migration results
|
|
*/
|
|
public static function migrate_existing_credentials() {
|
|
$credentials_to_migrate = [
|
|
'hvac_zoho_client_id',
|
|
'hvac_zoho_client_secret',
|
|
'hvac_zoho_refresh_token',
|
|
'hvac_google_maps_api_key'
|
|
];
|
|
|
|
$results = [
|
|
'migrated' => 0,
|
|
'skipped' => 0,
|
|
'errors' => []
|
|
];
|
|
|
|
foreach ($credentials_to_migrate as $option_name) {
|
|
$plaintext = get_option($option_name, '');
|
|
|
|
if (empty($plaintext)) {
|
|
$results['skipped']++;
|
|
continue;
|
|
}
|
|
|
|
// Check if it's already encrypted (basic heuristic)
|
|
$decrypted = self::decrypt($plaintext);
|
|
if ($decrypted !== false && $decrypted !== $plaintext) {
|
|
$results['skipped']++;
|
|
continue;
|
|
}
|
|
|
|
// Migrate to encrypted storage
|
|
if (self::store_credential($option_name, $plaintext)) {
|
|
$results['migrated']++;
|
|
} else {
|
|
$results['errors'][] = "Failed to migrate: $option_name";
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Check if OpenSSL is available
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function is_encryption_available() {
|
|
return function_exists('openssl_encrypt') && function_exists('openssl_decrypt');
|
|
}
|
|
} |