Networking & Content Delivery

Secure and accelerate your WordPress CMS with Amazon CloudFront, AWS WAF, and edge functions

Application owners often rely on content management systems (CMS) to publish and manage content on their websites. WordPress is the world’s most popular content management system. Originally launched as a blogging platform back in 2003, WordPress now powers 43% of all websites and controls a massive 64.3% of the known CMS market.

The purpose of this post is to show how you can secure and accelerate an existing WordPress website using Amazon Web Services (AWS) edge services: Amazon CloudFront, Amazon CloudFront Functions, and AWS WAF.

Amazon CloudFront is a content delivery network (CDN) service built for high performance, security, and developer convenience. It offers improved security and acceleration of content when served through it, either for static or dynamic content through the purpose-built and feature-rich AWS global network infrastructure supporting edge termination and WebSockets.

CloudFront Functions is a feature of Amazon CloudFront, that enables you to write lightweight functions in JavaScript for high-scale, latency-sensitive CDN customizations. CloudFront Functions can manipulate the requests and responses that flow through CloudFront, perform basic authentication and authorization, generate HTTP responses at the edge, and more.

AWS WAF is a web application firewall that helps protect your web applications or APIs against common web exploits and bots that may affect availability, compromise security, or consume excessive resources. It is fully integrated with Amazon CloudFront, providing mechanisms to improve the security of your application at Layer 7 (the application layer of the OSI model).

Solution overview

Regarding CloudFront, you will use the following (updated) CloudFront reference table to configure the behaviors and the cache policies with a small modification. You will redirect all traffic form HTTP to HTTPS :

