一、概述

什么是 WAF

AWS WAF 是一种 Web 应用程序防火墙,让您能够监控转发到 一个 Amazon CloudFront 分配、一个 Amazon API Gateway REST API 或一个 应用程序负载均衡器的 HTTP(S) 请求。它有助于保护您的 Web 应用程序或 API 免受可能影响可用性、危害安全性或消耗过多资源的常见 Web 攻*击。

使用 WAF 是向 Web 应用程序添加深度防御的一个很好的方法。一个 WAF 可以帮助降低诸如 SQL 注入、跨网站脚本和其他常见的风险。WAF 允许您创建自己的自定义规则,以决定是否在 HTTP 请求到达您的应用程序之前阻止或允许 HTTP 请求。

OWASP Juice Shop

为了测试 WAF 的拦截效果,我们需要准备一个测试站点,目前比较流行的就是 OWASP Juice Shop

OWASP Juice Shop 可能是最现代和最复杂的不安全的网络应用程序!它可以用于安全培训、意识演示、 CTFs,也可以作为安全工具的试验品!Juice Shop 包含了整个 OWASP 十大漏洞以及在现实世界应用程序中发现的许多其他安全漏洞!

安装 Juice Shop

因为我们的测试环境在 AWS,我们直接创建 EC2,在 EC2 上面部署 Juice Shop 站点进行测试。

  1. 在 EC2 界面,选择启动一个实例
  2. 选择一个 AMI,我们这里选择 Amazon Linux 2 AMI
  3. 配置实例的一些属性,然后把下面的启动脚本拷贝到用户数据里面
#!/bin/bash
sudo yum update -y
sudo amazon-linux-extras install docker
sudo service docker start
sudo docker pull bkimminich/juice-shop
sudo docker run -d -p 80:3000 bkimminich/juice-shop
  1. 配置好安全组,允许 80 端口流量进入
  2. 启动实例

创建 ALB

因为 WAF 不能直接添加到 EC2 上面,我们需要给 EC2 创建一个 ALB。

  1. 导航到 Load Balancers 界面,选择创建 Application Load Balancer
  2. 选择面向互联网的,监听 80,选好好 EC2 主机所在的子网
  3. 重新创建一个目标组
  4. 注册机器
  5. 创建完成,访问 ALB 的 public DNS

image-20200815110456079

二、Web ACLs 和 Managed Rules

Web ACLs

Web ACL (Web Access Control List)是 AWS WAF 部署的核心资源。它包含对其接收的每个请求求值的规则。Web ACL 通过 Amazon CloudFront distribution、 AWS API Gateway API 或 AWS Application Load Balancer 与您的 Web 应用程序相关联。

Managed Rules

开始使用 WAF 的最快方法是将 AWS WAF 的 AWS 托管规则组部署到 WebACL。

Managed Rule Groups 是一组规则,由 AWS 或 AWS 市场上的第三方创建和维护。这些规则提供了针对常见类型,或者针对特定的应用程序类型***的保护。

每个托管规则组都可以防范一组常见的,如 SQL 或命令行,下面我们看看在 WAF 中 AWS 提供了哪些托管规则组。

image-20200815103846867

image-20200815103904651

使用托管规则组

这里我们做个演示,在没有 WAF 加持的情况下是什么状况,加上 WAF 托管规则又是什么效果。

测试两种类型的模拟,一种是跨站脚本,还有一种是 SQL 注入***,如下:

export JUICESHOP_URL=<Your Juice Shop URL>
# This imitates a Cross Site Scripting attack
# This request should be blocked.
curl -X POST  $JUICESHOP_URL -F "user='<script><alert>Hello></alert></script>'"
# This imitates a SQL Injection attack
# This request should be blocked.
curl -X POST $JUICESHOP_URL -F "user='AND 1=1;"

我们分别请求一下,看看是否可以***成功:

image-20200815133134879

image-20200815133216930

