• Simple AWS
  • Posts
  • Using AWS WAF to Improve Application Security

Using AWS WAF to Improve Application Security

Setting up AWS Web Application Firewall to protect from Cross-Site Scripting, OS command execution and SQL injections, without modifying the application code.

You've deployed the most modern and sophisticated insecure web application: OWASP Juice Shop. It's an app made intentionally insecure, and it contains a vast number of hacking challenges of varying difficulty where the user is supposed to exploit the underlying vulnerabilities.

We're not going to hack it though, we're going to secure it! (at least partially). However, we won't touch the application's code.

Services

  • CloudFront: A Content Delivery Network (CDN) that caches content in edge locations distributed around the world and serves it to users with less latency than sending a request to your servers.

  • Web Application Firewall (WAF): A web application firewall that lets you monitor HTTP and HTTPS requests sent to your web resources, and take actions based on certain parameters. It integrates with CloudFront.

Solution: Set up Web Application Firewall (WAF) to block requests with exploits

To follow along the steps of this article, you'll first need to deploy the initial setup. You can find the code here, and you can deploy it to AWS with the following button:

Step 0: Test the app

First we'll make sure the exploits actually work! If your code already protects you from these, it's still a good idea to add these WAF rules, so you have another layer of protection (defense in depth!). But if the exploits didn't work on this tutorial, it would be a really bad tutorial, wouldn't it?

  1. In the AWS console, open CloudShell

  2. Set the URL of your CloudFront distribution as an env var

    1. First, run echo $JUICESHOP_URL to see if it's already set up. If it is, that's it.

    2. If it's not set up, go to the initial stack you deployed in CloudFormation, and from the Outputs tab copy the value for JuiceShopURL

    3. Run JUICESHOP_URL=[the value you just copied], replacing [the value you just copied] with the value you just copied, which looks something like http://d1486tu9ui8f00.cloudfront.net

  3. Run the following command, which sends a request where query arguments contain system file extensions that are unsafe to read or run:

    curl -I $JUICESHOP_URL?execute=http://evilhackerz.com/file.ini

  4. Verify that the response is 200 OK

  5. Run the following command, which sends a request with a Cross-Site Scripting attack:

    curl -X POST $JUICESHOP_URL -F "user=''"

  6. Verify that the response is 200 OK (it should be an HTML with <title>OWASP Juice Shop</title>)

  7. Run the following command, which attempts an SQL injection:
    curl -X POST $JUICESHOP_URL -F "user='AND 1=1;"

  8. Verify that the response is 200 OK (it should be an HTML with <title>OWASP Juice Shop</title>)

  9. Run the following command, which attempts a more complex SQL injection:
    curl -X POST $JUICESHOP_URL -F "1094 and 3=substirng(version(),1,1)"

  10. Verify that the response is 200 OK (it should be an HTML with <title>OWASP Juice Shop</title>)

Step 1: Create a WAF Web ACL

A Web ACL doesn't actually protect anything by itself. It's the engine that runs everything that WAF does, and it's the core resource of WAF. In this step we're just creating it, and in the next steps we'll add the actual protections: the rules.

  1. Go to the AWS WAF Console

  2. Click Create web ACL

  3. For name and description, enter simple-aws-waf

  4. Set the Resource type as Amazon CloudFront distributions

  5. Under Associated AWS resources, click Add AWS resources

  6. Choose the CloudFront distribution that was created by the initial setup, and click Add

  7. Click Next

  8. Click Next

  9. On the Configure metrics page, under Request sampling options, select Enable sampled requests

  10. Click Next

  11. Click create web ACL

Step 2: Enable WAF Logging

If a tree falls in a forest and no one is around to hear it, does it make a sound? It actually does, because of conservation of energy. But in this case we want to know what WAF is doing, that's what the logs are for.

  1. Click the web ACL you just created and open the Logging and metrics tab

  2. In the Logging section, click Enable

  3. For Logging destination, make sure CloudWatch Logs log group is selected

  4. For the log group, click Create new

  5. For the log group name, enter aws-waf-logs-simple-aws-waf

  6. Leave the other settings at default and click Create

  7. Go back to the browser tab with the WAF console, refresh the log group list and select the log group you just created

  8. Click Save

  9. In the Sampled requests section, click Edit

  10. Select Enable sampled requests

  11. Click Save