Property Static Dynamic (admin) Dynamic (front end)
1 Paths (behaviors) wp-content/*
wp-includes/*
wp-admin/*
wp-login.php
default (*)
2 Protocols HTTP and HTTPS Redirect to HTTPS Redirect to HTTPS HTTP and HTTPS Redirect to HTTPS
3 HTTP methods GET, HEAD ALL ALL
4 HTTP headers NONE ALL Host
CloudFront-Forwarded-Proto
CloudFront-Is-Mobile-Viewer
CloudFront-Is-Tablet-Viewer
CloudFront-Is-Desktop-Viewer
5 Cookies NONE ALL comment_*
wordpress_*
wp-settings-*
6 Query strings YES (invalidation) YES YES

Two distributions: One for the application owners (admin access) and one for the viewers

In this post, you dedicate one distribution for each of the two types of users: application owners (admin users) and viewers (non-admin users). This approach is the best one because it allows dedicated access, management, and cache policies for each group of users.

Target architecture for application owner and viewers access

Fig 1: Target architecture for application owner and viewers access

Additionally, you will configure an AWS WAF for each distribution.

For viewers, you will block all “Admin” access (wp-admin/*) with AWS WAF, using AWS Managed Rules for AWS WAF, more precisely, AWSManagedRulesAdminProtectionRuleSet. This rule group contains rules that allow you to block external access to exposed admin pages. This will reduce the risk of a malicious actor gaining administrative access to your WordPress.

You had to scope down the rule group to allowlist some URIs (wp-admin/css/*, wp-admin/js/*, and wp-admin/images/*) because they are called by pages that do not have admin privileges.

You will also configure additional rule groups, such as AWSManagedRulesWordPressRuleSet, that block request patterns associated with the exploitation of vulnerabilities specific to WordPress sites. This rule group should be used in conjunction with the SQL database and PHP application rule groups. Find the detailed list of the rule groups used below:

AWS WAF managed rules configured for viewers users

Fig 2 : AWS WAF managed rules configured for viewers users

Additional rules 

You also defined additional rules to protect your distributions. This is a recommendation but you can update the rules depending on your security posture.

  • AWSManagedRulesBotControlRuleSet– The bot control managed rule group provides rules to block and manage requests from bots. Bots can consume excess resources, skew business metrics, cause downtime, and perform malicious activities.
  • AWSManagedRulesPHPRuleSet – The PHP application rule group contains rules that block request patterns associated with the exploitation of vulnerabilities specific to the use of the PHP programming language, including injection of unsafe PHP functions.
  • AWSManagedRulesCommonRuleSet – The core rule set (CRS) rule group contains rules that are generally applicable to web applications. This provides protection against exploitation of a wide range of vulnerabilities, including some of the high-risk and commonly occurring vulnerabilities described in OWASP publications such as OWASP Top 10.
  • AWSManagedRulesKnownBadInputsRuleSet – The known bad inputs rule group contains rules to block request patterns that are known to be invalid and are associated with exploitation or discovery of vulnerabilities. This can help reduce the risk of a malicious actor discovering a vulnerable application.
  • AWSManagedRulesSQLiRuleSet – The SQL database rule group contains rules to block request patterns associated with exploitation of SQL databases, like SQL injection attacks. This can help prevent remote injection of unauthorized queries.

Following the same principle, the application owners distribution (admins) will be protected by AWS WAF with the following rules below:

AWS WAF managed rules configured for application owners

Fig 3 : AWS WAF managed rules configured for application owners

You have two options to protect the access to the admin page for application owners. You can either use an Amazon CloudFront Function to check if the authentication cookie set by WordPress at the authentication stage is in the query; otherwise you can redirect to the login page. Or you can configure the same rule AWSManagedRulesAdminProtectionRuleSet as earlier to block admin access if the authentication cookie is not set.

In this post, you will take the first option. You will implement a CloudFront Function that will check the session cookies (wordpress_logged_in_*) and redirect to the logging page if not set. However, for the URIs wp-admin/css/*, wp-admin/js/*, and wp-admin/images/*), it won’t redirect.

function handler(event) {
    var request = event.request;
    var cookies = request.cookies;
    var host = request.headers.host.value;
    var newurl = 'https://' + host +'/wp-login.php'
    var uri = request.uri
    console.log("Event : " + JSON.stringify(event));
    
    
    var auth_cookie = Object.keys(cookies).filter(v => v.startsWith('wordpress_logged_in_'));
    
    console.log("size cookies: " + auth_cookie.length);
    
    if(auth_cookie.length != 0){
        console.log("Auth cookie : " + JSON.stringify(auth_cookie));
    }else if(!uri.includes("wp-admin/css") && !uri.includes("wp-admin/js") && !uri.includes("wp-admin/images")){
        var response = {
                statusCode: 302,
                statusDescription: 'Found',
                headers:
                    { "location": { "value": newurl } }
                }
        console.log("Response : " + JSON.stringify(response));
        return response;
    }
    
    return request;
}

The pros of this are that the redirection will be done at the edge and thus accelerate the traffic, while the cons are the price of the CloudFront Functions ($0.10 per 1 million invocations). Depending on the number of requests on wp-admin, it might not be worth it financially. This function will be triggered on the viewer request for the behavior “wp-admin/*”. 

You may want to be more restrictive and force all requests for the admin distribution to be authenticated. In that case, you will apply the CloudFront Functions to all behaviors.

Another option not implemented here is the possibility to protect the admin distribution with an IP allow list configuration. You can create an IP match condition to allow only a set of IPs to access the admin distribution.

You also configured the HTTP security headers in the response to protect our WordPress application. HTTP security headers improve the privacy and security of a web application and protect it from vulnerabilities on the client side. The most common HTTP security headers are:

Prerequisites

You need to have a WordPress instance deployed and listening on a publicly available endpoint.

Solution walkthrough: Secure and accelerate your WordPress CMS with Amazon CloudFront, AWS WAF, and edge functions

Now that you have covered the proposed configuration, you will walk through the setup.

Once you have the WordPress backend, which will be our origin deployed, the following GitHub repository will deploy and configure Amazon CloudFront along with AWS WAF and CloudFront Functions to serve both anonymous viewers and consumers and content application onwers (admin).

Deployment steps

Position yourself in the wordpress-cloudfront folder if not already

# Position yourself in the wordpress-cloudfront folder if not already
$cd wordpress-cloudfront


# Create a virtualenv on MacOS and Linux:
$ python3 -m venv .venv


# Activate your virtualenv
$ source .venv/bin/activate

#I nstall the required dependencies.
$ pip install -r requirements.txt

# Synthesize the CloudFormation template for this code.
$ cdk synth

# Deploy the stack
cdk deploy --parameters WordPressOriginURL="XXXXX.us-east-2.elb.amazonaws.com"
--parameters WordpressOriginIsHTTPS=true
--parameters WordPressDomainConsumers="wordpress-consumers"
--parameters WordPressDomainPublishers="wordpress-publishers"
--parameters HostedZoneId="XXXXXXXXXX"
--parameters HostedZoneName="mydomain.com"
--all

Walkthrough of the deployment parameters

The CDK needs to be launched with some parameters. Let’s understand what each of them imply.

WordPress backend configuration

  • WordPressOriginURL: Define the WordPress backend endpoint. This is the publicly available domain name pointing to your current WordPress installation. This domain name needs to be different from the one serving your consumers (viewers).
  • WordPressOriginIsHTTPS: Define whether the backend listening on https or http value is true or false

WordPress front-end configuration

  • WordPressDomainConsumers: Specify the domain name consumers (viewers) use to access your website. This domain name will be mapped to the CloudFront distribution that will be created to serve the traffic.
  • WordPressDomainPublishers: Specify the domain name used by the application owner/publisher or admin to connect to your website. This domain name will be mapped to the CloudFront distribution that will be created to serve the traffic.

Route 53 DNS configuration

  • HostedZoneId and HostedZoneName: This is used to create the ‘Domain Name’ and ‘Admin Domain Name’ records in your Route 53 hosted zone. This field is required if either or both domain names are specified.

Important

When either or both ‘WordpressDomainConsumers’ and ‘WordpressDomainPublishers’ are specified as an alternate domain name to a CloudFront distribution, you must attach a trusted TLS certificate that validates your authorization to use the domain name. This deployment creates the necessary certificates through AWS Certificate Manager (ACM) for these domains and uses DNS-based validation to authorize usage of the domain.

Note

When you specify ‘WordpressOriginIsHTTPS=true’, your origin needs to be configured to receive requests on HTTPS with a valid certificate; otherwise, you will get an error when accessing the CloudFront distribution. The recommendation is to have an origin accessed on HTTPS.

Once you specify these parameters, proceed to deploy the CDK project. After the deployment is complete, which AWS resources are created depend on the options selected during deployment.

Conclusion

In conclusion, securing and accelerating your WordPress CMS can be a challenging task, but with Amazon CloudFront, AWS WAF, and edge functions, it can be made much easier. By leveraging AWS services, you can improve the performance of your website, protect it from malicious attacks, and improve user experience. With the ability to customize and scale these services to meet your specific needs, you can ensure that your WordPress CMS is not only fast and reliable but also secure.

Next steps

If your original WordPress stack is deployed on AWS you can configure the CloudFront managed prefix list to restrict access to the load balancer only from CloudFront. The CloudFront managed prefix list contains the IP address ranges of all of CloudFront’s globally distributed origin-facing servers. If your origin is hosted on AWS and protected by an Amazon VPC security group, you can use the CloudFront managed prefix list to allow inbound traffic to your origin only from CloudFront’s origin-facing servers, preventing any traffic not originating from CloudFront from reaching your origin.

Don’t let your WordPress site’s security be an afterthought. Contact us today, and let our experts help you implement this powerful CloudFront security measure. Safeguard your WordPress stack and ensure your content is accessible only through the trusted CloudFront network.

About the authors

Cheikh Mahaman headshot.jpg

Cheikh MAHAMAN

Cheikh is serving as a Senior Solutions Architect at Amazon Web Services (AWS), where specializes in cloud computing, digital transformation, and strategic technology advisory. Cheikh helps global organizations leverage cutting-edge cloud technologies to solve complex business challenges and drive impactful results. He is passionate about cloud architecture, data analytics, and artificial intelligence. Known for his ability to communicate complex technical concepts, he enjoys public speaking and sharing his knowledge with diverse audiences.
Find him on LinkedIn at in/cheikh-samba-mahaman.