Protect your webapps from malicious traffic with AWS Web Application Firewall

Table of contents

Introduction

As a rapidly increasing amount of companies are moving workloads to the cloud and extending their footprint through refactoring and modernization, the possible attack vectors are increasingly expanding. According to DataDogs State of Cloud Security Report, a substantial portion of cloud workloads are excessively privileged [FACT 5] and many virtual machines are publicly exposed to the internet [FACT 6].

In AWS, only a small number (1.5 percent) of Amazon EC2 instances have full administrator privileges. Overall, nearly one in four EC2 instances (23 percent) have administrator or highly sensitive permissions to the AWS account they run in. An attacker does not need full administrator privileges to have a substantial impact—there are other, more common and challenging-to-uncover types of permissions they can leverage. We found that:

  • 5.4 percent of EC2 instances have risky permissions that allow lateral movement in the account, such as connecting to other instances using SSM Sessions Manager.
  • 7.2 percent allow an attacker to gain full administrative access to the account by privilege escalation, such as permissions to create a new IAM user with administrator privileges.
  • 20 percent have excessive data access, such as listing and accessing data from all S3 buckets in the account.

    (Note that these conditions are not mutually exclusive—a specific instance can fall into several of these categories.)
FACT 5: A substantial portion of cloud workloads are excessively privileged – DataDog State of Cloud Security

7 percent of EC2 instances3 percent of Azure VMs, and 13 percent of Google Cloud VMs are publicly exposed to the internet. Among instances that are publicly exposed, HTTP and HTTPS are the most commonly exposed ports, and are not considered risky in general. After these, SSH and RDP remote access protocols are common.

FACT 6: Many virtual machines remain publicly exposed to the internet – DataDog State of Cloud Security

Scanning of public internet resources is happening all the time while malicious actors are becoming more and more sophisticated. Companies can have a tight perimeter for employee IAM-credentials with access to the AWS Console/CLI, but still have the back door wide open with unsecured web applications.

In the Security Pillar of the AWS Well-Architected Framework a key design principle is to apply security at all layers with a defense in depth approach for edge of network, VPC, load balancing, instance/compute, operating system, application and code.

The Web Application Firewall concept

You may be familiar with traditional firewalls that function on Layer 3/4 by defining rules for protocols and port ranges. As the HTTP protocol is a Layer 7 construct we need something a bit more advanced to inspect, monitor, filter/block unwanted requests to and from a web service.

A Web Application Firewall can help by preventing attacks exploiting a web application’s known vulnerabilities. The OWASP Top Ten list defines the most common attack vectors and is updated on a regular basis:

  1. Broken Access Control
    • Violation of principle of least privilege/elevation of privilege
    • Bypassing access control checks/viewing someone else’s account
    • Lack of multi-factor authentication
  2. Cryptographic Failures
    • Lack of proper encryption at rest/in transit
    • Old or weak cryptographic algorithms (not modern TLS)
  3. Injection
    • Lack of proper input validation/sanitation
    • SQL injection
    • Cross-site scripting (XSS)
    • OS command, ORM, LDAP injection
  4. Insecure Design
  5. Security Misconfiguration
  6. Vulnerable and Outdated Components
    • Operating system, web/application server, DBMS etc.
  7. Identification and Authentication Failures
  8. Brute force or automated attacks
  9. Software and Data Integrity Failures
  10. Security Logging and Monitoring Failures
    • Insufficient audit logging
  11. Server-Side Request Forgery

One caveat with WAF is, that depending on the deployment model, it may be resource intensive. All sorts of inspection and filtering generates CPU load, which in a traditional datacenter environment means the web server has less resource capacity for serving legitimate traffic, leading to performance degradation, especially while under heavy load or attack.

Deployment options

A number of Web Application Firewall solutions are available, each with it’s pros and cons.

Option #1 – Application layer