Step 3: Add AWS managed rules groups to the web ACL

This is where we actually protect our app! Rule groups are sets of rules that WAF will evaluate for every request. Rules have statements such as "If the request contains header X" and actions that can be Allow, Block, Count or CAPTCHA and Challenge. Requests are evaluated against all rule statements to see if they match, and if there's a match the rule action is applied. Rules are grouped in Rule groups, to make them more manageable.

Managed rule groups are rule groups already created by AWS, with rules that will protect you from some common exploits such as Cross-Site Scripting (XSS). In this step we're enabling a couple of them, that will protect us from the first 2 exploits.

  1. Go to the WAF console and select your web ACL

  2. Click the Rules tab

  3. In the Rules section, click Add rules, then click Add managed rule groups

  4. Expand the listing for the AWS managed rule groups

  5. In the Action column, turn on the Add to web ACL toggle for Core Rule Set and SQL Database

  6. Click Add rules, then click Save

Step 4: Test the rules!

We have our rules in place, now it's time to test them. Let's re-run the first two exploits, and see if the requests are blocked by WAF.

  1. In the AWS console, open CloudShell

  2. Set the URL of your CloudFront distribution as an env var

    1. First, run echo $JUICESHOP_URL to see if it's already set up. If it is, that's it.

    2. If it's not set up, go to the initial stack you deployed in CloudFormation, and from the Outputs tab copy the value for JuiceShopURL

    3. Run JUICESHOP_URL=[the value you just copied], replacing [the value you just copied] with the value you just copied, which looks something like http://d1486tu9ui8f00.cloudfront.net

  3. Run the following command, which sends a request where query arguments contain system file extensions that are unsafe to read or run:

    curl -I $JUICESHOP_URL?execute=http://evilhackerz.com/file.ini

  4. Verify that the response is 403 Forbidden

  5. Run the following command, which sends a request with a Cross-Site Scripting attack:

    curl -X POST $JUICESHOP_URL -F "user=''"

  6. Verify that the response is 403 Forbidden (it should be an HTML with <title>ERROR: The request could not be satisfied</title>)

Step 5: Add a rule to block SQL injections

