参考文档:
Kubernetes NGINX Ingress 带有内置的 WAF(Web Application Firewall),使用 ModSecurity 和 OWASP Core Rule Set。虽然默认情况下禁用这些规则,但您可以使用配置映射和入口注释来启用它们并微调其规则。ModSecurity 是一个开源的 Web应用程序防火墙(WAF)。它可以帮助您在应用程序前面提供额外的安全层。
关于 Kubernetes Ingresses、NGINX、ModSecurity 和核心规则集 101
让我们简要回顾一下所涉及的主要技术和组件,以便更轻松地遵循本文的其余部分:
NGINX
是一个开源的 Web 服务器,负载均衡器和反向代理。这通常被称为 open nginx,因为同名公司提供商业 NGINX + 产品。通过本文,我们将专注于 open nginx。NGINX Ingress Controller
是一个使用 open nginx 构建的开源 Kubernetes Ingress 控制器。本文假设这是您使用 NGINX 的方式,尽管如果您直接使用 NGINX,或者即使您只是使用 ModSecurity,其中大部分都是相关的。WAF
(Web应用程序防火墙)是一种专门针对 Web 流量的防火墙,即 HTTP/S。ModSecurity
是一个开源的 WAF,与流行的 Web 服务器(如Apache,NGINX 和 ISS)兼容,具有强大的规则定义语言。ModSecurity-nginx
连接器提供了 NGINX 和 ModSecurity 之间的接口,允许 WAF 规则作为每个请求的一部分进行处理。ModSecurity OWASP
核心规则集(又名 CRS )是由 OWASP 项目赞助的一组 ModSecurity(和其他兼容的WAF引擎)规则,旨在提供针对各种攻击的保护。
关于 ModSecurity 未来的说明
- ModSecurity 是一个由 Trustwave 支持的开源项目。2021 年 8 月,他们宣布结束其商业支持和维护,将所有未来责任移交给开源社区。
- OWASP 核心规则集打算继续开发和更新核心规则集。然而,他们对
ModSecurity
的长期可行性感到担忧,并计划构建一个名为Coraza
的替代WAF引擎(不一定是替代品),该引擎应该与ModSecurity
规则兼容。 - 最后,ModSecurity 以前也是 NGINX Plus 商业解决方案的一部分,它提供了 WAF 功能。F5(NGINX Plus背后的公司)也决定终止对 ModSecurity WAF 的支持,并将只提供替代的 App Protect WAF。
配置 Ingress-nginx 启用 ModSecurity
Ingress-nginx 使用 ConfigMap 进行配置。根据您的安装,您必须修改此配置映射,或者在 Helm 的
values.yaml
文件中对其进行调整(例如)。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32controller:
config:
...
# Enable Modsecurity and the OWASP Core rule set
enable-modsecurity: "true"
enable-owasp-modsecurity-crs: "true"
# Update ModSecurity config and rules
modsecurity-snippet: |
# Enable prevention mode. Can be any of: DetectionOnly,On,Off (default is DetectionOnly)
SecRuleEngine On
# Enable scanning of the request body
SecRequestBodyAccess On
# Enable XML and JSON parsing
SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\+|/)|text/)xml" \
"id:'200000',phase:1,t:none,t:lowercase,pass,nolog,\
ctl:requestBodyProcessor=XML"
SecRule REQUEST_HEADERS:Content-Type "application/json" \
"id:'200001',phase:1,t:none,t:lowercase,pass,nolog,\
ctl:requestBodyProcessor=JSON"
# Max request sizes in bytes (with/without files)
# Note NGINX Ingress has its own annotations, keep in sync!
SecRequestBodyLimit 20971520 # 20Mb (default is 12.5Mb)
SecRequestBodyNoFilesLimit 262144 # 250Kb (default is 128Kb)
SecRequestBodyLimitAction Reject # Reject if larger (we could also let it pass with ProcessPartial)
# Update config to include PUT/PATCH/DELETE in the allowed HTTP methods (instead of fully disabling 911100)
SecAction "id:900200,phase:1,nolog,pass,t:none,\
setvar:tx.allowed_methods=GET HEAD POST OPTIONS PUT PATCH DELETE"
# Send ModSecurity audit logs to the stdout (only for rejected requests)
SecAuditLog /dev/stdout
SecAuditLogFormat JSON
SecAuditEngine RelevantOnly # could be On/Off/RelevantOnly您可以使用
nginx.ingress.kubernetes.io/modsecurity-snippet
注释在入口级别进一步调整配置,甚至完全禁用它。例如,要禁用特定 ingress 中的 WAF,请执行以下操作:1
2nginx.ingress.kubernetes.io/modsecurity-snippet: |
SecRuleEngine Off也可以在一个 ingress 资源清单中针对局部的 ingress 资源配置启用 Modsecurity, 完整的示例如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
# New annotation
cert-manager.io/cluster-issuer: 'letsencrypt-prod'
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
# Configure ModSecurity
nginx.ingress.kubernetes.io/enable-modsecurity: "true"
nginx.ingress.kubernetes.io/enable-owasp-core-rules: "true"
nginx.ingress.kubernetes.io/modsecurity-transaction-id: "$request_id"
nginx.ingress.kubernetes.io/modsecurity-snippet: |
SecRuleEngine On # 默认值为 DetectionOnly(仅检测)。可以是以下任意一项:DetectionOnly,On,Off (仅检测、开、关)
name: example
namespace: example-service
spec:
rules:
- host: example.containerinfra.com
http:
paths:
- backend:
serviceName: example
servicePort: http
path: /
tls:
- hosts:
- example.containerinfra.com
secretName: example-containerinfra-com-tls建议首先在测试环境中运行 ModSecurity,并针对此运行测试集。您可能需要禁用某些规则,以便应用程序在启用 ModSecurity 的情况下正常工作。
当请求被阻止时,您将看到一个日志事件,详细说明触发了哪个规则:
1
[error] 574#574: *378 [client 0.0.0.0] ModSecurity: Access denied with code 403 (phase 2). Matched "Operator `Ge' with parameter `5' against variable `TX:ANOMALY_SCORE' (Value: `30' ) [file "/etc/nginx/owasp-modsecurity-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "80"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 30)"] [data ""] [severity "2"] [ver "OWASP_CRS/3.3.0"] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-generic"] [hostname "10.233.96.247"] [uri "/"] [unique_id "162773426746.083102"] [ref ""], client: 0.0.0.0, server: example.containerinfra.com, request: "GET /?path=..%2F..%2Fdrop%20table;&orgId=3 HTTP/2.0", host: "example.containerinfra.com"
记下日志消息中的 ID 949110。您可以使用
modsecurity-snippet
注释来禁用此规则:1
2
3nginx.ingress.kubernetes.io/modsecurity-snippet: |
SecRuleEngine On
SecRuleRemoveById 949110
在 Ingress Nginx 中配置 ModSecurity 和 CRS
默认模组安全设置
当同时设置 enable-owasp-modsecurity-crs: true
和 enable-modsecurity: true
时,前者会覆盖推荐的 ModSecurity 设置集。这可能令人惊讶,因为这意味着您不会启用请求正文检查,也不会分析 XML/JSON 请求以及其他一些建议的设置。
如果您打算使用核心规则集,因此启用
enable-owasp-modsecurity-crs: true
, 建议使用modsecurity-snippet
来定义和调整推荐的默认值集,例如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23config:
# Enable Modsecurity and the OWASP Core rule set
enable-modsecurity: "true"
enable-owasp-modsecurity-crs: "true"
modsecurity-snippet: |
# By default is DetectionOnly. Can be any of: DetectionOnly,On,Off
SecRuleEngine On
# Avoid sending status information about ModSecurity in response header
SecStatusEngine Off
# Send ModSecurity audit logs to the stdout (only for rejected requests)
SecAuditLog /dev/stdout
SecAuditLogFormat JSON
SecAuditEngine RelevantOnly # could be On/Off/RelevantOnly
# Max request sizes in bytes (with/without files) - Note NGINX Ingress has its own parameter/annotation that should be kept in sync
SecRequestBodyLimit 20971520 # 20Mb (default is 12.5Mb)
SecRequestBodyNoFilesLimit 262144 # 250Kb (default is 128Kb)
SecRequestBodyLimitAction Reject # Reject if larger (we could also let it pass with ProcessPartial)
# recommended limits for regular expression recursion. See https://github.com/SpiderLabs/owasp-modsecurity-crs/issues/656#issuecomment-262780221
SecPcreMatchLimit 500000
SecPcreMatchLimitRecursion 500000
# Include PUT/PATCH/DELETE in the allowed methods, otherwise those verbs will be rejected by rule 911100
SecAction "id:900200,phase:1,nolog,pass,t:none,\
setvar:tx.allowed_methods=GET HEAD POST OPTIONS PUT PATCH DELETE"
配置ModSecurity审核日志
使用 SecAuditXXX 指令,可以配置ModSecurity的审计日志。其中包括几个设置,例如:
- SecAuditEngine 审计日志引擎,可以是 On/Off/RelevantOnly
- SecAuditLogFormat 用于每个日志条目的格式
- SecAuditLog 写入日志的位置
- SecAuditLogParts 每个日志条目中要包含的内容
在 Kubernetes 的上下文中,你肯定希望将日志写入 stdout,并且可能以 JSON 的形式写入。我还建议仅在请求被拒绝时才编写审核条目,除非显式调试(审核可能会对性能产生重大影响),如下
1 | # Send ModSecurity audit logs to the stdout (only for rejected requests) |
每个条目都包含大量数据(取决于 SecAuditLogParts 指令),包括有关请求、响应、ModSecurity 引擎和导致拒绝的确切规则的数据。例如:
1 | { |
使用 Project Honeypot 阻止来自已知恶意 IP 的流量
Project honeypot 是一个系统,它通过在实时网站中为恶意 IP 创建陷阱并在分布式数据库中捕获它们的详细信息来捕获恶意 IP。然后,可以将此数据与 HTTP 黑名单服务一起使用,以阻止来自这些 IP 的请求。CRS 中包含一条规则,可让您与 Project Honeypot 集成并实施HTTP黑名单.
从 Project Honeypot 注册账号并从中检索 HTTP黑名单密钥
然后按如下方式更新 ModSecurity 配置,这将禁用来自所有已知恶意 IP 的请求(搜索引擎爬虫除外)
1
2
3
4
5
6
7
8
9
10SecHttpBlKey YOUR_HTTP_BLACKLIST_KEY
SecAction "id:900500,\
phase:1,\
nolog,\
pass,\
t:none,\
setvar:tx.block_search_ip=0,\
setvar:tx.block_suspicious_ip=1,\
setvar:tx.block_harvester_ip=1,\
setvar:tx.block_spammer_ip=1"
将配置从 modsecurity-snippet 提取到单独的 ConfigMap 中
到目前为止,我们已经使用 NGINX 的 modsecurity-snippet
字段来配置和调整ModSecurity 和 CRS 默认值。但是,此 NGINX 字段的(记录不佳)限制为 4096 个字符。一旦您开始覆盖默认值并调整规则,就很容易达到此限制!幸运的是,此评论中描述了一种解决方法。
创建一个新的配置映射,将所有 ModSecurity 指令作为单个文件属性
1
2
3
4
5
6
7
8
9apiVersion: v1
kind: ConfigMap
metadata:
name: "modsecurity-config"
namespace: nginx-ingress
data:
custom-modsecurity.conf: |
SecRuleEngine On
...将 ConfigMap 挂载为 NGINX 入口控制器的卷。例如,通过 helm chart 安装时,请使用以下值:
1
2
3
4
5
6
7
8
9controller:
...
extraVolumeMounts:
- name: modsecurity-config
mountPath: /etc/nginx/owasp-modsecurity-crs/custom/
extraVolumes:
- name: modsecurity-config
configMap:
name: modsecurity-config使用
modsecurity-snippet
来包含作为卷挂载的 ConfigMap 数据。注意确切的路径取决于卷装载和配置映射中的属性名称1
2
3modsecurity-snippet: |
# Increment this to force nginx to reload the rules when you change the configmap: 1.0.0
Include /etc/nginx/owasp-modsecurity-crs/custom/custom-modsecurity.conf
微调和处理误报
一组带注释的良好初始设置
在为 NGINX 入口启用它们时,请随意使用以下 ModSecurity 和 CRS 设置作为起点。然后根据需要进一步调整。
请注意,此代码段包括针对漏洞(如 Log4j)的其他规则,这些漏洞尚未成为默认 CRS 3.2 集的一部分。但是,CRS项目对这些漏洞做出快速反应,并发布了每个人都可以包含在ModSecurity设置中的新规则。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124##################################
# ModSecurity and CRS configuration
##################################
# By default is DetectionOnly. Can be any of: DetectionOnly,On,Off
SecRuleEngine On
# Avoid sending status information about ModSecurity in response header
SecStatusEngine Off
# Send ModSecurity audit logs to the stdout (only for rejected requests)
# Se https://github.com/kubernetes/ingress-nginx/issues/6438#issuecomment-728955575 and https://www.nginx.com/blog/modsecurity-logging-and-debugging/
SecAuditLog /dev/stdout
SecAuditLogFormat JSON
SecAuditEngine RelevantOnly # could be On/Off/RelevantOnly
# SecDebugLog /dev/stdout
# SecDebugLogLevel 9 # (1-9, 9 is the max)
# Enable scanning of the request body
SecRequestBodyAccess On
# Enable XML and JSON parsing
SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\+|/)|text/)xml" \
"id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML"
SecRule REQUEST_HEADERS:Content-Type "application/json" \
"id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON"
# Max request sizes in bytes (with/without files) - Note NGINX Ingress has its own parameter/annotation that should be kept in sync
SecRequestBodyLimit 20971520 # 20Mb (default is 12.5Mb)
SecRequestBodyNoFilesLimit 262144 # 250Kb (default is 128Kb)
SecRequestBodyLimitAction Reject # Reject if larger (we could also let it pass with ProcessPartial)
# Reject request if failed to parse request body
SecRule REQBODY_ERROR "!@eq 0" \
"id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2"
# recommended limits for regular expression recursion. See https://github.com/SpiderLabs/owasp-modsecurity-crs/issues/656#issuecomment-262780221
SecPcreMatchLimit 500000
SecPcreMatchLimitRecursion 500000
# Disable scanning of responses. Not the best place to stop an attack, and consumes a good deal of cpu/memory
SecResponseBodyAccess Off
SecResponseBodyLimitAction ProcessPartial
# Include PUT/PATCH/DELETE in the allowed methods, otherwise those verbs will be rejected by rule 911100
SecAction "id:900200,phase:1,nolog,pass,t:none,\
setvar:tx.allowed_methods=GET HEAD POST OPTIONS PUT PATCH DELETE"
# Set paranoia level to 1 for OWASP CRS rules
# Can optionally set tx.executing_paranoia_level as per commented line, so we can test higher level rules (without blocking traffic)
SecAction \
"id:900000,\
phase:1,\
nolog,\
pass,\
t:none,\
setvar:tx.paranoia_level=1"
# SecAction \
# "id:900001,\
# phase:1,\
# nolog,\
# pass,\
# t:none,\
# setvar:tx.executing_paranoia_level=2"
# Block requests from known-malicious IPs using project honeypot, see https://www.projecthoneypot.org
# Blacklist key is retrieved by signing up and requesting in https://www.projecthoneypot.org/httpbl_configure.php
# Note search-engine crawlers are still enabled as per block_search_ip paramter
SecHttpBlKey YOUR_PROJECT_HONEYPOT_BLACKLIST_KEY
SecAction "id:900500,\
phase:1,\
nolog,\
pass,\
t:none,\
setvar:tx.block_search_ip=0,\
setvar:tx.block_suspicious_ip=1,\
setvar:tx.block_harvester_ip=1,\
setvar:tx.block_spammer_ip=1"
##################################
# False positives
##################################
# Disable 20004 as its known for false positives, see https://github.com/SpiderLabs/owasp-modsecurity-crs/issues/827#issuecomment-311859317
SecRuleRemoveById 200004
# We really want to remove the following rule as it keeps firing with requests coming from localhost
# in k8s. See for example https://github.com/SpiderLabs/owasp-modsecurity-crs/issues/1566
SecRuleRemoveById 920350
##################################
# Additional rules
##################################
# Extend 932130 to better protect against CVE-2021-44228 (Log4j / Log4Shell)
SecRuleUpdateTargetById 932130 "REQUEST_HEADERS"
# Generic rule against CVE-2021-44228 (Log4j / Log4Shell), not yet released in CRS 3.3.2
# See https://coreruleset.org/20211213/crs-and-log4j-log4shell-cve-2021-44228/
SecRule REQUEST_LINE|ARGS|ARGS_NAMES|REQUEST_COOKIES|REQUEST_COOKIES_NAMES|REQUEST_HEADERS|XML://*|XML://@* "@rx (?:\${[^}]{0,4}\${|\${(?:jndi|ctx))" \
"id:1005,\
phase:2,\
block,\
t:none,t:urlDecodeUni,t:cmdline,\
log,\
msg:'Potential Remote Command Execution: Log4j CVE-2021-44228', \
tag:'application-multi',\
tag:'language-java',\
tag:'platform-multi',\
tag:'attack-rce',\
tag:'OWASP_CRS',\
tag:'capec/1000/152/137/6',\
tag:'PCI/6.5.2',\
tag:'paranoia-level/1',\
ver:'OWASP_CRS/3.4.0-dev',\
severity:'CRITICAL',\
setvar:'tx.rce_score=+%{tx.critical_anomaly_score}',\
setvar:'tx.anomaly_score_pl1=+%{tx.critical_anomaly_score}'"
# Spring framework remote execution (not yet released in CRS 3.3.2). This rule is also triggered by the following exploit(s):
# - https://www.rapid7.com/blog/post/2022/03/30/spring4shell-zero-day-vulnerability-in-spring-framework/
# - https://www.ironcastle.net/possible-new-java-spring-framework-vulnerability-wed-mar-30th/
#
SecRule ARGS|ARGS_NAMES|REQUEST_COOKIES|!REQUEST_COOKIES:/__utm/|REQUEST_COOKIES_NAMES|REQUEST_BODY|REQUEST_HEADERS|XML:/*|XML://@* \
"@rx (?:class\.module\.classLoader\.resources\.context\.parent\.pipeline|springframework\.context\.support\.FileSystemXmlApplicationContext)" \
"id:1006,\
phase:2,\
block,\
t:urlDecodeUni,\
msg:'Remote Command Execution: Malicious class-loading payload',\
logdata:'Matched Data: %{MATCHED_VAR} found within %{MATCHED_VAR_NAME}',\
tag:'application-multi',\
tag:'language-java',\
tag:'platform-multi',\
tag:'attack-rce',\
tag:'OWASP_CRS',\
tag:'capec/1000/152/248',\
tag:'PCI/6.5.2',\
tag:'paranoia-level/2',\
ver:'OWASP_CRS/3.4.0-dev',\
severity:'CRITICAL',\
setvar:'tx.rce_score=+%{tx.critical_anomaly_score}',\
setvar:'tx.anomaly_score_pl2=+%{tx.critical_anomaly_score}'"
从仅检测模式开始,然后切换到预防模式
从仅检测模式开始,可以试运行配置,而不会主动阻止任何流量。
1
SecRuleEngine DetectionOnly
然后,您可以查看审核日志以了解发现的阳性。这可以用作指南,帮助您在启用预防模式之前调整规则。
使用审核日志找出阻止请求的规则
确保已启用审核日志,如前所述,特别是对于被拒绝的请求。然后,您将能够看到触发并导致拒绝的规则的详细信息。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17{
"message":"",
"details":{
"match":"detected SQLi using libinjection.",
"reference":"v20,12",
"ruleId":"942100",
"file":"/etc/nginx/owasp-modsecurity-crs/rules/REQUEST-942-APPLICATION-ATTACK-SQLI.conf",
"lineNumber":"45",
"data":"",
"severity":"0",
"ver":"OWASP_CRS/3.3.2",
"rev":"",
"tags":[],
"maturity":"0",
"accuracy":"0"
}
},出于这些目的,您可以忽略规则 949110,因为这只是决定所有触发规则的分数是否允许拒绝请求的规则。
调整规则参数
如果要公开传统的 REST API,您很快就会意识到规则
911100
拒绝了使用 PUT/PATCH/DELETE 方法的请求。如果你谷歌或查看源代码,你会看到允许的方法在一个变量
tx.allowed_methods
中定义,该变量只允许GET/HEAD/POST/OPTIONS
在其默认值中。您可以通过在配置中包含以下指令将
PUT/PATCH/DELETE
方法添加到允许列表中:1
2SecAction "id:900200,phase:1,nolog,pass,t:none,\
setvar:tx.allowed_methods=GET HEAD POST OPTIONS PUT PATCH DELETE"
完全禁用规则
有时,您别无选择,只能禁用规则。给定规则 ID,您可以使用以下指令完全禁用它:
1
SecRuleRemoveById RULE_ID
例如,建议的一组初始设置以这种方式禁用规则200004因为它会导致误报。
在特定情况下禁用规则,例如特定路径或请求参数
您可以尝试更温和、更有针对性的方法,而不是完全禁用规则。例如,您可以:
禁用特定请求路径的规则。例如,使用以下指令禁用特定请求路径
/login
的规则 932160:1
2
3modsecurity-snippet: |
SecRule REQUEST_FILENAME "@streq /login" \
"phase:1,nolog,pass,id:15000,ctl:ruleRemoveById=932160"禁用特定请求参数的规则。例如,使用以下指令禁用特定参数密码的规则 932160(在查询字符串或请求正文中找到时):
1
2modsecurity-snippet: |
SecRuleUpdateTargetById 932160 !ARGS:password
覆盖单个 ingress 上的设置/配置,甚至禁用 ModSecurity
我们可以使用 nginx.ingress.kubernetes.io/modsecurity-snippet
入口注释为单个入口资源定义任何这些指令。
例如,您可以通过添加以下注释在特定入口中完全禁用 ModSecurity:
1
2nginx.ingress.kubernetes.io/modsecurity-snippet: |
SecRuleEngine Off