The first option describes how to implement WAF like capabilities either in your own code base or including a framework component such as ShieldON for PHP. Although it may seem handy this has directly integrated with your application code and since it’s deployed on the same compute option (VM or container) this option comes with the most severe performance impact.

Option #2 – Webserver module

By moving up one level in the stack and configuring WAF as a module in your web server of choice you can decouple from your application code base.

ModSecurity is a traditional option for Apache and Nginx web servers. If deployed on the same instance both your application and the WAF would compete for CPU resources. In some situations WAF can be extracted and deployed as a separate proxy tier, as further described below.

Option #3 – Virtual appliance

In this model the WAF component is separated from the application layer so that it can be managed and scaled independently. The main advantage is that malicious traffic can be blocked before reaching the application compute resources, ensuring maximum performance for legitimate requests. This also opens up the possibility to share the WAF component across application workloads in the same region.

AWS Marketplace has multiple options for Cloud WAF-as-a-Service virtual appliances and most premium solutions can be integrated with existing enterprise management, logging and reporting tools. Some also deploy HAproxy or Nginx with WAF like capabilities like the aforementioned ModSecurity.

Option #4 – AWS native service – Web Application Firewall

Our fourth option is AWS Web Application Firewall, a fully managed service. With this option you pay no license fees, only for what you use, and the WAF component can also be scaled and managed as with option #3, decoupled from the application layer. Some of the main benefits are that AWS WAF is tightly integrated with AWS services such as Amazon Cloudfront, AWS Application Load Balancer, AWS API Gateway, AWS Appsync and AWS Shield for DDoS protection. It’s relatively straightforward to set up and deploy and builders already familiar with AWS won’t have to learn something new (or relate to a 3rd party provider with possibly sub-optimal licensing agreements).

AWS WAF also supports Bot Control that provides visibility and control over common and pervasive bot traffic that can consume excess resources or lead to downtime and Fraud Control which can protect login and sign-up pages against attacks such as credential stuffing, credential cracking and fake account creation.

Relevant rules are configured in ordered priority, like traditional firewall rules, and you can choose from ALLOW, BLOCK or COUNT actions to achieve the desired behaviour.

  • ALLOW all requests except the ones that you prefer
  • BLOCK all requests except the ones that you prefer
  • COUNT requests that match certain criterias
  • CAPTCHA or challenge checks against requests that match certain criterias
AWS native service – WAF – Regional deployment

In this scenario AWS WAF is configured for Application Load Balancers, Amazon API Gateways or AWS AppSync at the regional level.

AWS Shield L3+L4 standard protection is included without additional charges, but AWS Shield Advanced (L7 protection) is not supported.

AWS native service – WAF – Global edge network

In this scenario AWS WAF is configured for Amazon Cloudfront at the global edge network level.

Mitigation of large scale attacks is most efficient the further “out” you get, because global network capacity combined is larger than regional capacity, so moving from a regional perimeter to the AWS global edge network is highly recommended. By adopting WAF with Cloudfront you can get full AWS Shield DDoS protection (Standard for L3/4 or Advanced for L3/4+7 for mission critical workloads) and provide AWS the optimal preconditions for mitigation. Malicious requests can be blocked even before it reaches the region.

Introduction to how AWS WAF works

  1. Define a Web Access Control List (Web ACL) configured to protect a set of AWS resources (such as Amazon Cloudfront or AWS Application Load Balancer).
  2. Specify your desired actions as rule statements. These can be custom and specified by you, managed by the AWS Threat Research Team or a 3rd party vendor from the AWS Marketplace.
    • Each rule consists of a condition and an action. Example: if request origin country is this value, then BLOCK the request.
    • A rule can be rate based, IP allow/deny, geoblocking at country level and so on.
  3. Organize re-usable rules in Rule Groups that can be attached to multiple WebACLs.

Rules are evaluated from the lowest numeric priority (1) and up until rule match that terminates the evaluation, or all rules are evaluated without match.

