Nginx 安全配置与防护

Security Configuration and Hardening

概述

安全是Web服务器配置的重要环节。Nginx提供了多种安全机制来保护Web应用免受常见攻击。本文将详细介绍Nginx的安全配置方法、防护策略和最佳实践。

1. 基础安全配置

1.1 隐藏服务器信息

http {
    # 隐藏Nginx版本号
    server_tokens off;

    # 自定义Server头部
    more_set_headers "Server: WebServer";

    server {
        listen 80;
        server_name example.com;

        # 隐藏PHP版本信息
        location ~ \.php$ {
            fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include fastcgi_params;

            # 隐藏PHP头部
            fastcgi_hide_header X-Powered-By;
        }

        # 移除敏感头部
        location / {
            proxy_hide_header X-Powered-By;
            proxy_hide_header X-AspNet-Version;
            proxy_hide_header X-AspNetMvc-Version;
        }
    }
}

1.2 安全头部配置

server {
    listen 443 ssl http2;
    server_name secure.example.com;

    # SSL配置
    ssl_certificate /etc/ssl/certs/example.com.crt;
    ssl_certificate_key /etc/ssl/private/example.com.key;

    # 安全头部
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

    # 内容安全策略
    add_header Content-Security-Policy "
        default-src 'self';
        script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdnjs.cloudflare.com;
        style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
        font-src 'self' https://fonts.gstatic.com;
        img-src 'self' data: https:;
        connect-src 'self' https://api.example.com;
        frame-ancestors 'none';
        base-uri 'self';
        form-action 'self';
    " always;

    # 权限策略
    add_header Permissions-Policy "
        camera=(),
        microphone=(),
        geolocation=(),
        payment=(),
        usb=()
    " always;

    location / {
        root /var/www/secure;
        try_files $uri $uri/ =404;
    }
}

2. 访问控制

2.1 IP白名单和黑名单

http {
    # 定义可信IP范围
    geo $trusted_ip {
        default 0;
        192.168.1.0/24 1;
        10.0.0.0/8 1;
        172.16.0.0/12 1;
    }

    # 黑名单IP
    map $remote_addr $blocked_ip {
        default 0;
        ~^192\.168\.100\. 1;  # 阻止特定网段
        ~^203\.0\.113\. 1;    # 阻止特定网段
        1.2.3.4 1;            # 阻止特定IP
    }

    server {
        listen 80;
        server_name example.com;

        # 阻止黑名单IP
        if ($blocked_ip) {
            return 403 "Access denied";
        }

        # 管理区域只允许可信IP
        location /admin/ {
            if (!$trusted_ip) {
                return 403 "Admin access denied";
            }

            root /var/www/admin;
            try_files $uri $uri/ =404;
        }

        # 普通访问
        location / {
            root /var/www/html;
            try_files $uri $uri/ =404;
        }
    }
}

2.2 基于地理位置的访问控制

http {
    # GeoIP配置
    geoip_country /usr/share/GeoIP/GeoIP.dat;
    geoip_city /usr/share/GeoIP/GeoLiteCity.dat;

    # 允许的国家
    map $geoip_country_code $allowed_country {
        default no;
        US yes;
        CA yes;
        GB yes;
        DE yes;
        FR yes;
        JP yes;
        AU yes;
    }

    # 阻止的国家
    map $geoip_country_code $blocked_country {
        default no;
        CN yes;  # 根据需要调整
        RU yes;
        KP yes;
    }

    server {
        listen 80;
        server_name geo.example.com;

        # 阻止特定国家
        if ($blocked_country = yes) {
            return 403 "Access from your country is not allowed";
        }

        # 某些页面只允许特定国家
        location /restricted/ {
            if ($allowed_country = no) {
                return 403 "This content is not available in your region";
            }

            root /var/www/restricted;
            try_files $uri $uri/ =404;
        }

        location / {
            # 记录地理信息
            access_log /var/log/nginx/geo_access.log '$remote_addr - [$time_local] "$request" $status $body_bytes_sent "$geoip_country_name" "$geoip_city"';

            root /var/www/html;
            try_files $uri $uri/ =404;
        }
    }
}

3. 防护措施

3.1 DDoS防护

