Security Guidelines
Table of Contents
Security Guidelines
Security by Design for WordPress Themes
Jankx 2.0 được xây dựng với security-first approach, tích hợp sâu các biện pháp bảo mật vào kiến trúc core.
🛡️ Security Principles
Defense in Depth
- Input Validation: Validate all user inputs
- Output Escaping: Escape all outputs
- Access Control: Implement proper permissions
- Data Sanitization: Sanitize all data
- Nonce Verification: Verify request authenticity
- CSRF Protection: Prevent cross-site request forgery
Security Layers
┌─────────────────────────────────────┐
│ Application Layer │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Input │ │ Output │ │
│ │ Validation │ │ Escaping │ │
│ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Framework Layer │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Nonce │ │ Capability│ │
│ │ Verification│ │ Checks │ │
│ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ WordPress Layer │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Hooks │ │ Filters │ │
│ │ Security │ │ Security │ │
│ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────┘
🔒 Input Security
Input Validation
class InputValidator
{
public function validateEmail(string $email): bool
{
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
public function validateUrl(string $url): bool
{
return filter_var($url, FILTER_VALIDATE_URL) !== false;
}
public function validateInteger(int $value, int $min = null, int $max = null): bool
{
if ($min !== null && $value < $min) {
return false;
}
if ($max !== null && $value > $max) {
return false;
}
return is_int($value);
}
public function validateString(string $value, int $minLength = 0, int $maxLength = null): bool
{
$length = strlen($value);
if ($length < $minLength) {
return false;
}
if ($maxLength !== null && $length > $maxLength) {
return false;
}
return true;
}
public function validateArray(array $data, array $required = [], array $optional = []): bool
{
// Check required fields
foreach ($required as $field) {
if (!isset($data[$field])) {
return false;
}
}
// Check for unexpected fields
$allowedFields = array_merge($required, $optional);
foreach ($data as $field => $value) {
if (!in_array($field, $allowedFields)) {
return false;
}
}
return true;
}
}
Input Sanitization
class InputSanitizer
{
public function sanitizeText(string $text): string
{
return sanitize_text_field($text);
}
public function sanitizeEmail(string $email): string
{
return sanitize_email($email);
}
public function sanitizeUrl(string $url): string
{
return esc_url_raw($url);
}
public function sanitizeHtml(string $html): string
{
return wp_kses_post($html);
}
public function sanitizeArray(array $data): array
{
$sanitized = [];
foreach ($data as $key => $value) {
if (is_string($value)) {
$sanitized[$key] = $this->sanitizeText($value);
} elseif (is_array($value)) {
$sanitized[$key] = $this->sanitizeArray($value);
} else {
$sanitized[$key] = $value;
}
}
return $sanitized;
}
public function sanitizeFile(array $file): array
{
return [
'name' => sanitize_file_name($file['name']),
'type' => sanitize_mime_type($file['type']),
'tmp_name' => $file['tmp_name'],
'error' => (int) $file['error'],
'size' => (int) $file['size']
];
}
}
🛡️ Output Security
Output Escaping
class OutputEscaper
{
public function escapeHtml(string $text): string
{
return esc_html($text);
}
public function escapeAttr(string $text): string
{
return esc_attr($text);
}
public function escapeUrl(string $url): string
{
return esc_url($url);
}
public function escapeJs(string $text): string
{
return esc_js($text);
}
public function escapeCss(string $text): string
{
return esc_css($text);
}
public function escapeHtmlComment(string $text): string
{
return esc_html__($text, 'jankx');
}
public function escapeTemplate(array $data): array
{
$escaped = [];
foreach ($data as $key => $value) {
if (is_string($value)) {
$escaped[$key] = $this->escapeHtml($value);
} elseif (is_array($value)) {
$escaped[$key] = $this->escapeTemplate($value);
} else {
$escaped[$key] = $value;
}
}
return $escaped;
}
}
Template Security
class SecureTemplateRenderer
{
private $escaper;
public function __construct(OutputEscaper $escaper)
{
$this->escaper = $escaper;
}
public function render(string $template, array $data): string
{
// Escape all data before rendering
$escapedData = $this->escaper->escapeTemplate($data);
return $this->renderTemplate($template, $escapedData);
}
private function renderTemplate(string $template, array $data): string
{
// Use secure template engine
$engine = new SecureTemplateEngine();
return $engine->render($template, $data);
}
}
🔐 Authentication & Authorization
Nonce Verification
class NonceManager
{
public function createNonce(string $action): string
{
return wp_create_nonce($action);
}
public function verifyNonce(string $nonce, string $action): bool
{
return wp_verify_nonce($nonce, $action);
}
public function createNonceField(string $action, string $name = '_wpnonce'): string
{
return wp_nonce_field($action, $name, true, false);
}
public function createNonceUrl(string $url, string $action): string
{
return wp_nonce_url($url, $action);
}
}
Capability Checks
class CapabilityChecker
{
public function canEditPosts(): bool
{
return current_user_can('edit_posts');
}
public function canEditPages(): bool
{
return current_user_can('edit_pages');
}
public function canManageOptions(): bool
{
return current_user_can('manage_options');
}
public function canUploadFiles(): bool
{
return current_user_can('upload_files');
}
public function canEditPost(int $postId): bool
{
return current_user_can('edit_post', $postId);
}
public function canDeletePost(int $postId): bool
{
return current_user_can('delete_post', $postId);
}
public function requireCapability(string $capability): void
{
if (!current_user_can($capability)) {
wp_die(__('You do not have sufficient permissions to access this page.', 'jankx'));
}
}
}
🚫 CSRF Protection
CSRF Token Management
class CSRFProtector
{
private $nonceManager;
public function __construct(NonceManager $nonceManager)
{
$this->nonceManager = $nonceManager;
}
public function generateToken(string $action): string
{
return $this->nonceManager->createNonce($action);
}
public function verifyToken(string $token, string $action): bool
{
return $this->nonceManager->verifyNonce($token, $action);
}
public function validateRequest(string $action): bool
{
$nonce = $_POST['_wpnonce'] ?? $_GET['_wpnonce'] ?? '';
return $this->verifyToken($nonce, $action);
}
public function requireValidToken(string $action): void
{
if (!$this->validateRequest($action)) {
wp_die(__('Security check failed.', 'jankx'));
}
}
}
Form Security
class SecureFormHandler
{
private $csrfProtector;
private $validator;
private $sanitizer;
public function __construct(CSRFProtector $csrfProtector, InputValidator $validator, InputSanitizer $sanitizer)
{
$this->csrfProtector = $csrfProtector;
$this->validator = $validator;
$this->sanitizer = $sanitizer;
}
public function handleFormSubmission(string $action, array $rules): array
{
// Verify CSRF token
$this->csrfProtector->requireValidToken($action);
// Validate input
if (!$this->validator->validateArray($_POST, $rules['required'], $rules['optional'])) {
throw new ValidationException('Invalid form data');
}
// Sanitize input
$sanitizedData = $this->sanitizer->sanitizeArray($_POST);
return $sanitizedData;
}
public function renderForm(string $action, array $fields): string
{
$nonceField = $this->csrfProtector->generateToken($action);
$html = '<form method="post" action="">';
$html .= wp_nonce_field($action, '_wpnonce', true, false);
foreach ($fields as $field) {
$html .= $this->renderField($field);
}
$html .= '<input type="submit" value="Submit">';
$html .= '</form>';
return $html;
}
}
🛡️ XSS Prevention
XSS Protection
class XSSProtector
{
public function preventXSS(string $input): string
{
// Remove script tags
$input = preg_replace('/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/mi', '', $input);
// Remove event handlers
$input = preg_replace('/on\w+\s*=/i', '', $input);
// Remove javascript: URLs
$input = preg_replace('/javascript:/i', '', $input);
// Remove data: URLs
$input = preg_replace('/data:/i', '', $input);
return $input;
}
public function sanitizeUserContent(string $content): string
{
// Use WordPress kses for HTML content
$allowedTags = [
'p' => [],
'br' => [],
'strong' => [],
'em' => [],
'a' => ['href' => [], 'title' => []],
'img' => ['src' => [], 'alt' => [], 'title' => []]
];
return wp_kses($content, $allowedTags);
}
public function validateImageUrl(string $url): bool
{
// Check if URL is safe
$parsed = parse_url($url);
if (!$parsed || !isset($parsed['scheme'])) {
return false;
}
// Only allow http and https
if (!in_array($parsed['scheme'], ['http', 'https'])) {
return false;
}
// Check for suspicious patterns
$suspiciousPatterns = [
'javascript:',
'data:',
'vbscript:',
'onload=',
'onerror='
];
foreach ($suspiciousPatterns as $pattern) {
if (stripos($url, $pattern) !== false) {
return false;
}
}
return true;
}
}
🔒 File Upload Security
File Upload Validation
class SecureFileUploader
{
private $allowedTypes = [
'image/jpeg',
'image/png',
'image/gif',
'image/webp'
];
private $maxFileSize = 5242880; // 5MB
public function validateUpload(array $file): bool
{
// Check file size
if ($file['size'] > $this->maxFileSize) {
return false;
}
// Check file type
if (!in_array($file['type'], $this->allowedTypes)) {
return false;
}
// Check file extension
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
if (!in_array($extension, $allowedExtensions)) {
return false;
}
// Check for malicious content
if ($this->containsMaliciousContent($file['tmp_name'])) {
return false;
}
return true;
}
private function containsMaliciousContent(string $filePath): bool
{
$content = file_get_contents($filePath);
// Check for PHP tags
if (strpos($content, '<?php') !== false) {
return true;
}
// Check for script tags
if (preg_match('/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/mi', $content)) {
return true;
}
return false;
}
public function secureUpload(array $file): string
{
if (!$this->validateUpload($file)) {
throw new SecurityException('Invalid file upload');
}
// Generate secure filename
$extension = pathinfo($file['name'], PATHINFO_EXTENSION);
$filename = uniqid() . '.' . $extension;
// Move to secure location
$uploadDir = wp_upload_dir();
$destination = $uploadDir['basedir'] . '/secure/' . $filename;
if (!move_uploaded_file($file['tmp_name'], $destination)) {
throw new SecurityException('Failed to upload file');
}
return $uploadDir['baseurl'] . '/secure/' . $filename;
}
}
🔍 Security Monitoring
Security Logger
class SecurityLogger
{
public function logSecurityEvent(string $event, array $data = []): void
{
$logEntry = [
'timestamp' => current_time('mysql'),
'event' => $event,
'ip' => $this->getClientIP(),
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
'user_id' => get_current_user_id(),
'data' => $data
];
error_log('SECURITY: ' . json_encode($logEntry));
}
public function logFailedLogin(string $username): void
{
$this->logSecurityEvent('failed_login', ['username' => $username]);
}
public function logXSSAttempt(string $input): void
{
$this->logSecurityEvent('xss_attempt', ['input' => $input]);
}
public function logCSRFAttempt(string $action): void
{
$this->logSecurityEvent('csrf_attempt', ['action' => $action]);
}
public function logFileUploadAttempt(array $file): void
{
$this->logSecurityEvent('file_upload_attempt', [
'filename' => $file['name'],
'type' => $file['type'],
'size' => $file['size']
]);
}
private function getClientIP(): string
{
$ipKeys = ['HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR'];
foreach ($ipKeys as $key) {
if (array_key_exists($key, $_SERVER) === true) {
foreach (explode(',', $_SERVER[$key]) as $ip) {
$ip = trim($ip);
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
return $ip;
}
}
}
}
return $_SERVER['REMOTE_ADDR'] ?? '';
}
}
🛠 Security Configuration
Security Headers
class SecurityHeaders
{
public function setSecurityHeaders(): void
{
// Content Security Policy
header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https:;");
// X-Frame-Options
header("X-Frame-Options: SAMEORIGIN");
// X-Content-Type-Options
header("X-Content-Type-Options: nosniff");
// X-XSS-Protection
header("X-XSS-Protection: 1; mode=block");
// Referrer Policy
header("Referrer-Policy: strict-origin-when-cross-origin");
// Permissions Policy
header("Permissions-Policy: geolocation=(), microphone=(), camera=()");
}
}
Security Constants
// Define security constants
define('JANKX_SECURITY_NONCE_ACTION', 'jankx_security_nonce');
define('JANKX_SECURITY_MAX_LOGIN_ATTEMPTS', 5);
define('JANKX_SECURITY_LOCKOUT_DURATION', 900); // 15 minutes
define('JANKX_SECURITY_SESSION_TIMEOUT', 3600); // 1 hour
define('JANKX_SECURITY_PASSWORD_MIN_LENGTH', 8);
define('JANKX_SECURITY_FILE_MAX_SIZE', 5242880); // 5MB
Next: XSS Prevention | SVG Sanitization | Nonce Verification |