To calculate the complexity and evaluation of the total combination of rules and rule groups, each Web ACL has a concept called Web Capacity Units (WCU) which is limited to maximum 5000 WCUs per Web ACL or Rule Group.

If your company has subscribed to AWS Shield Advanced, the service will add an additional Rule Group managed by the AWS Shield Response Team for tailored mitigation.

Here is an overview of some of the Managed Rule Groups available for configuration in the AWS Console:

AWS Managed rule groups

AWS WAF Traffic dashboard insight

Having visibility into incoming traffic is paramount for successful mitigation and to ensure valid traffic is not impacted by mistake. AWS WAF WebACL logs can be configured to be shipped to an Amazon CloudWatch Logs log group or an Amazon S3 bucket which enables easy querying with CloudWatch Logs Insights and/or Amazon Athena. In addition an Amazon Data Firehouse delivery stream destination can also be set up for further processing or shipping to a 3rd party log analysis solution such as Splunk.

AWS Web Application Firewall is of course set up for my blog so I have the opportunity to share some recent insights from the last seven days since publication of this post.

The graph below illustrates the distribution between Allowed and Blocked requests. At this point in time I have no Count actions configured.

The graph below illustrates the types of attacks identified in the requests. The majority is of type NoUserAgent and there are just a few BadBots.

The graph below illustrates the ten most common rule labes added to incoming requests.

In addition to useful graphs on the Traffic overview tab you can also easily query the WAF logs directly from the CloudWatch Log Insights tab.

Here is a sample of some of the information available in the request logs which you can base your rule logic on, with some details redacted or modified. For full description of all available log fields see AWS WAF Developer Guide – Log fields.

We can see that action: ALLOW and terminatingRuleId: Default_Action, so the request was permitted and passed on to the backend.

action: ALLOW
httpRequest.clientIp: 123.45.67.809
httpRequest.country: NO
httpRequest.headers.10.value: https://hedrange.com/
httpRequest.headers.4.value: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0
httpRequest.uri: /wp-content/uploads/2023/10/2023-10-oidc-feat.png
ruleGroupList.0.ruleGroupId: AWS#AWSManagedRulesAmazonIpReputationList
ruleGroupList.1.ruleGroupId: AWS#AWSManagedRulesWordPressRuleSet
ruleGroupList.2.ruleGroupId: AWS#AWSManagedRulesCommonRuleSet
terminatingRuleId: Default_Action
terminatingRuleType: REGULAR

The CloudWatch Logs query below searches the selected CloudWatch Logs Groups containing the WAF logs for country, action, URI and terminating rule ID for BLOCKed requests during the last week, limited to 50 results:

  fields httpRequest.country, action, httpRequest.uri, terminatingRuleId
| filter action = "BLOCK"
| sort @timestamp desc
| limit 50

