AWS WAF (Web Application Firewall) with Laravel Getting Started Guide

Our SaaS runs a Laravel PHP web application on AWS ECS (Elastic Container Service) behind an AWS ALB (Application Load Balancer). Manually configuring the AWS WAF (Web Application Firewall) to protect our web application was very easy.

This guide will walk you through manually configuring AWS WAF to protect your Laravel PHP web application running behind an AWS ALB. Keep in mind that best practice is to write all of your AWS infrastructure as code so that your infrastructure can be easily rebuilt or duplicated. You should write a CloudFormation template that can build your WAF after you become more familiar with AWS WAF.

Pricing

Like any other AWS service, AWS WAF pricing was not clear at first. We created an ACL ($5/mo), then added several AWS Managed Groups ($1/mo each), then manually created a rule of our own to allow certain traffic that was being blocked by the AWS Managed Groups ($1/mo). Our total cost to enable WAF on our application was $11/mo.

TIP: Once the ACL has been created and configured, the same ACL and associated rules can be applied to multiple resources in your account (e.g. one or more ALB plus one or more CloudFront distributions, etc) meaning we only pay the $11/mo once even if we use this WAF configuration on multiple resources. You can only configure an ALB to use one WAF ACL at one time.

Terminology

ACL ($5/mo)

The ACL contains all of the rules you want to apply to your web application. Your web application will only need one ACL. If you have multiple web applications and you need different WAF rules for each application, plan on creating a separate ACL for each application.

Rules ($1/mo each)

You can manually create a variety of rules to block or allow certain situations. For example, we might choose to always allow traffic from a specific IP with a request header for a specific target hostname. This is helpful when fine-tuning the managed rules provided by AWS, we have had to create very few of our own rules.

You can place one or more rules into a rule group. This step is optional.

Marketplace Rule Groups ($/mo price varies)

You can purchase managed rule groups from various security vendors through the AWS marketplace. Pricing for a managed rule groups is typically $10-20/month regardless of how many rules the vendor places in their rule group. Vendors offered these managed rule groups before AWS began offering their own managed rule groups (below). We do not currently use any marketplace rule groups.

Some customers complain that the AWS managed rules generate too many false-positives (e.g. blocking legitimate users) for their applications, so they prefer the marketplace rules. You will have to do your own research and decide if the marketplace rules are worth the additional cost compared to the AWS managed rules for your application.

AWS Managed Rule Groups ($1/mo each)

When AWS released WAF v2, AWS announced AWS Managed Rules. Like marketplace rule groups (above), AWS managed rule groups cost a flat fee ($1/mo per group) regardless of how many rules the group may actually contain. If you are new to WAF, I would recommend starting with these rule groups!

Configuring WAF for Laravel

Each ACL has a capacity for up to 1500 compute units. Each individual rule requires a few units. Each AWS managed rule group requires a pre-defined number of compute units. If you are just getting started with AWS WAF and you are wanting to protect a Laravel PHP application on Linux servers, you might consider adding the following AWS managed rule groups to your ACL:

  1. Core rule set (700 units)
  2. Known bad inputs (200 units)
  3. Linux operating system (200 units)
  4. PHP application (100 units)
  5. POSIX operating system (100 units)

Pricing: If you add these 5 rule groups to a single WAF ACL, your total cost would be $10/mo. We calculate this by adding $5/mo for the ACL plus $5/mo for these 5 rule groups ($1/mo per rule group).

Capacity: These 5 rule groups would consume a total of 1300 units of your WAF’s total capacity of 1500 units, leaving some headroom for an additional managed ruleset or your own manual rules.

Instructions

  1. Create your first WAF ACL (e.g. “application-name-waf”)
  2. Go to the ACL “Rules” tab, choose “Add Rules”, then choose “Add managed rule groups”
    1. Expand “AWS managed rule groups”
    2. Toggle “Add to web ACL” next to each AWS managed rule group you would like to use
    3. IMPORTANT: Click the “Edit” button below each toggle, then enable “Set all rule actions to count” for the rule group. When we first enable WAF on our application, this option will prevent the rule from actually blocking any traffic and will allow us to figure out of legitimate traffic will be blocked BEFORE we enable blocking (see below).
    4. Repeat prior 2 steps for each AWS managed rule group you would like to use.
  3. Go to the ACL “Associated AWS Resources” tab, then choose “Add AWS resources”
    1. Assuming your application is running behind an AWS ALB, you would choose “Application Load Balancer”, then you would choose your application’s ALB (e.g. “application-name-alb”), then you would click “Add”. You could also configure an API Gateway or AppSync on this page.
  4. Go to the ACL “Logging and Metrics” tab, click “Edit” next to logging.
    1. Configure your “Logging destination” as “CloudWatch Logs log group”. You will need to create a new Log Group with a name that begins with “aws-waf-logs-“.
      1. Log Group TIP: I suggest the full Log Group name use the required prefix plus your WAF ACL name (e.g. “aws-waf-logs-application-name-waf”) so that your WAF ACL name corresponds with your Log Group name.
      2. Log Group TIP: When creating your log group in CloudWatch under Log Groups, be sure to configure a Retention Period for the group. 30 days is probably fine for most people. We have a longer retention period so we can go back further to look at trends, but please be aware that you have to pay a small amount for CloudWatch log storage ($0.03/mo per GB). If your web application receives a LOT of traffic, keep an eye on the “Stored bytes” value on your Log Group page in CloudWatch and adjust retention as necessary.
      3. Redacted fields are optional. Skip for now.
      4. Filter logs are optional. Skip for now.
      5. Click “Save”
      6. Note: If you are prompted for the names of your CloudWatch metrics, you can accept the default names (e.g. “AWS-AWSManagedRulesCommonRuleSet”, etc)