创建规则

通过测试看到,两种***都成功,下面我们来创建 WAF,并和 ALB 进行关联,然后再进行测试

  1. 打开 AWS WAF Console 界面,选择创建 Web ACLs
  2. 类型选择 Regional,因为我们的 EC2 在 US East 区域,选择 N. Virginia
  3. 在资源管理里面选择我们的 ALB
  4. 选择 Add Rules > Add Managed Rule Groups
  5. 从 AWS 托管组里面选择 Core Rule Set 和 SQL Database

image-20200815133729597

image-20200815133746446

  1. 创建 web ACL

拦截测试

添加好托管规则组之后,我们在模拟***查看效果。

image-20200815134103928

现在可以看到,全部拦截成功,通过我们简单的配置,对一般常见的***很快拦截下载,保障的应用程序的安全。

三、自定义规则

如果你不使用 AWS 提供的托管规则组,WAF 允许您为处理请求创建自己的规则。这对于添加与特定应用程序相关的逻辑非常有用。

通过自定义规则,我们可以检测一些 HTTP 请求的组件,比如:

  • Source IP
  • Headers
  • Body
  • URI
  • Query Parameters

通过这些检测,WAF 可以拦截或允许任何相关的请求。

特定***

在观察访日日志的过程中,发现所有的都带有一个请求头X-TomatoAttack,阻止这个请求头可以阻止所有的,下面我们将创建自定义规则来拦截这个请求头,在没有 WAF 加持的情况下面,我们模拟***,查看情况。

curl -H "X-TomatoAttack: Red" "${JUICESHOP_URL}"
curl -H "X-TomatoAttack: Green" "${JUICESHOP_URL}"

image-20200815141037732

image-20200815141105054

创建规则

然后我们创建自定义规则:

  1. 在 web ACL 中,选择添加 Custom Rule
  2. 在 Inspect 里面选择 Header
  3. 如果 X-TomatoAttack >=0,我们就拦截请求

image-20200815141849044

拦截测试

规则添加好之后,我们在进行测试,发现带有 X-TomatoAttack Header 的都被拦截,其他的 Header 可以正常请求。

image-20200815142143117

自定义规则允许您实现自己的逻辑来处理 WAF 中的请求。自定义规则可以检查请求的许多组件,然后在规则语句为真时采取行动阻止或允许请求。

每个 Web ACL 都有一个最大的 Web ACL 容量单元(WCU)。这是1500,但是如果需要可以增加。Web ACL 中的每个规则和规则组都有助于实现这一限制。

四、高级自定义规则

我们已经创建了一个简单的 WAF 规则,用于评估请求的一部分。如何创建一个规则来对一个请求的多个部分进行评估呢?

所有 WAF 规则都定义为 JSON 对象。对于复杂的规则,直接使用 JSON 格式比使用 Console 规则编辑器更有效。可以使用 get-rule-group 命令使用 API、 CLI 或 Console 检索 JSON 格式的现有规则。使用您最喜欢的 JSON 文本编辑器修改它们,然后在 API、 CLI 或控制台中使用 update-rule-group 重新上传它们。

在 JSON 中定义规则允许使用版本控制,以查看复杂规则集是如何、什么是以及为什么发生了更改。

可以使用 AND、OR 和 NOT 运算符创建更复杂的规则。这对于检查请求的多个部分很有用。例如,您只能在查询字符串或 Header 包含某个键/值时才允许请求。

我们把之前创建好的自定义规则使用 JSON editor 查看,如下:

{
  "Name": "header-tomato",
  "Priority": 0,
  "Action": {
    "Block": {}
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "header-tomato"
  },
  "Statement": {
    "SizeConstraintStatement": {
      "FieldToMatch": {
        "SingleHeader": {
          "Name": "x-tomatoattack"
        }
      },
      "ComparisonOperator": "GE",
      "Size": 0,
      "TextTransformations": [
        {
          "Type": "NONE",
          "Priority": 0
        }
      ]
    }
  }
}