httpRequest.countryactionhttpRequest.uriterminatingRuleId
UABLOCK/archivarix.cms.phpAWS-AWSManagedRulesAmazonIpReputationList
FRBLOCK/AWS-AWSManagedRulesCommonRuleSet
USBLOCK/AWS-AWSManagedRulesAmazonIpReputationList
UABLOCK/wp-content/themes/sketch/404.phpAWS-AWSManagedRulesCommonRuleSet
USBLOCK/AWS-AWSManagedRulesCommonRuleSet
USBLOCK/AWS-AWSManagedRulesCommonRuleSet
USBLOCK/AWS-AWSManagedRulesCommonRuleSet
PHBLOCK/xmlrpc.phpAWS-AWSManagedRulesWordPressRuleSet
PHBLOCK/xmlrpc.phpAWS-AWSManagedRulesWordPressRuleSet
USBLOCK/xmlrpc.phpAWS-AWSManagedRulesWordPressRuleSet
USBLOCK/xmlrpc.phpAWS-AWSManagedRulesWordPressRuleSet
USBLOCK/xmlrpc.phpAWS-AWSManagedRulesWordPressRuleSet
USBLOCK/xmlrpc.phpAWS-AWSManagedRulesWordPressRuleSet
USBLOCK/AWS-AWSManagedRulesCommonRuleSet
USBLOCK/AWS-AWSManagedRulesCommonRuleSet
USBLOCK/AWS-AWSManagedRulesCommonRuleSet
CABLOCK/robots.txtAWS-AWSManagedRulesCommonRuleSet
USBLOCK/AWS-AWSManagedRulesCommonRuleSet
CNBLOCK/xmlrpc.phpAWS-AWSManagedRulesWordPressRuleSet
USBLOCK/AWS-AWSManagedRulesCommonRuleSet
IEBLOCK/wp-content/plugins/index.phpAWS-AWSManagedRulesCommonRuleSet
IEBLOCK/css/sgd.phpAWS-AWSManagedRulesCommonRuleSet
IEBLOCK/revision.phpAWS-AWSManagedRulesCommonRuleSet
IEBLOCK/.well-known/admin.phpAWS-AWSManagedRulesCommonRuleSet
IEBLOCK/wp-content/install.phpAWS-AWSManagedRulesCommonRuleSet
IEBLOCK/wp-includes/Requests/dropdown.phpAWS-AWSManagedRulesCommonRuleSet
IEBLOCK/wp-includes/pomo/fgertreyersd.php.suspectedAWS-AWSManagedRulesCommonRuleSet
IEBLOCK/wp-includes/sts.phpAWS-AWSManagedRulesCommonRuleSet
IEBLOCK/google.phpAWS-AWSManagedRulesCommonRuleSet
IEBLOCK/wp-content/uploads/error_log.phpAWS-AWSManagedRulesCommonRuleSet
IEBLOCK/db.phpAWS-AWSManagedRulesCommonRuleSet
IEBLOCK/wp-includes/pomo/wp-login.phpAWS-AWSManagedRulesCommonRuleSet
IEBLOCK/wp-admin/js/privacy-tools.min.phpAWS-AWSManagedRulesCommonRuleSet
IEBLOCK/autoload_classmap.phpAWS-AWSManagedRulesCommonRuleSet
IEBLOCK/link.phpAWS-AWSManagedRulesCommonRuleSet
IEBLOCK/ws.phpAWS-AWSManagedRulesCommonRuleSet
IEBLOCK/doc.phpAWS-AWSManagedRulesCommonRuleSet
IEBLOCK/wp-admin/js/widgets/cong.phpAWS-AWSManagedRulesCommonRuleSet
IEBLOCK/wp-includes/rest-api/endpoints/html.phpAWS-AWSManagedRulesCommonRuleSet
IEBLOCK/wp-content/uploads/wp-login.php.suspectedAWS-AWSManagedRulesCommonRuleSet
IEBLOCK/01.phpAWS-AWSManagedRulesCommonRuleSet
IEBLOCK/wp-content/uploads/cong.phpAWS-AWSManagedRulesCommonRuleSet
IEBLOCK/.well-known//.well-known/owlmailer.phpAWS-AWSManagedRulesCommonRuleSet
IEBLOCK/wp-includes/js/tinymce/skins/wordpress/images/index.phpAWS-AWSManagedRulesCommonRuleSet
IEBLOCK/worm0.PhP7AWS-AWSManagedRulesCommonRuleSet
IEBLOCK/user.phpAWS-AWSManagedRulesCommonRuleSet
IEBLOCK/edit.phpAWS-AWSManagedRulesCommonRuleSet
IEBLOCK/wp-includes/js/tinymce/skins/lightgray/img/index.phpAWS-AWSManagedRulesCommonRuleSet
IEBLOCK/options.phpAWS-AWSManagedRulesCommonRuleSet

As we can see there are many good examples here of potential malicious URIs which are blocked before reaching the application – even though the resources does not exist on the web server in question, this is just random HTTP request guessing from malicious actors.

AWS WAF provisioning with Terraform

