Preparing for PCI-DSS Compliance: Security Hardening in WordPress and AWS Infrastructures
WordPress Security Hardening for PCI-DSS
Achieving and maintaining PCI-DSS compliance for a WordPress-powered application, especially one handling cardholder data (CHD), necessitates a rigorous approach to security. This goes beyond basic WordPress security plugins and requires deep dives into server configurations, application-level hardening, and robust access controls. We’ll focus on practical, production-ready steps.
1. Secure WordPress Core and Plugins/Themes
Regular updates are non-negotiable. However, for PCI-DSS, we need to ensure that the update process itself is secure and that we’re not introducing vulnerabilities through third-party code.
1.1. Automated Security Audits
Implement automated scanning for vulnerabilities in core, plugins, and themes. Tools like WPScan can be integrated into CI/CD pipelines.
Example WPScan integration in a CI/CD script (e.g., GitLab CI, GitHub Actions):
# Install WPScan (if not already available) pip install wpscan # Run a vulnerability scan wpscan --url https://your-pci-compliant-domain.com --enumerate plugins,themes,users --api-token YOUR_WPVS_API_TOKEN --format json > wpscan_report.json # In a CI/CD pipeline, you would then parse wpscan_report.json # and fail the build if critical vulnerabilities are detected. # Example check (simplified): if grep -q '"level":"high"' wpscan_report.json; then echo "High severity vulnerability detected. Failing build." exit 1 fi
1.2. Plugin and Theme Vetting
Before deploying any plugin or theme, especially those that interact with CHD or sensitive user data, perform manual code reviews or use static analysis tools. Prioritize plugins from reputable sources with active development and good security track records. Remove all unused plugins and themes.
2. Database Security
The WordPress database (typically MySQL/MariaDB) is a prime target. PCI-DSS mandates strict controls over database access and data protection.
2.1. Database User Privileges
The WordPress database user should have the absolute minimum necessary privileges. Avoid granting `ALL PRIVILEGES`. For instance, the WordPress user should not have privileges to drop tables or execute administrative commands.
Example MySQL user creation and privilege granting:
-- Connect to MySQL as root or a privileged user -- mysql -u root -p CREATE USER 'wp_pci_user'@'localhost' IDENTIFIED BY 'a_very_strong_password'; GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX, DROP, EXECUTE ON wp_database_name.* TO 'wp_pci_user'@'localhost'; -- Revoke unnecessary privileges REVOKE GRANT OPTION ON wp_database_name.* FROM 'wp_pci_user'@'localhost'; REVOKE SUPER ON *.* FROM 'wp_pci_user'@'localhost'; -- Ensure no SUPER privileges FLUSH PRIVILEGES;
Note: The `DROP` privilege might be controversial. If your security policy dictates, you might omit it, but WordPress core often uses it for transient cleanup. If omitted, ensure alternative cleanup mechanisms are in place or that WordPress is configured to not rely on it. For PCI-DSS, minimizing privileges is paramount. If WordPress requires `DROP`, ensure it’s only for specific tables (e.g., `wp_options` for transients) and not for core tables.
2.2. Data Encryption
While WordPress itself doesn’t natively encrypt sensitive data at rest within the database (like credit card numbers, which should ideally never be stored), you can leverage database-level encryption features (e.g., MySQL’s Transparent Data Encryption – TDE) or application-level encryption for specific fields if absolutely necessary. For PCI-DSS, storing CHD directly in the WordPress database is highly discouraged and often prohibited unless specific, robust controls are in place.
3. File System Permissions
Incorrect file permissions are a common vector for attacks. WordPress files and directories must have restrictive permissions.
Recommended permissions:
# Navigate to your WordPress root directory
cd /var/www/html/your-wordpress-site
# Set ownership to the web server user (e.g., www-data, apache)
sudo chown -R www-data:www-data .
# Set directory permissions (755 is common, but 750 might be more secure if possible)
sudo find . -type d -exec chmod 755 {} \;
# Set file permissions (644 is common, but 640 might be more secure)
sudo find . -type f -exec chmod 644 {} \;
# Crucially, wp-config.php must be more restrictive
sudo chmod 640 wp-config.php
# Or even more restrictive if your server setup allows (e.g., 600 if not readable by group)
# sudo chmod 600 wp-config.php
# Ensure uploads directory is writable by the web server, but not executable
sudo chmod -R 755 wp-content/uploads
# If using PHP-FPM, ensure the user running PHP-FPM can write to uploads
# sudo chown -R www-data:www-data wp-content/uploads
4. Web Server Configuration (Nginx Example)
The web server acts as the first line of defense. Hardening Nginx is critical for PCI-DSS compliance.
4.1. Restrict Access to Sensitive Files
Prevent direct access to configuration files, log files, and other sensitive assets.
server {
listen 443 ssl http2;
server_name your-pci-compliant-domain.com;
# SSL Configuration (essential for PCI-DSS)
ssl_certificate /etc/letsencrypt/live/your-pci-compliant-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-pci-compliant-domain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3; # Use modern, secure protocols
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s; # Use reliable DNS resolvers
resolver_timeout 5s;
# Security Headers
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self';" always; # Adjust CSP as needed
root /var/www/html/your-wordpress-site;
index index.php index.html index.htm;
location / {
try_files $uri $uri/ /index.php?$args;
}
# Prevent access to sensitive files
location ~* /(?:wp-config\.php|readme\.html|license\.txt|wp-settings\.php|wp-cron\.php|xmlrpc\.php)$ {
deny all;
return 403;
}
# Prevent access to hidden files
location ~ /\. {
deny all;
return 403;
}
# Prevent access to wp-includes (except for specific files if needed, e.g., AJAX handlers)
location ~ ^/wp-includes/.*\.php$ {
deny all;
return 403;
}
# PHP processing
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; # Adjust PHP version and socket path
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
# Security for PHP files
# Ensure PHP files in wp-includes are not directly executable
if ($uri ~* "^/wp-includes/") {
return 403;
}
}
# Deny access to .htaccess files
location ~ /\.ht {
deny all;
return 403;
}
# Protect the uploads directory from direct PHP execution
location ~* /wp-content/uploads/.*.(php|js|css|html|htm)$ {
deny all;
return 403;
}
# Access and error logs
access_log /var/log/nginx/your-pci-compliant-domain.com.access.log;
error_log /var/log/nginx/your-pci-compliant-domain.com.error.log;
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name your-pci-compliant-domain.com;
return 301 https://$host$request_uri;
}
4.2. Rate Limiting and Intrusion Prevention
Implement rate limiting to prevent brute-force attacks on login pages and XML-RPC. Consider integrating with a Web Application Firewall (WAF) like ModSecurity or using cloud-based WAF services.
# Example Nginx rate limiting for login attempts
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/min;
limit_req_zone $binary_remote_addr zone=xmlrpc_limit:10m rate=1r/min;
# ... inside your server block ...
location /xmlrpc.php {
limit_req zone=xmlrpc_limit burst=1 nodelay;
# ... rest of xmlrpc.php configuration ...
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location /wp-login.php {
limit_req zone=login_limit burst=10 nodelay;
# ... rest of wp-login.php configuration ...
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
5. AWS Infrastructure Security for PCI-DSS
When hosting WordPress on AWS, compliance extends to the cloud environment. This involves securing EC2 instances, VPCs, S3 buckets, RDS databases, and IAM roles.
5.1. VPC and Network Security
VPC Design: Use private subnets for your WordPress servers and RDS instances. Expose only necessary services (e.g., web servers) through public subnets via NAT Gateways or Load Balancers. Implement Security Groups and Network ACLs (NACLs) with a deny-by-default policy.
Security Groups:
# Example Security Group for WordPress EC2 instances (assuming behind ALB) # Inbound Rules: # Type: SSH, Protocol: TCP, Port Range: 22, Source: Your Bastion Host/VPN IP Range (e.g., 10.0.1.0/24) # Type: HTTP, Protocol: TCP, Port Range: 80, Source: ALB Security Group ID (sg-xxxxxxxxxxxxxxxxx) # Type: HTTPS, Protocol: TCP, Port Range: 443, Source: ALB Security Group ID (sg-xxxxxxxxxxxxxxxxx) # Example Security Group for Application Load Balancer (ALB) # Inbound Rules: # Type: HTTP, Protocol: TCP, Port Range: 80, Source: 0.0.0.0/0 # Type: HTTPS, Protocol: TCP, Port Range: 443, Source: 0.0.0.0/0 # Example Security Group for RDS (MySQL/MariaDB) # Inbound Rules: # Type: MySQL/Aurora, Protocol: TCP, Port Range: 3306, Source: WordPress EC2 Security Group ID (sg-yyyyyyyyyyyyyyyyy)
5.2. EC2 Instance Hardening
Use hardened Amazon Machine Images (AMIs). Regularly patch operating systems and installed software. Implement a bastion host for SSH access, disallowing direct SSH from the internet.
SSH Access via Bastion Host:
# On your local machine: ssh -A -i ~/.ssh/your-private-key.pem ec2-user@your-bastion-host-public-ip \ -J ec2-user@your-wordpress-instance-private-ip # Or using SSH config: # ~/.ssh/config # Host bastion # HostName your-bastion-host-public-ip # User ec2-user # IdentityFile ~/.ssh/your-private-key.pem # # Host wordpress-instance # HostName your-wordpress-instance-private-ip # User ec2-user # IdentityFile ~/.ssh/your-private-key.pem # ProxyJump bastion # Then connect: ssh wordpress-instance
5.3. IAM Roles and Policies
Follow the principle of least privilege for IAM users and roles. EC2 instances should assume IAM roles to access other AWS services (e.g., S3 for backups) rather than using access keys embedded in code or configuration.
# Example IAM Policy for an EC2 instance needing S3 access for backups
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:ListBucket",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::your-pci-compliant-backup-bucket",
"arn:aws:s3:::your-pci-compliant-backup-bucket/*"
]
}
]
}
5.4. RDS Security
Ensure RDS instances are in private subnets. Enable encryption at rest. Configure automated backups and point-in-time recovery. Restrict access via Security Groups as detailed above.
5.5. Logging and Monitoring
Enable comprehensive logging for AWS services (CloudTrail, VPC Flow Logs, ELB access logs) and your WordPress application (web server logs, PHP error logs). Centralize logs using CloudWatch Logs or a SIEM solution. Set up alerts for suspicious activities.
# Example: Installing and configuring CloudWatch agent on EC2 for log forwarding
# Download agent
wget https://s3.amazonaws.com/amazoncloudwatch-agent/linux/amd64/latest/amazon-cloudwatch-agent.zip
unzip amazon-cloudwatch-agent.zip
sudo ./install.sh
# Create a configuration file (e.g., /opt/aws/amazon-cloudwatch/agent/cwagent-config.json)
# This example forwards Nginx access and error logs, and PHP error logs
{
"agent": {
"metrics_collection_interval": 60,
"run_as_user": "cwagent"
},
"logs": {
"logs_collected": {
"files": {
"collect_list": [
{
"file_path": "/var/log/nginx/your-pci-compliant-domain.com.access.log",
"log_group_name": "/aws/wordpress/nginx/access",
"log_stream_name": "{instance_id}/nginx-access"
},
{
"file_path": "/var/log/nginx/your-pci-compliant-domain.com.error.log",
"log_group_name": "/aws/wordpress/nginx/error",
"log_stream_name": "{instance_id}/nginx-error"
},
{
"file_path": "/var/log/php7.4-fpm.log", # Adjust path for your PHP logs
"log_group_name": "/aws/wordpress/php/error",
"log_stream_name": "{instance_id}/php-error"
}
]
}
}
}
}
# Start the agent
sudo /opt/aws/amazon-cloudwatch/agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:/opt/aws/amazon-cloudwatch/agent/cwagent-config.json -s
6. Regular Auditing and Penetration Testing
PCI-DSS compliance is an ongoing process. Schedule regular internal and external vulnerability scans and penetration tests. Maintain detailed records of all security configurations, changes, and audit findings. This forms the backbone of your compliance audit evidence.