openresty配置
确保已经正确整合openresty和modsecurity
完整配置如下,增加了白名单和黑名单已经限制请求次数
load_module modules/ngx_http_modsecurity_module.so;
#user nobody;
worker_processes 4;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
underscores_in_headers on;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
gzip on; # 启用 Gzip 压缩
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
gzip_vary on; # 向响应头添加 `Vary: Accept-Encoding`,以确保代理缓存的正确性
gzip_min_length 1024; # 设置压缩的最小文件大小,较小的文件可能不压缩
gzip_proxied any; # 启用代理后端的压缩响应
gzip_comp_level 5; # 设置压缩级别,范围是1-9,数值越大压缩比越高,但CPU消耗也更大
geo $whitelist {
default 0; # 默认不是白名单中的IP
#10.0.0.0/8 1; # 白名单IP段
#192.168.1.100 1; # 白名单单个IP
}
geo $blacklist {
default 0; # 默认不是黑名单中的IP
#192.168.1.0/24 1; # 黑名单IP段
#203.0.113.50 1; # 黑名单单个IP
}
# 将白名单和黑名单组合起来判断
map $whitelist$blacklist $access {
"10" 0; # 在白名单中时,即使在黑名单中,仍然允许访问
"11" 0; # 在白名单中时,即使在黑名单中,仍然允许访问
"01" 1; # 不在白名单中,但在黑名单中,拒绝访问
"00" 0; # 不在白名单或黑名单中,允许访问(默认行为,可以改为1表示拒绝)
}
# 调整为50MB的限流区,允许每秒10个请求,突发请求允许20个根据IP地址的数量自行调整限流区
limit_req_zone $binary_remote_addr zone=req_zone:50m rate=10r/s;
# 定义一个连接限制区,大小为10MB,最大连接数为10个
limit_conn_zone $binary_remote_addr zone=conn_zone:50m;
log_format custom '$remote_addr $host $proxy_add_x_forwarded_for - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /app/openresty/nginx/logs/access.log custom;
server {
listen 8080;
server_name 0.0.0.0;
charset utf-8;
# 在特定位置启用 ModSecurity
modsecurity on;
modsecurity_rules_file /app/openresty/nginx/conf/modsecurity.conf;
#access_log logs/host.access.log main;
if ($access) {
return 403; # 如果$access为1,则拒绝访问
}
location / {
# 应用请求限流
limit_req zone=req_zone burst=20 nodelay;
# 应用连接限制
limit_conn conn_zone 10;
proxy_pass http://127.0.0.1:9080;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 关键部分:重写后端服务器的重定向URL
proxy_redirect default;
# 错误处理
error_page 503 /error503.html;
}
location = /error503.html {
internal;
default_type text/html;
return 503 "Service Unavailable";
}
}
}
配置modsecurity
修改modsecurity.conf添加规则
Include /app/openresty/nginx/modsec/crs/crs-setup.conf
Include /app/openresty/nginx/modsec/crs/rules/*.conf
# -- Rule engine initialization ----------------------------------------------
# Enable ModSecurity, attaching it to every transaction. Use detection
# only to start with, because that minimises the chances of post-installation
# disruption.
#
SecRuleEngine on
# -- Request body handling ---------------------------------------------------
# Allow ModSecurity to access request bodies. If you don't, ModSecurity
# won't be able to see any POST parameters, which opens a large security
# hole for attackers to exploit.
#
SecRequestBodyAccess On
# Enable XML request body parser.
# Initiate XML Processor in case of xml content-type
#
SecRule REQUEST_HEADERS:Content-Type "^(?:application(?:/soap\+|/)|text/)xml" \
"id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML"
# Enable JSON request body parser.
# Initiate JSON Processor in case of JSON content-type; change accordingly
# if your application does not use 'application/json'
#
SecRule REQUEST_HEADERS:Content-Type "^application/json" \
"id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON"
# Sample rule to enable JSON request body parser for more subtypes.
# Uncomment or adapt this rule if you want to engage the JSON
# Processor for "+json" subtypes
#
#SecRule REQUEST_HEADERS:Content-Type "^application/[a-z0-9.-]+[+]json" \
# "id:'200006',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON"
# Maximum request body size we will accept for buffering. If you support
# file uploads then the value given on the first line has to be as large
# as the largest file you are willing to accept. The second value refers
# to the size of data, with files excluded. You want to keep that value as
# low as practical.
#
SecRequestBodyLimit 104857600
SecRequestBodyNoFilesLimit 52428800
# What to do if the request body size is above our configured limit.
# Keep in mind that this setting will automatically be set to ProcessPartial
# when SecRuleEngine is set to DetectionOnly mode in order to minimize
# disruptions when initially deploying ModSecurity.
#
SecRequestBodyLimitAction Reject
# Maximum parsing depth allowed for JSON objects. You want to keep this
# value as low as practical.
#
SecRequestBodyJsonDepthLimit 512
# Maximum number of args allowed per request. You want to keep this
# value as low as practical. The value should match that in rule 200007.
SecArgumentsLimit 1000
# If SecArgumentsLimit has been set, you probably want to reject any
# request body that has only been partly parsed. The value used in this
# rule should match what was used with SecArgumentsLimit
SecRule &ARGS "@ge 1000" \
"id:'200007', phase:2,t:none,log,deny,status:400,msg:'Failed to fully parse request body due to large argument count',severity:2"
# Verify that we've correctly processed the request body.
# As a rule of thumb, when failing to process a request body
# you should reject the request (when deployed in blocking mode)
# or log a high-severity alert (when deployed in detection-only mode).
#
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"
# By default be strict with what we accept in the multipart/form-data
# request body. If the rule below proves to be too strict for your
# environment consider changing it to detection-only. You are encouraged
# _not_ to remove it altogether.
#
SecRule MULTIPART_STRICT_ERROR "!@eq 0" \
"id:'200003',phase:2,t:none,log,deny,status:400, \
msg:'Multipart request body failed strict validation: \
PE %{REQBODY_PROCESSOR_ERROR}, \
BQ %{MULTIPART_BOUNDARY_QUOTED}, \
BW %{MULTIPART_BOUNDARY_WHITESPACE}, \
DB %{MULTIPART_DATA_BEFORE}, \
DA %{MULTIPART_DATA_AFTER}, \
HF %{MULTIPART_HEADER_FOLDING}, \
LF %{MULTIPART_LF_LINE}, \
SM %{MULTIPART_MISSING_SEMICOLON}, \
IQ %{MULTIPART_INVALID_QUOTING}, \
IP %{MULTIPART_INVALID_PART}, \
IH %{MULTIPART_INVALID_HEADER_FOLDING}, \
FL %{MULTIPART_FILE_LIMIT_EXCEEDED}'"
# Did we see anything that might be a boundary?
#
# Here is a short description about the ModSecurity Multipart parser: the
# parser returns with value 0, if all "boundary-like" line matches with
# the boundary string which given in MIME header. In any other cases it returns
# with different value, eg. 1 or 2.
#
# The RFC 1341 descript the multipart content-type and its syntax must contains
# only three mandatory lines (above the content):
# * Content-Type: multipart/mixed; boundary=BOUNDARY_STRING
# * --BOUNDARY_STRING
# * --BOUNDARY_STRING--
#
# First line indicates, that this is a multipart content, second shows that
# here starts a part of the multipart content, third shows the end of content.
#
# If there are any other lines, which starts with "--", then it should be
# another boundary id - or not.
#
# After 3.0.3, there are two kinds of types of boundary errors: strict and permissive.
#
# If multipart content contains the three necessary lines with correct order, but
# there are one or more lines with "--", then parser returns with value 2 (non-zero).
#
# If some of the necessary lines (usually the start or end) misses, or the order
# is wrong, then parser returns with value 1 (also a non-zero).
#
# You can choose, which one is what you need. The example below contains the
# 'strict' mode, which means if there are any lines with start of "--", then
# ModSecurity blocked the content. But the next, commented example contains
# the 'permissive' mode, then you check only if the necessary lines exists in
# correct order. Whit this, you can enable to upload PEM files (eg "----BEGIN.."),
# or other text files, which contains eg. HTTP headers.
#
# The difference is only the operator - in strict mode (first) the content blocked
# in case of any non-zero value. In permissive mode (second, commented) the
# content blocked only if the value is explicit 1. If it 0 or 2, the content will
# allowed.
#
#
# See #1747 and #1924 for further information on the possible values for
# MULTIPART_UNMATCHED_BOUNDARY.
#
SecRule MULTIPART_UNMATCHED_BOUNDARY "@eq 1" \
"id:'200004',phase:2,t:none,log,deny,msg:'Multipart parser detected a possible unmatched boundary.'"
# PCRE Tuning
# We want to avoid a potential RegEx DoS condition
#
SecPcreMatchLimit 1000
SecPcreMatchLimitRecursion 1000
# Some internal errors will set flags in TX and we will need to look for these.
# All of these are prefixed with "MSC_". The following flags currently exist:
#
# MSC_PCRE_LIMITS_EXCEEDED: PCRE match limits were exceeded.
#
SecRule TX:/^MSC_/ "!@streq 0" \
"id:'200005',phase:2,t:none,deny,msg:'ModSecurity internal error flagged: %{MATCHED_VAR_NAME}'"
# -- Response body handling --------------------------------------------------
# Allow ModSecurity to access response bodies.
# You should have this directive enabled in order to identify errors
# and data leakage issues.
#
# Do keep in mind that enabling this directive does increases both
# memory consumption and response latency.
#
SecResponseBodyAccess On
# Which response MIME types do you want to inspect? You should adjust the
# configuration below to catch documents but avoid static files
# (e.g., images and archives).
#
SecResponseBodyMimeType text/plain text/html text/xml
# Buffer response bodies of up to 512 KB in length.
SecResponseBodyLimit 524288
# What happens when we encounter a response body larger than the configured
# limit? By default, we process what we have and let the rest through.
# That's somewhat less secure, but does not break any legitimate pages.
#
SecResponseBodyLimitAction ProcessPartial
# -- Filesystem configuration ------------------------------------------------
# The location where ModSecurity stores temporary files (for example, when
# it needs to handle a file upload that is larger than the configured limit).
#
# This default setting is chosen due to all systems have /tmp available however,
# this is less than ideal. It is recommended that you specify a location that's private.
#
SecTmpDir /app/openresty/nginx/sec_temp
# The location where ModSecurity will keep its persistent data. This default setting
# is chosen due to all systems have /tmp available however, it
# too should be updated to a place that other users can't access.
#
SecDataDir /app/openresty/nginx/sec_data
# -- File uploads handling configuration -------------------------------------
# The location where ModSecurity stores intercepted uploaded files. This
# location must be private to ModSecurity. You don't want other users on
# the server to access the files, do you?
#
#SecUploadDir /opt/modsecurity/var/upload/
# By default, only keep the files that were determined to be unusual
# in some way (by an external inspection script). For this to work you
# will also need at least one file inspection rule.
#
#SecUploadKeepFiles RelevantOnly
# Uploaded files are by default created with permissions that do not allow
# any other user to access them. You may need to relax that if you want to
# interface ModSecurity to an external program (e.g., an anti-virus).
#
#SecUploadFileMode 0600
# -- Debug log configuration -------------------------------------------------
# The default debug log configuration is to duplicate the error, warning
# and notice messages from the error log.
#
SecDebugLog /app/openresty/nginx/logs/modsec_debug.log
SecDebugLogLevel 1
# -- Audit log configuration -------------------------------------------------
# Log the transactions that are marked by a rule, as well as those that
# trigger a server error (determined by a 5xx or 4xx, excluding 404,
# level response status codes).
#
SecAuditEngine RelevantOnly
SecAuditLogRelevantStatus "^(?:5|4(?!04))"
# Log everything we know about a transaction.
SecAuditLogParts ABIJDEFHZ
# Use a single file for logging. This is much easier to look at, but
# assumes that you will use the audit log only ocassionally.
#
SecAuditLogType Serial
SecAuditLog /app/openresty/nginx/logs/modsec_audit.log
# Specify the path for concurrent audit logging.
#SecAuditLogStorageDir /opt/modsecurity/var/audit/
# -- Miscellaneous -----------------------------------------------------------
# Use the most commonly used application/x-www-form-urlencoded parameter
# separator. There's probably only one application somewhere that uses
# something else so don't expect to change this value.
#
SecArgumentSeparator &
# Settle on version 0 (zero) cookies, as that is what most applications
# use. Using an incorrect cookie version may open your installation to
# evasion attacks (against the rules that examine named cookies).
#
SecCookieFormat 0
# Specify your Unicode Code Point.
# This mapping is used by the t:urlDecodeUni transformation function
# to properly map encoded data to your language. Properly setting
# these directives helps to reduce false positives and negatives.
#
SecUnicodeMapFile unicode.mapping 20127
# Improve the quality of ModSecurity by sharing information about your
# current ModSecurity version and dependencies versions.
# The following information will be shared: ModSecurity version,
# Web Server version, APR version, PCRE version, Lua version, Libxml2
# version, Anonymous unique id for host.
# NB: As of April 2022, there is no longer any advantage to turning this
# setting On, as there is no active receiver for the information.
SecStatusEngine Off
# 设置请求体大小限制,防止大请求DDoS攻击
SecRequestBodyLimit 31457280 # 设置请求体最大大小为30MB
SecRequestBodyNoFilesLimit 31457280 # 非文件请求体限制为30MB
# 1. 设置全局变量
SecAction "id:900000,phase:1,nolog,pass,t:none,setvar:tx.ddos_protection_threshold=200" # 每分钟允许的最大请求数。
SecAction "id:900001,phase:1,nolog,pass,t:none,setvar:tx.ddos_protection_burst=300" #允许的突发请求数量。
SecAction "id:900002,phase:1,nolog,pass,t:none,setvar:tx.ddos_protection_block_time=600" # IP 被封禁的时间(秒)。
SecAction "id:900003,phase:1,nolog,pass,t:none,setvar:tx.ddos_connection_threshold=100" #每个 IP 允许的最大并发连接数。
SecAction "id:900004,phase:1,nolog,pass,t:none,setvar:tx.ddos_connection_burst=150" # 允许的最大并发连接数的突发数量。
SecAction "id:900005,phase:1,nolog,pass,t:none,setvar:tx.dos_counter_expire_time=60" # DDoS 请求计数器的过期时间(秒)。
SecAction "id:900006,phase:1,nolog,pass,t:none,setvar:tx.connection_counter_expire_time=60" # 并发连接计数器的过期时间(秒)
# 2. 基于请求频率的防护(每个 IP 每分钟最大请求数)
SecRule IP:DOS_COUNTER "@gt %{tx.ddos_protection_threshold}" \
"id:900100,phase:2,log,deny,status:429,msg:'DDoS Attack Detected: Exceeded request threshold',chain"
SecRule IP:DOS_COUNTER "@le %{tx.ddos_protection_burst}" \
"setvar:ip.ddos_block=1,expirevar:ip.ddos_block=%{tx.ddos_protection_block_time},setvar:!ip.dos_counter"
# 3. 每次访问增加 IP 计数器并记录日志
SecRule REMOTE_ADDR "^.*$" \
"id:900101,phase:2,log,pass,setvar:ip.dos_counter=+1,expirevar:ip.dos_counter=%{tx.dos_counter_expire_time},msg:'IP Address: %{REMOTE_ADDR}, DOS_COUNTER: %{ip.dos_counter}'"
# 4. 阻止已被标记为恶意的 IP
SecRule IP:DDOS_BLOCK "@eq 1" \
"id:900102,phase:2,deny,status:403,log,msg:'Blocked IP due to suspected DDoS activity'"
# 5. 基于并发连接数的防护(每个 IP 的最大并发连接数)
SecRule IP:CONNECTION_COUNTER "@gt %{tx.ddos_connection_threshold}" \
"id:900200,phase:2,deny,status:429,log,msg:'DDoS Attack Detected: Exceeded connection threshold',chain"
SecRule IP:CONNECTION_COUNTER "@le %{tx.ddos_connection_burst}" \
"setvar:ip.ddos_block=1,expirevar:ip.ddos_block=%{tx.ddos_protection_block_time},setvar:!ip.connection_counter"
# 6. 每次连接增加 IP 并发连接计数器
SecRule REMOTE_ADDR "^.*$" \
"id:900201,phase:2,nolog,pass,setvar:ip.connection_counter=+1,expirevar:ip.connection_counter=%{tx.connection_counter_expire_time}"
# 7. 阻止已被标记为恶意的 IP(并发连接数)
SecRule IP:DDOS_BLOCK "@eq 1" \
"id:900202,phase:2,deny,status:403,log,msg:'Blocked IP due to excessive connections'"