For demonstration reference is made to my blog post Develop lightweight and secure REST APIs with AWS Lambda Function URL and Terraform which includes Terraform resource configurations for AWS WAF configured for Amazon Cloudfront.

  1. As a first step we define a WAFv2 WebACL with a default action of ALLOW.
  2. The first rule is the AWS Managed Rule AWSManagedRulesAmazonIpReputationList (WCU: 25) with priority 1.
    • The Amazon IP reputation list rule group contains rules that are based on Amazon internal threat intelligence. This is useful if you would like to block IP addresses typically associated with bots or other threats. Blocking these IP addresses can help mitigate bots and reduce the risk of a malicious actor discovering a vulnerable application (reference).
  3. The second rule is the AWS Managed Rule AWSManagedRulesWordPressRuleSet (WCU: 100) with priority 2.
    • The WordPress application rule group contains rules that block request patterns associated with the exploitation of vulnerabilities specific to WordPress sites. You should evaluate this rule group if you are running WordPress. This rule group should be used in conjunction with the SQL database and PHP application rule groups (reference).
  4. The third rule is the AWS Managed Rule AWSManagedRulesKnownBadInputsRuleSet (WCU: 200) with priority 3.
    • 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 (reference).
  5. The fourth rule is the AWS Managed Rule “AWSManagedRulesCommonRuleSet” (WCU: 700) with priority 4.
    • 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. Consider using this rule group for any AWS WAF use case (reference).
  6. Then we define a Cloudfront distribution.
  7. Configure AWS WAF WebACL for Cloudfront.

# Step #1 - Create a Web ACL
resource "aws_wafv2_web_acl" "lambda_function_url_demo" {
  #checkov:skip=CKV2_AWS_31: WAF2 logging configuration not necessary for this use-case.
  count       = var.provision_cloudfront == true ? 1 : 0
  provider    = aws.us-east-1
  name        = "lambda_function_url_demo"
  description = "Web ACL with managed rule groups for lambda_function_url_demo"
  scope       = "CLOUDFRONT"

  default_action {
    allow {}
  }
# Step 2 - First rule
  rule {
    name     = "AWSManagedRulesAmazonIpReputationList"
    priority = 1

    override_action {
      none {}
    }

    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesAmazonIpReputationList"
        vendor_name = "AWS"
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = false
      metric_name                = "AWSManagedRulesAmazonIpReputationList"
      sampled_requests_enabled   = false
    }
  }
# Step 3 - Second rule
  rule {
    name     = "AWSManagedRulesKnownBadInputsRuleSet"
    priority = 3

    override_action {
      none {}
    }

    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesKnownBadInputsRuleSet"
        vendor_name = "AWS"
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = false
      metric_name                = "AWSManagedRulesKnownBadInputsRuleSet"
      sampled_requests_enabled   = false
    }
  }
# Step 4 - Third rule
  rule {
    name     = "AWSManagedRulesKnownBadInputsRuleSet"
    priority = 3

    override_action {
      none {}
    }

    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesKnownBadInputsRuleSet"
        vendor_name = "AWS"
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = false
      metric_name                = "AWSManagedRulesKnownBadInputsRuleSet"
      sampled_requests_enabled   = false
    }
  }