http {
    # 限制连接数
    limit_conn_zone $binary_remote_addr zone=conn_limit_per_ip:10m;
    limit_conn_zone $server_name zone=conn_limit_per_server:10m;

    # 限制请求频率
    limit_req_zone $binary_remote_addr zone=req_limit_per_ip:10m rate=5r/s;
    limit_req_zone $server_name zone=req_limit_per_server:10m rate=100r/s;

    # 大文件上传限制
    limit_req_zone $binary_remote_addr zone=upload_limit:10m rate=1r/m;

    server {
        listen 80;
        server_name protected.example.com;

        # 应用连接限制
        limit_conn conn_limit_per_ip 10;
        limit_conn conn_limit_per_server 1000;

        # 应用请求频率限制
        limit_req zone=req_limit_per_ip burst=20 nodelay;

        # 不同路径的不同限制
        location / {
            limit_req zone=req_limit_per_ip burst=10 nodelay;
            root /var/www/html;
            try_files $uri $uri/ =404;
        }

        location /api/ {
            limit_req zone=req_limit_per_ip burst=5 nodelay;
            proxy_pass http://api-backend;
        }

        location /upload/ {
            limit_req zone=upload_limit burst=3 nodelay;
            client_max_body_size 100m;
            proxy_pass http://upload-backend;
        }

        # 限制状态码处理
        error_page 429 @rate_limit;
        location @rate_limit {
            return 200 '{"error":"Rate limit exceeded","retry_after":60}';
            add_header Content-Type application/json;
            add_header Retry-After 60;
        }
    }
}

3.2 Bot防护

