Nginx安全配置实战:防御SQL注入与目录遍历攻击
1. 项目概述与核心价值最近在梳理线上业务的安全基线发现很多初级开发者甚至部分运维同学对Nginx的认知还停留在“高性能Web服务器”或“反向代理”的层面认为安全防护是后端应用防火墙WAF或业务代码的事。这其实是一个巨大的误区。Nginx作为流量入口的第一道关卡其配置的严谨性直接决定了攻击面的大小。一次配置疏忽比如不当的location匹配或缺失的关键指令就可能将你的应用服务器直接暴露在目录遍历、SQL注入、恶意扫描等常见Web攻击之下。这个实战教程的核心就是带你跳出“Nginx只是个转发工具”的思维定式将其武装成一个具备基础但至关重要的主动防御层。我们不会涉及复杂的WAF模块编译而是聚焦于Nginx原生配置就能实现的、针对目录遍历、SQL注入、路径穿越、恶意User-Agent等高频攻击的拦截策略。这些配置就像给房子安装最基础的门锁和窗户护栏成本极低但能有效阻挡大部分“顺手牵羊”式的自动化攻击。无论你是负责业务部署的运维工程师还是需要了解基础设施安全的开发人员掌握这些配置都能让你在构建系统时多一份踏实在出现安全警报时多一个排查方向。2. 防御体系设计思路与原则在动手写配置之前我们必须先理清防御的思路。Nginx位于网络边界它的防御逻辑应该遵循“最小权限”和“纵深防御”原则核心目标是过滤和阻断明显恶意的请求减轻后端应用的压力而不是替代应用层的业务安全校验。2.1 防御策略分层我的策略通常分为三层请求结构层过滤针对不符合HTTP协议规范或明显畸形的请求进行拦截。例如异常的请求方法、过长的URI、包含特殊字符的路径等。语义规则层拦截针对具有明确攻击特征的请求内容进行匹配和阻断。这是本次实战的重点包括检测URL中的敏感路径../、SQL关键词union select、系统命令; cat /etc/passwd等。访问行为层限制针对高频、恶意的访问行为进行限制如对特定URI的暴力破解、扫描器爬虫的访问等。这部分通常需要结合limit_req和limit_conn模块。一个关键认知是Nginx的规则匹配是大小写敏感且基于正则表达式的。攻击者经常会使用大小写变换、编码、注释符分割等技巧来绕过简单的字符串匹配。因此我们的规则必须考虑这些绕过手法使用更严谨的正则表达式。2.2 核心配置模块与位置主要用到的Nginx模块是ngx_http_core_module和ngx_http_rewrite_module。防御配置放置的位置至关重要http块内定义全局的映射表map、共享的限流规则等。server块内针对特定域名的通用防护规则如屏蔽非法域名访问、定义错误页面。location块内最精细的防护针对具体的应用路径设置规则。绝大多数攻击检测规则应放在location / {}这个处理所有请求的块中或者放在业务location之前确保请求首先经过安全过滤。注意规则的顺序就是匹配的顺序。应将最严格、最可能命中的阻断规则放在前面将一些记录日志的规则放在后面以提高效率。3. 核心攻击拦截配置实战详解下面我们进入核心环节逐项拆解如何配置。我会给出配置片段并解释每一行背后的意图和可能遇到的坑。3.1 防御目录遍历与路径穿越目录遍历Directory Traversal攻击的核心是利用../或其变体跳出Web根目录访问系统文件。防御的关键是检测请求URI中是否包含这些序列。server { listen 80; server_name yourdomain.com; # 关键定义Web根目录这是所有相对路径的基准 root /var/www/html; location / { # 防御目录遍历拦截包含 ../、..\、./、.\\ 等路径穿越序列的请求 if ($request_uri ~* \.\.(/|%2f|%5c)) { return 403; } if ($request_uri ~* \.(/|%2f|%5c)) { # 注意单纯一个点“.”可能是正常请求这里示例拦截“./”实际需谨慎 # 更常见的做法是结合其他特征这里仅作演示 return 403; } # 防御编码绕过拦截URL编码的穿越序列如 %2e%2e%2f (../), %2e%2e/ (../) # 也防御反斜杠版本Windows路径及其编码 %2e%2e%5c if ($request_uri ~* %2e%2e) { return 403; } # 可选防御绝对路径访问某些配置错误下可能生效 if ($request_uri ~* ^/(etc|bin|usr|var|root|windows|winnt)) { return 403; } # 你的正常代理规则或静态文件服务规则放在下面 proxy_pass http://backend_server; } # 自定义403错误页面避免暴露服务器信息 error_page 403 /403.html; location /403.html { internal; root /usr/share/nginx/html; } }实操心得$request_uri变量包含了原始的、未经解码的请求URI包括查询参数而$uri是解码并规范化后的。防御时一定要用$request_uri因为攻击载荷可能在查询参数里且编码可能不同。正则表达式~*表示不区分大小写匹配。\.\.匹配两个点(/|%2f|%5c)匹配正斜杠、URL编码的正斜杠或反斜杠。这个组合能覆盖../、..\、%2e%2e%2f等多种形式。对“.”的拦截要非常小心因为很多正常的CSS、JS文件引用会使用./。除非你有非常明确的理由否则不建议单独拦截“.”更应关注“..”的组合。return 403;直接返回403状态码中断请求处理。比deny all更灵活可以自定义错误页面。3.2 防御SQL注入探测SQL注入攻击的探测特征通常体现在查询字符串$args或$query_string中。我们的目标是拦截包含常见SQL关键字和操作符的恶意请求。http { # 使用map创建一个变量用于标记请求是否可疑。map块放在http块内只计算一次效率高。 map $args $sql_injection { default 0; # 匹配SQL关键词和操作符忽略大小写。\b表示单词边界提高准确性。 ~* (\bunion\b.*\bselect|\bselect.*\bfrom|\binsert\b.*\binto|\bupdate\b.*\bset|\bdelete\b.*\bfrom|\bdrop\b|\btruncate\b|\balter\b|\bexec\b|\bexecute\b|\bxp_cmdshell\b) 1; ~* (\bor\b|\band\b)\s*[\d] 1; # 匹配 or 11 and 11 等变体 ~* (\bwaitfor\b.*\bdelay\b|\bsleep\s*\(|\bbenchmark\s*\() 1; # 匹配时间盲注特征 ~* (--|\#|\/\*.*\*\/) 1; # 匹配SQL注释符 ~* (\bconcat\s*\(|\bgroup_concat\s*\() 1; # 匹配常见拼接函数 } # 另一个map检查URI路径部分是否包含可疑内容有时攻击载荷在路径中 map $request_uri $uri_sql_injection { default 0; ~* (\bunion\b.*\bselect|\bselect.*\bfrom) 1; } } server { listen 80; server_name yourdomain.com; location / { # 检查map变量如果值为1则阻断请求 if ($sql_injection 1) { # 可以记录到特殊日志便于分析 access_log /var/log/nginx/sql_block.log main; return 403; } if ($uri_sql_injection 1) { access_log /var/log/nginx/sql_block.log main; return 403; } # 额外防御拦截常见的注入测试参数名 if ($query_string ~* (id|user|name|pass|password|account|login|query|search|q)[^]*[;]) { return 403; } proxy_pass http://backend_server; } }注意事项与高级绕过误报风险这是最大的挑战。例如网站搜索功能可能允许用户输入“union”、“select”等词。因此必须将规则细化到具体的location。对于已知的安全接口如/api/search可能需要放宽或禁用某些规则。可以使用location ~ ^/api/search$ { ... }来定义例外。编码绕过攻击者会使用URL编码%27代表单引号、双重编码、HTML编码等。我们的正则匹配的是Nginx接收到的变量而$args和$request_uri在匹配前已经进行了一部分解码。但为了更全面可以在map规则中加入编码形式的匹配例如%27单引号、%2527双重编码的单引号。但要注意过度匹配会增加误报和性能开销。性能考量复杂的正则表达式会影响性能。map指令在http块中定义只在请求处理初期计算一次然后将结果0或1存储在变量中在多个location中复用效率很高。应优先使用map。记录日志拦截时记录到独立日志sql_block.log便于后续安全审计和规则调优。不要和普通访问日志混在一起。3.3 防御其他常见Web攻击除了上述两种还有大量自动化工具发起的常见探测攻击。server { location / { # 1. 防御敏感文件访问 (如 .git, .svn, .env, 备份文件) if ($request_uri ~* \.(git|svn|htaccess|htpasswd|env|bak|old|swp|sql|tar\.gz|zip)$) { return 404; # 返回404比403更好不暗示文件存在 } if ($request_uri ~* /\.(git|svn)) { return 404; } # 2. 防御命令注入特征 (分号、管道符、反引号等) if ($query_string ~* [;|]) { # 注意是正常的查询参数分隔符此规则可能误报需结合上下文或调整正则 # 更安全的做法是匹配命令注入模式如 ; ls | cat if ($query_string ~* ;(ls|cat|id|whoami|pwd)\s) { return 403; } } # 3. 防御恶意User-Agent (常见扫描器、漏洞利用工具) if ($http_user_agent ~* (nmap|sqlmap|nikto|acunetix|nessus|w3af|dirb|hydra|metasploit)) { # 可以直接返回444Nginx会立即关闭连接不发送任何响应头 return 444; # 或者记录并返回假数据 # access_log /var/log/nginx/scanner.log; # root /dev/null; # return 200 Not Found; } # 拦截空或非常规的User-Agent if ($http_user_agent - ) { return 444; } # 4. 限制HTTP方法只允许GET, POST, HEAD等 if ($request_method !~ ^(GET|HEAD|POST|OPTIONS)$) { return 405; # Method Not Allowed } # 5. 限制请求体大小防止缓冲区溢出攻击 client_max_body_size 10M; # 6. 隐藏Nginx版本号在http或server块中配置 server_tokens off; proxy_pass http://backend_server; } }配置详解与取舍敏感文件拦截返回404而非403是一种“安全模糊”策略让攻击者无法区分是文件不存在还是被拦截。命令注入防御直接匹配[;|]误报率极高因为是正常参数分隔符和可能在搜索功能中出现。**最佳实践是结合具体上下文**例如只在对cmd、exec这类高危参数进行匹配或者像示例中匹配具体的命令模式。在实际生产中这部分逻辑更适合放在专业的WAF或应用层。User-Agent拦截返回444Nginx自定义的非标准状态码直接关闭连接对扫描器非常有效能节省服务器资源。但要注意一些合法的爬虫如某些云监控可能使用非常规UA需要加白名单。白名单可以通过map实现map $http_user_agent $is_allowed_ua { ... }。HTTP方法限制对于纯API服务器或静态网站严格限制方法很有效。但对于需要处理PUT、DELETE等方法的RESTful API则需要调整。4. 高级防护组合使用Map与限流单一的匹配拦截是基础结合map和限流模块能构建更智能的防御。4.1 使用Map管理黑名单与评分我们可以创建一个“威胁评分”系统对请求的不同可疑特征进行加分超过阈值则阻断。http { # 初始化一个请求的威胁分数 map $remote_addr $request_threat_score { default 0; } # 定义多个map来检查不同特征并赋值分数 map $args $sql_score { default 0; ~* (\bunion\b.*\bselect) 10; ~* (--|\#) 3; } map $request_uri $traversal_score { default 0; ~* \.\./ 10; } map $http_user_agent $ua_score { default 0; ~* (sqlmap|nmap) 15; ~* - 5; } # 在server或location中通过set指令累加分数这是一个简化示例实际需要更复杂的逻辑可能借助Lua模块 # Nginx原生配置难以实现动态累加此处仅为思路展示。 # 更实用的做法是定义多个map每个map对应一个动作0通过1阻断然后用多个if判断。 }虽然原生Nginx难以实现复杂的累加逻辑但我们可以定义多个独立的map每个对应一个阻断条件然后用多个if语句判断逻辑更清晰。4.2 配置请求频率限制这是防御暴力破解和CC攻击的利器。http { # 定义一个限制请求速率的共享内存区名为one大小10m每秒最多10个请求 limit_req_zone $binary_remote_addr zoneone:10m rate10r/s; # 定义一个限制连接数的共享内存区 limit_conn_zone $binary_remote_addr zoneaddr:10m; } server { location /login { # 应用限流每秒最多处理10个请求突发队列不超过5个 limit_req zoneone burst5 nodelay; # 限制同一IP同时只能有1个连接 limit_conn addr 1; # 超过限制时返回429状态码 limit_req_status 429; limit_conn_status 429; proxy_pass http://backend_auth_server; } location /api/ { # 对API接口也可以实施较宽松的限流 limit_req zoneone burst20; proxy_pass http://backend_api_server; } }参数解读limit_req_zone ... zoneone:10m rate10r/s;在http块定义。$binary_remote_addr以二进制格式存储客户端IP更省空间。10m的内存可以存储大量IP的状态。rate10r/s表示每秒10个请求。limit_req zoneone burst5 nodelay;在location中应用。burst5允许超过速率后排队5个请求。nodelay表示对于排队中的请求也立即处理而不是延迟处理这适用于需要快速响应的场景如登录但会瞬间消耗更多后端资源。不加nodelay则会平滑处理。limit_conn_zone和limit_conn用于限制同一IP的并发连接数对防止慢速攻击或连接耗尽有效。5. 配置调试、测试与监控配置写好了如何验证其有效性并监控拦截效果5.1 配置语法检查与重载# 1. 每次修改配置文件后务必先检查语法 nginx -t # 2. 如果语法正确平滑重载配置不影响在线请求 nginx -s reload # 如果-t报错根据错误信息定位行号修改。常见错误括号不匹配、分号缺失、未知指令。5.2 模拟攻击测试使用curl命令模拟恶意请求测试拦截规则。# 测试目录遍历 curl -I http://yourdomain.com/../../etc/passwd curl -I http://yourdomain.com/%2e%2e/%2e%2e/etc/passwd # 测试SQL注入 curl -I http://yourdomain.com/?id1 OR 11 curl -I http://yourdomain.com/?id1 UNION SELECT null,user(),null # 测试敏感文件访问 curl -I http://yourdomain.com/.git/config curl -I http://yourdomain.com/backup.zip # 测试恶意User-Agent curl -I -A sqlmap/1.7.0 http://yourdomain.com/ curl -I -A - http://yourdomain.com/ # 观察返回状态码应为403、404、444或429限流时。5.3 日志分析与监控配置独立的拦截日志便于分析和告警。http { log_format security_block $remote_addr - [$time_local] $request $status $http_user_agent $http_referer Blocked_Reason: $request_uri; # 在拦截的if块中使用 # if ($sql_injection 1) { # access_log /var/log/nginx/security_block.log security_block; # return 403; # } }定期检查/var/log/nginx/security_block.log可以看到被拦截的请求详情和原因。可以将此日志接入ELKElasticsearch, Logstash, Kibana或类似监控系统设置告警规则例如同一IP在短时间内触发多次拦截则发送告警。5.4 常见问题排查实录问题1规则导致正常业务请求被拦截误报。排查查看Nginx错误日志error.log和上述安全拦截日志找到被误拦的请求URL和特征。解决细化规则将过于宽泛的正则表达式收紧。例如匹配union select时确保前后有单词边界\b。调整作用域将通用规则从location /移到更具体的、不涉及敏感功能的location块。或者为特定的安全接口如搜索创建单独的location并禁用某些规则。使用白名单对于已知的安全参数或路径使用if条件排除。例如if ($request_uri !~ ^/api/secure-search) { ... 应用规则 ... }。问题2攻击请求绕过了规则漏报。排查检查攻击载荷是否使用了双重编码、大小写混合、注释符分割等技巧。对比$request_uri和$uri的值。解决完善正则在map规则中增加对编码变体的匹配。例如同时匹配union、Union、UNION以及%55%4e%49%4f%4eURL编码。多层检测结合$args、$request_uri、$http_user_agent等多个变量进行综合判断提高单个特征的门槛如分数累加思路。考虑专业WAF对于复杂的、变形的攻击原生Nginx正则匹配可能力不从心。此时应考虑集成ModSecurity开源WAF或使用云WAF服务。问题3配置重载后部分规则不生效。排查确认配置文件语法检查通过。检查规则所在的server块或location块是否被正确匹配。使用curl测试时确保域名和端口正确。解决Nginx的if指令在location中有一些限制它是“重写”模块的一部分在某些上下文中行为可能不符合预期。如果遇到奇怪的问题尝试将复杂的if判断逻辑移到map指令中定义map的匹配更高效且行为更可预测。问题4限流配置未生效。排查检查limit_req_zone和limit_conn_zone定义的内存区名称、大小是否与location中引用的zone名称一致。检查客户端IP$binary_remote_addr是否能够正确获取如果前端有代理可能需要使用$http_x_forwarded_for但要小心伪造。解决确保limit_req和limit_conn指令放在需要限制的location块内。对于经过多层代理的情况需要配置realip模块来获取真实客户端IP用于限流。经过以上从原理到实战从配置到排查的完整梳理你应该已经能够为你的Nginx搭建起一道坚实的基础防线。记住安全是一个持续的过程配置上线后持续的日志监控和规则调优与最初的部署同样重要。这套配置不能防御所有攻击但它能像一张滤网帮你过滤掉绝大部分噪音和低层次威胁让你和你的后端应用能更专注于业务逻辑本身。