# Step 5 - Fourth rule  
  rule {
    name     = "AWSManagedRulesCommonRuleSet"
    priority = 4

    override_action {
      none {}
    }

    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesCommonRuleSet"
        vendor_name = "AWS"
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = false
      metric_name                = "AWSManagedRulesCommonRuleSet"
      sampled_requests_enabled   = false
    }
  }
  
  visibility_config {
    cloudwatch_metrics_enabled = true
    metric_name                = "web-acl-lambda-function-url-demo"
    sampled_requests_enabled   = true
  }
}
# Step 6 - Define Cloudfront distribution resource
resource "aws_cloudfront_distribution" "lambda_function_url_demo" {
  #checkov:skip=CKV_AWS_310: Origin failover is not required for this use-case.
  #checkov:skip=CKV2_AWS_42: Custom SSL certificate is not required for this use-case.
  #checkov:skip=CKV2_AWS_32: Response headers policy not required.
  #checxkov:skip=CKV_AWS_68: WAF to come
  #checxkov:skip=CKV_AWS_111: WAF to come
  #checkov:skip=CKV2_AWS_47: WAF to come
  count    = var.provision_cloudfront == true ? 1 : 0
  provider = aws.us-east-1
  origin {
    domain_name              = local.lambda_function_url_demo_domain_name
    origin_access_control_id = aws_cloudfront_origin_access_control.cloudfront_oac_lambda_url[0].id
    origin_id                = local.lambda_function_origin_id

    custom_origin_config {
      http_port                = 80
      https_port               = 443
      origin_protocol_policy   = "https-only"
      origin_ssl_protocols     = ["TLSv1.2"]
      origin_keepalive_timeout = 5
      origin_read_timeout      = 30
    }
  }

  enabled             = true
  is_ipv6_enabled     = true
  default_root_object = "index.html"
  price_class         = "PriceClass_200"

  logging_config {
    include_cookies = false
    bucket          = module.cloudfront_logs[0].s3_bucket_bucket_domain_name
    prefix          = "lambda_function_url_demo"
  }

  default_cache_behavior {
    allowed_methods  = ["HEAD", "DELETE", "POST", "GET", "OPTIONS", "PUT", "PATCH"]
    cached_methods   = ["GET", "HEAD", "OPTIONS"]
    target_origin_id = local.lambda_function_origin_id

    forwarded_values {
      query_string = true

      cookies {
        forward = "none"
      }
    }

    viewer_protocol_policy = "redirect-to-https"
    min_ttl                = 0
    default_ttl            = 0
    max_ttl                = 86400
    compress               = true
  }

  restrictions {
    geo_restriction {
      restriction_type = "none"
      locations        = []
    }
  }

  tags = {
    Name = "LambdaFunctionUrlDemo"
  }

  viewer_certificate {
    cloudfront_default_certificate = true
    minimum_protocol_version       = "TLSv1.2_2018"
  }
# Step 7 - Configure AWS WAF WebACL for Cloudfront
  web_acl_id = aws_wafv2_web_acl.lambda_function_url_demo[0].arn
}

Full sample code including logging configuration is available at https://github.com/haakond/terraform-aws-lambda-function-url/blob/main/waf.tf and https://github.com/haakond/terraform-aws-lambda-function-url/blob/main/cloudfront.tf. Check out the README.md for how to deploy.

AWS WAF pricing

AWS charges 1) per Web ACL, 2) the amount of rules configured and 3) the amount requests processed. The more WCUs your configuration consumes the higher the cost.

More advanced capabilities such as Bot Control and Fraud Control have additional subscription and processing costs.

You can also subscribe to Managed Rules from 3rd party provides from the AWS Marketplace, which will be billed separately.

For full insight and scenario examples study https://aws.amazon.com/waf/pricing/.

Conclusion and recommendations

In this blog post we explored Web Application Firewall as a concept and considered different implementation options. We reviewed AWS WAF as a managed service and explored relevant rules, traffic analysis and logging.

To get up and running with WAF I recommend to start simple and only choose relevant rules applicable for your type of workload; application, operating system and compute option.

Align with your security department about applicable policies and protection mechanisms to adhere to. The more complexity you add to WAF the more intensive traffic analysis will be, which in turn increases the costs.

To reduce rule evaluation (and cost), add the widest and most probable rules (lowest WCU) to be executed first and the most narrow or heavy (highest WCU) ones at last. Basic price for a Web ACL includes up to 1500 WCUs, so try to stay below to avoid extra charges. .

When adding new rules, choose action type COUNT first and observe the WAF logs for a reasonable period of time to ensure valid traffic is not impacted, before switching to BLOCK.

References and additional resources


Posted

in

by