http {
    # 恶意Bot检测
    map $http_user_agent $is_bot {
        default 0;
        ~*bot 1;
        ~*crawler 1;
        ~*spider 1;
        ~*scraper 1;
        ~*wget 1;
        ~*curl 1;
        ~*python 1;
        ~*java 1;
        ~*apache 1;
        ~*httpclient 1;
    }

    # 好的Bot白名单
    map $http_user_agent $is_good_bot {
        default 0;
        ~*googlebot 1;
        ~*bingbot 1;
        ~*slurp 1;
        ~*duckduckbot 1;
        ~*baiduspider 1;
        ~*yandexbot 1;
        ~*facebookexternalhit 1;
        ~*twitterbot 1;
        ~*linkedinbot 1;
    }

    # 可疑请求模式
    map $request_uri $suspicious_request {
        default 0;
        ~*\.(php|asp|aspx|jsp)$ 1;
        ~*\.(sql|bak|backup|old)$ 1;
        ~*/\.\./.*$ 1;
        ~*union.*select 1;
        ~*concat.*\( 1;
        ~*base64_decode 1;
        ~*script.*alert 1;
    }

    server {
        listen 80;
        server_name bot-protected.example.com;

        # Bot访问控制
        location / {
            # 阻止恶意Bot(除非是好Bot)
            if ($is_bot = 1) {
                set $block_bot 1;
            }
            if ($is_good_bot = 1) {
                set $block_bot 0;
            }
            if ($block_bot = 1) {
                return 403 "Bot access denied";
            }

            # 阻止可疑请求
            if ($suspicious_request = 1) {
                return 403 "Suspicious request detected";
            }

            root /var/www/html;
            try_files $uri $uri/ =404;
        }

        # Robots.txt
        location = /robots.txt {
            access_log off;
            return 200 "User-agent: *\nDisallow: /admin/\nDisallow: /private/\nSitemap: https://example.com/sitemap.xml\n";
            add_header Content-Type text/plain;
        }
    }
}

3.3 SQL注入和XSS防护

http {
    # SQL注入检测
    map $args $sql_injection {
        default 0;
        ~*union.*select 1;
        ~*\bunion\b.*\bselect\b 1;
        ~*concat.*\( 1;
        ~*\bor\b.*\b1=1\b 1;
        ~*drop.*table 1;
        ~*insert.*into 1;
        ~*delete.*from 1;
        ~*update.*set 1;
        ~*\bexec\b.*\( 1;
        ~*script.*alert 1;
    }

    # XSS检测
    map $args $xss_attack {
        default 0;
        ~*<script 1;
        ~*javascript: 1;
        ~*onload= 1;
        ~*onerror= 1;
        ~*onclick= 1;
        ~*onmouseover= 1;
        ~*eval\( 1;
        ~*expression\( 1;
        ~*vbscript: 1;
    }

    # 文件包含攻击检测
    map $args $file_inclusion {
        default 0;
        ~*\.\./\.\. 1;
        ~*/etc/passwd 1;
        ~*/proc/self/environ 1;
        ~*php://filter 1;
        ~*php://input 1;
        ~*data: 1;
    }

    server {
        listen 80;
        server_name waf.example.com;

        # Web应用防火墙规则
        location / {
            # SQL注入防护
            if ($sql_injection = 1) {
                access_log /var/log/nginx/security.log;
                return 403 "SQL injection attempt detected";
            }

            # XSS防护
            if ($xss_attack = 1) {
                access_log /var/log/nginx/security.log;
                return 403 "XSS attack attempt detected";
            }

            # 文件包含攻击防护
            if ($file_inclusion = 1) {
                access_log /var/log/nginx/security.log;
                return 403 "File inclusion attempt detected";
            }

            # 检查请求体大小
            client_max_body_size 10m;

            root /var/www/html;
            try_files $uri $uri/ =404;
        }

        # 特殊文件保护
        location ~* \.(htaccess|htpasswd|ini|conf|bak|old|sql)$ {
            deny all;
        }

        # 隐藏目录保护
        location ~ /\. {
            deny all;
        }
    }
}

4. 文件和目录安全

4.1 文件类型限制

server {
    listen 80;
    server_name file-secure.example.com;

    # 禁止执行特定文件类型
    location ~* \.(php|php3|php4|php5|phtml|pl|py|jsp|asp|aspx|cgi)$ {
        # 只在特定目录允许PHP
        if ($uri !~ ^/app/) {
            return 403 "Execution not allowed";
        }

        # PHP处理(仅限/app/目录)
        fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    # 禁止访问备份和配置文件
    location ~* \.(bak|backup|old|orig|tmp|temp|~|#|conf|ini|log)$ {
        deny all;
    }

    # 源代码文件保护
    location ~* \.(inc|class|lib|src)$ {
        deny all;
    }

    # 上传目录安全
    location /uploads/ {
        # 禁止执行脚本
        location ~ \.(php|php3|php4|php5|phtml|pl|py|jsp|asp|aspx|cgi)$ {
            return 403 "Script execution not allowed in uploads directory";
        }

        # 只允许特定文件类型
        location ~* \.(jpg|jpeg|png|gif|pdf|doc|docx|txt)$ {
            expires 1d;
            add_header Cache-Control "public";
        }

        # 其他文件类型拒绝访问
        location ~* \.(.*)$ {
            return 403 "File type not allowed";
        }
    }

    location / {
        root /var/www/html;
        try_files $uri $uri/ =404;
    }
}

4.2 目录浏览控制

server {
    listen 80;
    server_name directory-secure.example.com;

    # 全局禁用目录浏览
    autoindex off;

    # 特定目录启用目录浏览(仅管理员)
    location /files/ {
        # IP访问控制
        allow 192.168.1.0/24;
        deny all;

        autoindex on;
        autoindex_exact_size off;
        autoindex_localtime on;
        autoindex_format html;
    }

    # 私有目录保护
    location /private/ {
        # 基本认证
        auth_basic "Private Area";
        auth_basic_user_file /etc/nginx/.htpasswd;

        # IP限制
        allow 192.168.1.0/24;
        deny all;

        root /var/www;
        try_files $uri $uri/ =404;
    }

    # 系统目录保护
    location ~ ^/(bin|etc|var|usr|tmp|proc|sys)/ {
        return 404;
    }

    location / {
        root /var/www/html;
        try_files $uri $uri/ =404;
    }
}

5. 监控和日志安全

5.1 安全日志配置

http {
    # 安全事件日志格式
    log_format security '$remote_addr - [$time_local] "$request" '
                       '$status $body_bytes_sent "$http_referer" '
                       '"$http_user_agent" "$geoip_country_code" '
                       '"$args" "$request_body"';

    # 错误日志格式
    log_format error_detailed '$time_local [error] $pid#$tid: $errmsg '
                             'client: $remote_addr, server: $server_name, '
                             'request: "$request", host: "$host"';

    server {
        listen 80;
        server_name monitored.example.com;

        # 记录所有访问
        access_log /var/log/nginx/security_access.log security;

        # 记录安全事件
        location / {
            # 记录可疑活动
            if ($args ~ "union|select|insert|delete|update|drop|exec|script") {
                access_log /var/log/nginx/security_threats.log security;
            }

            # 记录失败的认证尝试
            error_page 401 403 = @auth_failure;

            root /var/www/html;
            try_files $uri $uri/ =404;
        }

        location @auth_failure {
            access_log /var/log/nginx/auth_failures.log security;
            return 403 "Access denied";
        }
    }
}

5.2 实时安全监控

#!/bin/bash
# security-monitor.sh

SECURITY_LOG="/var/log/nginx/security_threats.log"
AUTH_LOG="/var/log/nginx/auth_failures.log"
ALERT_EMAIL="security@example.com"
TEMP_FILE="/tmp/security_alerts.tmp"

# 监控安全威胁
monitor_threats() {
    local recent_threats=$(tail -100 $SECURITY_LOG | grep "$(date '+%d/%b/%Y:%H')" | wc -l)

    if [ $recent_threats -gt 10 ]; then
        echo "HIGH THREAT LEVEL: $recent_threats security events in the last hour" >> $TEMP_FILE

        # 获取攻击者IP
        tail -100 $SECURITY_LOG | grep "$(date '+%d/%b/%Y:%H')" | \
        awk '{print $1}' | sort | uniq -c | sort -nr | head -5 >> $TEMP_FILE
    fi
}

# 监控认证失败
monitor_auth_failures() {
    local failed_attempts=$(tail -100 $AUTH_LOG | grep "$(date '+%d/%b/%Y:%H')" | wc -l)

    if [ $failed_attempts -gt 20 ]; then
        echo "HIGH AUTH FAILURE RATE: $failed_attempts failed attempts in the last hour" >> $TEMP_FILE

        # 获取攻击来源
        tail -100 $AUTH_LOG | grep "$(date '+%d/%b/%Y:%H')" | \
        awk '{print $1}' | sort | uniq -c | sort -nr | head -5 >> $TEMP_FILE
    fi
}

# 发送告警
send_alert() {
    if [ -s $TEMP_FILE ]; then
        {
            echo "Security Alert - $(date)"
            echo "================================"
            cat $TEMP_FILE
            echo ""
            echo "Recent security events:"
            tail -20 $SECURITY_LOG
        } | mail -s "Security Alert - Nginx" $ALERT_EMAIL

        rm -f $TEMP_FILE
    fi
}

# 主监控循环
while true; do
    > $TEMP_FILE
    monitor_threats
    monitor_auth_failures
    send_alert
    sleep 300  # 每5分钟检查一次
done

6. 自动防护脚本

6.1 自动IP封锁

#!/bin/bash
# auto-block.sh

LOG_FILE="/var/log/nginx/access.log"
BLOCK_LIST="/etc/nginx/conf.d/blocked_ips.conf"
THRESHOLD=100  # 每小时请求阈值
BAN_DURATION=3600  # 封锁时间(秒)

# 分析访问日志
analyze_log() {
    local current_hour=$(date +"%d/%b/%Y:%H")

    # 统计每个IP的请求数
    grep "$current_hour" $LOG_FILE | \
    awk '{print $1}' | \
    sort | uniq -c | \
    sort -nr | \
    while read count ip; do
        if [ $count -gt $THRESHOLD ]; then
            echo "Blocking IP $ip (requests: $count)"
            block_ip $ip
        fi
    done
}

# 封锁IP
block_ip() {
    local ip=$1
    local expire_time=$(($(date +%s) + $BAN_DURATION))

    # 检查是否已经被封锁
    if ! grep -q "deny $ip" $BLOCK_LIST; then
        echo "# Blocked at $(date) - expires at $(date -d @$expire_time)" >> $BLOCK_LIST
        echo "deny $ip;" >> $BLOCK_LIST
        echo "# End block for $ip" >> $BLOCK_LIST

        # 重新加载Nginx
        nginx -s reload

        # 记录封锁日志
        echo "$(date): Blocked IP $ip for $BAN_DURATION seconds" >> /var/log/nginx/auto_block.log
    fi
}

# 清理过期封锁
cleanup_expired() {
    local temp_file="/tmp/blocked_ips.tmp"
    local current_time=$(date +%s)

    > $temp_file

    while IFS= read -r line; do
        if [[ $line =~ ^#.*expires\ at ]]; then
            local expire_timestamp=$(echo $line | grep -o 'expires at .*' | cut -d' ' -f3-)
            local expire_time=$(date -d "$expire_timestamp" +%s)

            if [ $current_time -lt $expire_time ]; then
                echo "$line" >> $temp_file
                read -r next_line
                echo "$next_line" >> $temp_file
                read -r end_line
                echo "$end_line" >> $temp_file
            else
                # 跳过过期的封锁条目
                read -r next_line
                read -r end_line
                echo "$(date): Unblocked expired IP from $next_line" >> /var/log/nginx/auto_block.log
            fi
        else
            echo "$line" >> $temp_file
        fi
    done < $BLOCK_LIST

    mv $temp_file $BLOCK_LIST
    nginx -s reload
}

# 主函数
main() {
    # 创建封锁列表文件
    if [ ! -f $BLOCK_LIST ]; then
        echo "# Auto-generated blocked IPs" > $BLOCK_LIST
    fi

    while true; do
        analyze_log
        cleanup_expired
        sleep 3600  # 每小时运行一次
    done
}

main

6.2 安全配置验证

#!/bin/bash
# security-check.sh

CONFIG_FILE="/etc/nginx/nginx.conf"
SECURITY_SCORE=0
MAX_SCORE=100

check_server_tokens() {
    if grep -q "server_tokens off" $CONFIG_FILE; then
        echo "✓ Server tokens disabled"
        SECURITY_SCORE=$((SECURITY_SCORE + 10))
    else
        echo "✗ Server tokens not disabled"
    fi
}

check_ssl_config() {
    if grep -q "ssl_protocols.*TLSv1\.3" $CONFIG_FILE; then
        echo "✓ TLS 1.3 enabled"
        SECURITY_SCORE=$((SECURITY_SCORE + 15))
    else
        echo "✗ TLS 1.3 not enabled"
    fi

    if grep -q "ssl_session_tickets off" $CONFIG_FILE; then
        echo "✓ SSL session tickets disabled"
        SECURITY_SCORE=$((SECURITY_SCORE + 5))
    else
        echo "✗ SSL session tickets not disabled"
    fi
}

check_security_headers() {
    local headers=("X-Frame-Options" "X-Content-Type-Options" "X-XSS-Protection" "Strict-Transport-Security")

    for header in "${headers[@]}"; do
        if grep -q "$header" /etc/nginx/sites-available/*; then
            echo "✓ $header configured"
            SECURITY_SCORE=$((SECURITY_SCORE + 5))
        else
            echo "✗ $header not configured"
        fi
    done
}

check_rate_limiting() {
    if grep -q "limit_req_zone" $CONFIG_FILE; then
        echo "✓ Rate limiting configured"
        SECURITY_SCORE=$((SECURITY_SCORE + 15))
    else
        echo "✗ Rate limiting not configured"
    fi
}

check_access_control() {
    if grep -q "allow\|deny" /etc/nginx/sites-available/*; then
        echo "✓ Access control configured"
        SECURITY_SCORE=$((SECURITY_SCORE + 10))
    else
        echo "✗ Access control not configured"
    fi
}

check_file_permissions() {
    local config_perms=$(stat -c "%a" $CONFIG_FILE)
    if [ "$config_perms" = "644" ] || [ "$config_perms" = "640" ]; then
        echo "✓ Nginx config file permissions are secure"
        SECURITY_SCORE=$((SECURITY_SCORE + 5))
    else
        echo "✗ Nginx config file permissions are not secure ($config_perms)"
    fi
}

generate_report() {
    echo ""
    echo "==================================="
    echo "Nginx Security Assessment Report"
    echo "==================================="
    echo "Security Score: $SECURITY_SCORE/$MAX_SCORE"
    echo ""

    if [ $SECURITY_SCORE -ge 80 ]; then
        echo "Security Level: GOOD"
    elif [ $SECURITY_SCORE -ge 60 ]; then
        echo "Security Level: MODERATE"
    else
        echo "Security Level: POOR"
    fi

    echo ""
    echo "Recommendations:"
    if [ $SECURITY_SCORE -lt $MAX_SCORE ]; then
        echo "- Review and implement missing security configurations"
        echo "- Regularly update Nginx and system packages"
        echo "- Monitor security logs for suspicious activities"
        echo "- Consider implementing Web Application Firewall (WAF)"
    fi
}

# 运行检查
echo "Running Nginx Security Check..."
echo ""

check_server_tokens
check_ssl_config
check_security_headers
check_rate_limiting
check_access_control
check_file_permissions

generate_report

小结

通过本文的学习,你应该掌握:

  1. 基础安全配置和信息隐藏技巧
  2. 安全头部和内容安全策略配置
  3. 访问控制和地理位置限制
  4. DDoS防护和Bot防护措施
  5. SQL注入和XSS攻击防护
  6. 文件和目录安全控制
  7. 安全监控和自动防护脚本
  8. 安全配置的验证和评估

下一篇文章将介绍访问控制与限流的详细配置。

powered by Gitbook© 2025 编外计划 | 最后修改: 2025-08-29 15:40:15

results matching ""

    No results matching ""