Congrats! At this point, your new AWS WAF is filtering traffic for your Laravel PHP web application running behind AWS ALB.

Monitoring WAF Logs for Blocked Traffic

You have configured your first WAF ACL with a few AWS managed rules with each rule action set to count (instead of block) and you have applied the WAF ACL to your ALB. At this point, you should log a few days or weeks of usage, then review all of the traffic that would have been blocked by your new WAF ACL.

Viewing WAF Activity Graph in the WAF Console

Open the AWS WAF console and drill-down to your new WAF ACL. The “Overview” tab for the ACL will show a graph of all requests for the selected period (e.g. 1 hour, 3 hours, 12 hours, 1 day, etc).

If the “ALL AllowedRequests” metric and the “application-name-waf AllowedRequests” metric always show the exact same count, your WAF ACL is not detecting any requests that need to be blocked. Make sure you added one or more AWS managed log groups in the Rules section of your WAF ACL. Otherwise, we may need to wait for more traffic before analyzing your logs.

If you see any “Blocked” metrics, we need to make sure “count” setting is enabled for each rule group. Go to your WAF ACL, go to WAF Rules, click “Add Rules”, click “Add managed rule groups”, expand “AWS Managed Rule Groups”, find each group where “Add to web ACL” is enabled, and click “Edit”, then make sure “Set all rule actions to count” is enabled.

If the “ALL CountedRequests” metric ever shows a non-zero count, we will need to investigate further. At this point, we use the CloudWatch console to find/review the individual WAF ACL requests that were counted.

Viewing WAF Activity Logs in the CloudWatch Console

Open the AWS CloudWatch console, go to Log Groups, and drill-down to your new WAF log group (e.g. “aws-waf-logs-application-name-waf”), go to Log Streams, click Search all Log Streams. This will allow you to search individual requests for specific activity. If you leave the search input blank and choose a time filter option (e.g. 30 minutes), you will see all individual requests processed by WAF during that time period.

While you may not normally need to look at the individual requests, you should open/expand at least one log entry and become familiar with the payload of the logged request before you begin reviewing the “counted” requests that your new WAF ACL would have normally blocked.

Reviewing WAF Counted Requests in the CloudWatch Console

Open the AWS CloudWatch console, go to Logs Insights, select a Log Period (e.g. 12 hours), select your Log Group (e.g. “aws-waf-logs-application-name-waf”), then run the following query:

fields @timestamp
| filter (@message like 'excludedRules":[{"exclusionType":"EXCLUDED_AS_COUNT","ruleId":' and @message like 'terminatingRuleId":"Default_Action"')
| parse @message '"name":"Host","value":""' as headersHost 
| parse @message '"name":"host","value":""' as headersHostLower
| fields coalesce(headersHost, headersHostLower) as targetHost
| parse @message '"ruleId":"*"' as ruleName
| display @timestamp, httpRequest.clientIp, httpRequest.country, httpRequest.httpVersion, targetHost, httpRequest.uri, ruleName, httpRequest.requestId
|limit 100

This query will filter all requests for rules that were excluded as a count action (e.g. “exclusionType” equals “EXCLUDED_AS_COUNT”) and where the terminating rule was the default action (e.g. “terminatingRuleId” equals “Default_Action”).

This query will parse the host value from the HTTP request headers (as “targetHost”) and will also parse the Rule ID that was counted (as “ruleName”)

Your query should look similar to this:

Your query results should look similar to this:

False Positives

Each query result above would have been a blocked request if you had not enabled the “count” action on each of your AWS managed rule groups. You should review all of your query results and determine if any counted requests were legitimate traffic.

If the AWS managed rules are blocking legitimate traffic, you will have to do one of the following:

  1. Create a manual rule that allows the specific traffic condition that is being blocked
  2. Modify your application so that the path or variable or content that is being blocked no longer triggers the rule. For example, you may be able to rename a variable name that is triggering a rule.
  3. Disable a portion of the AWS managed rule group or the entire AWS managed rule group.

In our case, a 3rd party integration uses XML payloads to transmit data to an older API so we created a custom rule that allowed that specific traffic. This one custom rule increased our AWS WAF cost by $1/mo. After saving the rule, WAF allowed us to configure the priority of the rule so that our custom rule was applied before any of the AWS managed rule groups.

Blocking Requests

After you have reviewed your counted requests and have eliminated false positives, you are ready to configure AWS WAF to begin blocking requests!

  1. Open the AWS WAF Console
  2. Go to your WAF ACL
  3. Go to WAF Rules
  4. Click “Add Rules”
  5. Click “Add managed rule groups”
  6. Expand “AWS Managed Rule Groups”
    1. Find each group where “Add to web ACL” is enabled
    2. Click “Edit”
    3. Disable “Set all rule actions to count”
    4. Repeat steps above for each group where “Add to web ACL” is enabled

Congrats! Your new WAF ACL is now protecting your Laravel PHP web application.

Did you find this helpful? Let me know by sending me a comment. I tend to update and maintain posts more frequently if I know others find them helpful. Thanks for visiting!