如何制作复杂规则

比如我们又收到了一个新的具有如下特征:

  • 包含一个 body,大小超过 100kb
  • 缺少请求头 x-upload-photo: true

这个复合规则比较复杂,难以使用 console 完成,我们需要通过编辑 json 来完成,我们先制作一个空 rule:

{
  "Name": "complex-rule",
  "Priority": 0,
  "Action": {
    "Block": {}
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "complex-rule"
  },
  "Statement": {
    // We will add the rule here
  }
}

有两点我们需要注意:

  1. 如果请求 body 大于 100kb,阻止请求
  2. 如果请求不包含请求头 x-upload-body: true,阻止请求

我们需要 OrStatementNotStatement 来表达规则逻辑。

{
"Statement": {
  "OrStatement": {
    "Statements": [
      {
        // Inspect Body Size here
      },
      {
        "NotStatement": {
        // Inspect Header here
        }
      }
    ]
  }
}

要检查 body 的大小,我们将使用 SizeConstraintStatement 来验证请求 body 的大小。

"SizeConstraintStatement": {
  "FieldToMatch": {
    "Body": {}
  },
  "ComparisonOperator": "GT",
  "Size": "100",
  "TextTransformations": [
    {
      "Type": "NONE",
      "Priority": 0
    }
  ]
}

要检查请求的 header,使用 ByteMatchStatement

"ByteMatchStatement": {
  "FieldToMatch": {
    "SingleHeader": {
      "Name": "x-upload-image"
    }
  },
  "PositionalConstraint": "EXACTLY",
  "SearchString": "true",
  "TextTransformations": [
    {
      "Type": "NONE",
      "Priority": 0
    }
  ]
}

最终生成的规则如下:

{
  "Name": "complex-rule",
  "Priority": 0,
  "Action": {
    "Block": {}
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "complex-rule"
  },
  "Statement": {
    "OrStatement": {
      "Statements": [
        {
          "SizeConstraintStatement": {
            "FieldToMatch": {
              "Body": {}
            },
            "ComparisonOperator": "GT",
            "Size": "100",
            "TextTransformations": [
              {
                "Type": "NONE",
                "Priority": 0
              }
            ]
          }
        },
        {
          "NotStatement": {
            "Statement": {
              "ByteMatchStatement": {
                "FieldToMatch": {
                  "SingleHeader": {
                    "Name": "x-upload-body"
                  }
                },
                "PositionalConstraint": "EXACTLY",
                "SearchString": "true",
                "TextTransformations": [
                  {
                    "Type": "NONE",
                    "Priority": 0
                  }
                ]
              }
            }
          }
        }
      ]
    }
  }
}

这样一个复杂的规则就一步步创建好了,可以抵挡特定的一些***。

***升级

假设我们目前有一个复杂的规则组,可以阻挡包含下面任一条件的请求:

  1. 请求包含 header x-milkshake: chocolate,阻止请求
  2. 请求包含查询字符串milkshake=banana,阻止请求

规则的 json 格式如下:

{
  "Name": "complex-rule-challenge",
  "Priority": 0,
  "Action": {
    "Block": {}
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "complex-rule-challenge"
  },
  "Statement": {
    "OrStatement": {
      "Statements": [
        {
          "ByteMatchStatement": {
            "FieldToMatch": {
              "SingleHeader": {
                "Name": "x-milkshake"
              }
            },
            "PositionalConstraint": "EXACTLY",
            "SearchString": "chocolate",
            "TextTransformations": [
              {
                "Type": "NONE",
                "Priority": 0
              }
            ]
          }
        },
        {
          "ByteMatchStatement": {
            "FieldToMatch": {
              "SingleQueryArgument": {
                "Name": "milkshake"
              }
            },
            "PositionalConstraint": "EXACTLY",
            "SearchString": "banana",
            "TextTransformations": [
              {
                "Type": "NONE",
                "Priority": 0
              }
            ]
          }
        }
      ]
    }
  }
}

但是最近者近期升级了请求,现在的请求变为如下:

  1. 请求头包含 x-milkshake: chocolatex-favourite-topping: nuts
  2. 查询字符串包含milkshake=bananafavourite-topping=sauce

我们需要更新现有规则。使用 AndStatement 扩展两个现有语句。 更新后的 json 格式如下:

{
  "Name": "complex-rule-challenge",
  "Priority": 0,
  "Action": {
    "Block": {}
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "complex-rule-challenge"
  },
  "Statement": {
    "OrStatement": {
      "Statements": [
        {
          "AndStatement": {
            "Statements": [
              {
                "ByteMatchStatement": {
                  "FieldToMatch": {
                    "SingleHeader": {
                      "Name": "x-milkshake"
                    }
                  },
                  "PositionalConstraint": "EXACTLY",
                  "SearchString": "chocolate",
                  "TextTransformations": [
                    {
                      "Type": "NONE",
                      "Priority": 0
                    }
                  ]
                }
              },
              {
                "ByteMatchStatement": {
                  "FieldToMatch": {
                    "SingleHeader": {
                      "Name": "x-favourite-topping"
                    }
                  },
                  "PositionalConstraint": "EXACTLY",
                  "SearchString": "nuts",
                  "TextTransformations": [
                    {
                      "Type": "NONE",
                      "Priority": 0
                    }
                  ]
                }
              }
            ]
          }
        },
        {
          "AndStatement": {
            "Statements": [
              {
                "ByteMatchStatement": {
                  "FieldToMatch": {
                    "SingleQueryArgument": {
                      "Name": "milkshake"
                    }
                  },
                  "PositionalConstraint": "EXACTLY",
                  "SearchString": "banana",
                  "TextTransformations": [
                    {
                      "Type": "NONE",
                      "Priority": 0
                    }
                  ]
                }
              },
              {
                "ByteMatchStatement": {
                  "FieldToMatch": {
                    "SingleQueryArgument": {
                      "Name": "favourite-topping"
                    }
                  },
                  "PositionalConstraint": "EXACTLY",
                  "SearchString": "sauce",
                  "TextTransformations": [
                    {
                      "Type": "NONE",
                      "Priority": 0
                    }
                  ]
                }
              }
            ]
          }
        }
      ]
    }
  }
}

拦截测试

更新规则之后,之前被拦截的现在可以允许。

# Allowed
curl -H "x-milkshake: chocolate" "${JUICESHOP_URL}"
curl  "${JUICESHOP_URL}?milkshake=banana"

使用新的参数请求的,将被阻止。

# Blocked
curl -H "x-milkshake: chocolate" -H "x-favourite-topping: nuts" "${JUICESHOP_URL}"
curl  "${JUICESHOP_URL}?milkshake=banana&favourite-topping=sauce"

image-20200815170428990

在这一节,我们为大家演示了如何通过 json 制定一个复杂的规则,这个规则是在 console 无法实现的。

五、测试新规则

在部署新规则之前,对其进行测试是至关重要的。这是为了确保您不会意外地阻止有效的请求。

到目前为止,在指定对请求采取什么操作时,您已经使用了 Block 和 Allow。还有第三个动作 Count。Count 允许您度量满足规则条件的请求数量。

Count 是一个非终止操作。当请求与 Count 操作匹配规则时,web ACL 将继续处理其余的规则。

当具有 Count 的规则动作被匹配时,事件将作为 CloudWatch 指标发出。要查看规则的计数,请到 CloudWatch 指标控制台。选择 AWS/WAFv2,然后选择 Region、 Rule、 WebACL 以查看指标。

欢迎大家扫码关注,获取更多信息

【AWS征文】带你探秘 AWS WAF 如何抵挡各种攻*击来保护你的应用安全