In this step we're adding a custom rule that will protect us from SQL injections. We're not actually writing the statement, and instead using a match already predefined by AWS: Contains SQL injection attacks. We are choosing all the other options however.

  1. Go to the WAF console and select your web ACL

  2. Click the Rules tab

  3. In the Rules section, click Add rules and click Add my own rules and rule groups

  4. For the rule name, enter block-sql-injection

  5. In the If a request dropdown, make sure matches the statement is selected

  6. Under Statement:

    1. For Inspect, select Body

    2. For Match type, select Contains SQL injection attacks (it's a searchable field)

    3. For Oversize handling, select Continue

    4. For Sensitivity level, select High

  7. Click Add Rule

  8. Click Save

Step 6: Test the SQL injection rule

With the new rule in place, we re-run the last two exploits, which were SQL injections of different complexity. They should both be blocked.

  1. In the AWS console, open CloudShell

  2. Set the URL of your CloudFront distribution as an env var

    1. First, run echo $JUICESHOP_URL to see if it's already set up. If it is, that's it.

    2. If it's not set up, go to the initial stack you deployed in CloudFormation, and from the Outputs tab copy the value for JuiceShopURL

    3. Run JUICESHOP_URL=[the value you just copied], replacing [the value you just copied] with the value you just copied, which looks something like http://d1486tu9ui8f00.cloudfront.net

  3. Run the following command, which attempts an SQL injection:
    curl -X POST $JUICESHOP_URL -F "user='AND 1=1;"

  4. Verify that the response is 403 ERROR (it should be an HTML with <title>ERROR: The request could not be satisfied</title>)

  5. Run the following command, which attempts a more complex SQL injection:
    curl -X POST $JUICESHOP_URL -F "1094 and 3=substirng(version(),1,1)"

  6. Verify that the response is 403 ERROR (it should be an HTML with <title>ERROR: The request could not be satisfied</title>)

In the previous step I told you to select High for Sensitivity level. If you had selected Low, the first of these two exploits (a really simple SQL injection) would have been blocked, but the last one (a more complex SQL injection) wouldn't have been caught. A High Sensitivity level can also cause false positives though, so test it for yourself.

Step 7: View the WAF logs in CloudWatch Logs

We've already seen WAF in action from the attacker's point of view. But in a real scenario you're probably not going to be the one attacking. From your point of view, the information you have are the logs.

  1. Go to the WAF console and select your web ACL

  2. Click the CloudWatch Logs Insights tab

  3. Click Run query

  4. Under the Logs tab that appeared below, open the log entries and take a couple of seconds to read them

  5. Briefly consider how much of a pain it would be to read these logs!

  6. In the text area above the Run query button, delete everything and paste the following:
    fields httpRequest.clientIp as ClientIP, httpRequest.country as Country, httpRequest.uri as URI, terminatingRuleId as Rule

    | filter action = "BLOCK"

    | stats count(*) as RequestCount by Country, ClientIP, URI, Rule

    | sort RequestCount desc

  7. Click Run query again

  8. Now check the logs! Much better, right?

Bonus: You can do aggregate queries! Here's a list of useful queries that you can copy.

WAF Pricing

WAF's pricing is actually pretty complex, and you'll find 9 different examples in the pricing page. Here are the basics though:

  • As a base, you're charged $5/month per Web ACL

  • Plus $1/month per rule group (managed or created by you)

  • Plus $1/month per custom rule (either in a rule group or not)

  • Plus $0.60 per 1 million requests (up to 16KB body, up to 1500 WCUs)

  • Plus $0.30 per million requests for each additional 16KB

  • Plus $0.20 per million requests for each additional 500 WCUs

WCUs are Web ACL Capacity Units. When you create a rule, WAF calculates how many WCUs it consumes depending on the complexity of the rule. For rule groups, when you create them you set the WCU capacity (can't change it later), and then the sum of the WCUs of all the rules you add can't exceed that rule group's WCU capacity. When you assign rules and rule groups to a Web ACL, the WCUs consumed by the Web ACL is the sum of the WCUs of all rules associated directly, plus the sum of the WCU capacity of all rule groups associated with the Web ACL.

For reference, the 1500 WCUs included in the base $5/month price is usually a lot. The max WCUs a Web ACL can have is 5000 (can be increased by contacting support)

Regarding the body, you can decide what rules do when the body exceeds 16 KB. That's the Continue you set for Oversize handling in Step 5.

I wrote a more detailed article on WAF Pricing, which you might want to check out.

By the way, you can purchase WAF rule groups in the AWS Marketplace!

Best Practices for AWS WAF

Operational Excellence

  • Implement Continuous Monitoring and Alerting: Set up CloudWatch alarms to monitor WAF metrics, such as the number of blocked requests and rule matches. Configure automated notifications to get alerted when potential security threats are detected. This helps you proactively respond to and mitigate security risks.

  • Enable WAF Logging: We already did this =). We also analyzed the logs with CloudWatch Logs Insights, but you can also use Athena or Elasticsearch to better understand potential attack patterns and fine-tune the WAF rules accordingly.

Security

  • Regularly Update WAF Rules: Security is a race to patch the latest exploits. Stay up-to-date with the latest threats and update your WAF rules. Managed rule groups are automatically updated, but if you're a security pro, you probably want to stay on top of this.

Reliability

  • Test and Validate WAF Rules: Of the 7 steps we had in our step-by-step, 3 were testing! Set up the rules, then test them, then test them periodically, then learn about new threats and test them again!

Performance Efficiency

This time I've got nothing for you here.

Cost Optimization

  • Use Managed Rule Groups: You're billed $1/month per custom rule, and just $1/month per an entire managed rule group. Solve what you can with managed rule groups! Keep an eye on WCUs though.

AWS WAF Resources

The Guidelines for Implementing AWS WAFĀ by AWS outlines recommendations for implementing AWS WAF to protect existing and new web applications. Great read before you mess up your app's security! And probably a great read if you've already messed it up.

Did you like this issue?

Login or Subscribe to participate in polls.

Join the conversation

or to participate.