史上最全 nginx 安装升级安全配置

背景

nginx 是常用的代理服务软件,代理层通常是比较靠近用户的,代理层的安全性至关重要,需要我们日常工作中对代理层做好安全相关配置和升级。

这里选择部署 openrestry,OpenResty 是以 Nginx 为核心的 Web 开发平台,可以解析执行 Lua 脚本,方便后期基于 nginx 做 web 开发或者自研 WAF。

1. 下载 openrestry

访问官网 https://openresty.org/cn/ 下载最新版本 openrestry

cd /root/
wget https://openresty.org/download/openresty-1.21.4.1.tar.gz

2. nginx 编译安全配置

tar xvf openresty-1.21.4.1.tar.gz
cd /root/openresty-1.21.4.1/bundle/nginx-1.21.4/

# - 1.隐藏版本
vim src/core/nginx.h
#define NGINX_VERSION      "6666"
#define NGINX_VER          "FW/" NGINX_VERSION ".6"

#define NGINX_VAR          "FW"

# - 2.修改头部
vim  src/http/ngx_http_header_filter_module.c
# 49 static u_char ngx_http_server_string[] = "Server: FW" CRLF;

# - 3.修改错误页响应头部(response header)
vim src/http/ngx_http_special_response.c
# 22 "<hr><center>FW</center>" CRLF
# ...
# 29 "<hr><center>FW</center>" CRLF
# ...
# 36 "<hr><center>FW</center>" CRLF

3. 增加三方模块

3.1 动态配置 upstream 的模块 nginx_upstream_check_module

检出 github 代码

cd /root
git clone https://github.com/yzprofile/ngx_http_dyups_module.git

3.2 增加 upstream 监控检查模块 nginx_upstream_check_module

检出 github 代码

git clone https://github.com/yaoweibin/nginx_upstream_check_module.git

3.3 增加 nginx 监控模块 nginx-module-vts

检出 github 代码

https://github.com/vozlt/nginx-module-vts.git

4. 编译安全 nginx

编译之前需要给 nginx 打补丁,因为 nginx-module-vts 模块监控需要

切到nginx源码目录
cd /root/openresty-1.21.4.1/bundle/nginx-1.21.4/
打补丁
patch -p1 < /root/nginx_upstream_check_module/check_1.20.1+.patch 

编译安全 nginx

cd /root/openresty-1.21.4.1/

./configure --prefix=/apps/nginx --with-http_realip_module  --with-http_v2_module --with-http_image_filter_module --with-http_iconv_module  --with-stream_realip_module --with-stream --with-stream_ssl_module --with-stream_geoip_module --with-http_slice_module --with-http_sub_module --add-module=/root/ngx_http_dyups_module --add-module=/root/nginx_upstream_check_module --with-http_stub_status_module --with-http_geoip_module --with-http_gzip_static_module --add-module=/root/nginx-module-vts

make

make install

如果有报如下错,看下是否补丁没有打

/root/nginx-module-vts/src/ngx_http_vhost_traffic_status_display_json.c: In function ‘ngx_http_vhost_traffic_status_display_set_upstream_grou’:
/root/nginx-module-vts/src/ngx_http_vhost_traffic_status_display_json.c:604:61: error: ‘ngx_http_upstream_rr_peer_t’ {aka ‘struct ngx_http_upstream_rr_peer_s’} has no member named ‘check_index’; did you mean ‘checked’?
                 if (ngx_http_upstream_check_peer_down(peer->check_index)) {
                                                             ^~~~~~~~~~~
                                                             checked
make[2]: *** [objs/Makefile:3330: objs/addon/src/ngx_http_vhost_traffic_status_display_json.o] Error 1
make[2]: Leaving directory '/root/openresty-1.21.4.1/build/nginx-1.21.4'
make[1]: *** [Makefile:10: build] Error 2
make[1]: Leaving directory '/root/openresty-1.21.4.1/build/nginx-1.21.4'
make: *** [Makefile:9: all] Error 2
[root@localhost.localdomain openrest

解决办法:

yum install patch

cd /root/openresty-1.21.4.1/bundle/nginx-1.21.4/

patch -p1 < /root/nginx_upstream_check_module/check_1.20.1+.patch    

启动 nginx

启动:
 /apps/nginx/nginx/sbin/nginx -c /apps/nginx/nginx/conf/nginx.conf
 
 reload:
  /apps/nginx/nginx/sbin/nginx -s reload -c /apps/nginx/nginx/conf/nginx.conf
 

5. nginx 升级

在工作中我们会遇到 nginx 漏洞比如 openssl 漏洞因而需要升级 nginx 版本,或者因为 nginx 某些特性我们需要升级 nginx。 升级有两种方式(这里主要聊的是部署在虚机里的,容器重新打个镜像即可): 一是开新虚机直接升级 nginx 版本,然后把 nginx 配置 copy 过来启动,验证没问题后挂载到 LB 上,逐步替换老的 nginx; 二是通过在原来的机器上升级,这里主要谈谈第二种方式。 升级步骤:

前提: 
1. 有多台nginx,且从LB上摘掉一台不影响服务
2. pid路径: /data/data/nginx/conf/nginx.pid;
3. conf目录路径独立: /data/data/nginx/conf/


升级步骤:
1. 从LB上摘除要升级的nginx,观察nginx日志确保没有流量后做下一步动作
2. configure 时指定新的./configure --prefix=/apps/nginx_new 目录
3. 安装完后把nginx_new 目录下的conf 做软连指向/data/data/nginx/conf/
4. nginx reload :  /apps/nginx_new/nginx/sbin/nginx -s reload -c /data/data/nginx/conf//nginx.conf
5. 验证升级后的nginx,如果没有问题然后挂载到LB上,继续重复上述步骤完成其他nginx升级

6. nginx 安全配置

6.1 信息泄露,关闭 nginx 版本号显示

http{
server_tokens off
....

6.2 禁用不需要的 Nginx 模块

自动安装的 Nginx 会内置很多模块,并不是所有的模块都需要,对于非必须的模块可以禁用,如 autoindex module ,下面展示如何禁用

# ./configure --without-http_autoindex_module
# make
# make install

6.3 控制资源和限制

为了防止对 Nginx 进行潜在的 DOS 攻击,可以为所有客户端设置缓冲区大小限制,配置如下:

## Start: Size Limits & Buffer Overflows ##
client_body_buffer_size  1K;
client_header_buffer_size 1k;
client_max_body_size 1k;
large_client_header_buffers 2 1k;
## END: Size Limits & Buffer Overflows ##

client_body_buffer_size 1k;:(默认 8k 或 16k)这个指令可以指定连接请求实体的缓冲区大小。如果连接请求超过缓存区指定的值,那么这些请求实体的整体或部分将尝试写入一个临时文件。 client_header_buffer_size 1k;:指定客户端请求头部的缓冲区大小。绝大多数情况下一个请求头不会大于 1k,不过如果有来自于 wap 客户端的较大的 cookie 它可能会大于 1k,Nginx 将分配给它一个更大的缓冲区,这个值可以在 large_client_header_buffers 里面设置。 client_max_body_size 1k;:指令指定允许客户端连接的最大请求实体大小,它出现在请求头部的 Content-Length 字段。如果请求大于指定的值,客户端将收到一个”Request Entity Too Large” (413)错误。记住,浏览器并不知道怎样显示这个错误。 large_client_header_buffers 2 1k;:指定客户端一些比较大的请求头使用的缓冲区数量和大小。请求字段不能大于一个缓冲区大小,如果客户端发送一个比较大的头,nginx 将返回”Request URI too large” (414)。同样,请求的头部最长字段不能大于一个缓冲区,否则服务器将返回”Bad request” (400)。缓冲区只在需求时分开。默认一个缓冲区大小为操作系统中分页文件大小,通常是 4k 或 8k,如果一个连接请求最终将状态转换为 keep-alive,它所占用的缓冲区将被释放。

你还需要控制超时来提高服务器性能并与客户端断开连接,配置如下:

## Start: Timeouts ##
client_body_timeout   10;
client_header_timeout 10;
keepalive_timeout     5 5;
send_timeout          10;
## End: Timeouts ##

client_body_timeout 10;:指令指定读取请求实体的超时时间。这里的超时是指一个请求实体没有进入读取步骤,如果连接超过这个时间而客户端没有任何响应,Nginx 将返回一个”Request time out” (408)错误。 client_header_timeout 10;:指令指定读取客户端请求头标题的超时时间。这里的超时是指一个请求头没有进入读取步骤,如果连接超过这个时间而客户端没有任何响应,Nginx 将返回一个”Request time out” (408)错误。 keepalive_timeout 5 5; :参数的第一个值指定了客户端与服务器长连接的超时时间,超过这个时间,服务器将关闭连接。参数的第二个值(可选)指定了应答头中 Keep-Alive: timeout=time 的 time 值,这个值可以使一些浏览器知道什么时候关闭连接,以便服务器不用重复关闭,如果不指定这个参数,nginx 不会在应答头中发送 Keep-Alive 信息。(但这并不是指怎样将一个连接 “Keep-Alive”)参数的这两个值可以不相同。 send_timeout 10;:指定了发送给客户端应答后的超时时间,Timeout 是指没有进入完整 established 状态,只完成了两次握手,如果超过这个时间客户端没有任何响应,nginx 将关闭连接。

6.4 禁用所有不需要的 HTTP 方法

禁用所有不需要的 HTTP 方法,下面设置意思是只允许 GET、HEAD、POST 方法,过滤掉 DELETE 和 TRACE 等方法。

location / {
limit_except GET HEAD POST { deny all; }
}

另一种方法是在 server 块 设置,不过这样是全局设置的,要注意评估影响

if ($request_method !~ ^(GET|HEAD|POST)$ ) {
    return 444; }

6.5 防止 Host 头攻击

添加一个默认 server,当 host 头被修改匹配不到 server 时会跳到该默认 server,该默认 server 直接返回 403 错误。

server {
       listen 80 default;

       server_name _;

       location / {
            return 403;
       }
}

6.6 配置 SSL 和 cipher suites

Nginx 默认允许使用不安全的旧 SSL 协议,ssl_protocols TLSv1 TLSv1.1 TLSv1.2,建议做如下修改:

ssl_protocols TLSv1.2 TLSv1.3;

此外要指定 cipher suites ,可以确保在 TLSv1 握手时,使用服务端的配置项,以增强安全性。

ssl_prefer_server_ciphers on

6.7 防止图片盗链

图片或 HTML 盗链的意思是有人直接用你网站的图片地址来显示在他的网站上。最终的结果,你需要支付额外的宽带费用。这通常是在论坛和博客。我强烈建议您封锁,并阻止盗链行为。

location /images/ {
  valid_referers none blocked www.example.com example.com;
   if ($invalid_referer) {
     return   403;
   }
}

例如:重定向并显示指定图片。

valid_referers blocked www.example.com example.com;
if ($invalid_referer) {
rewrite ^/images/uploads.*.(gif|jpg|jpeg|png)$ http://www.examples.com/banned.jpg last
}

6.8 目录限制

你可以对指定的目录设置访问权限。所有的网站目录应该一一的配置,只允许必须的目录访问权限。

你可以通过 IP 地址来限制访问目录

location /docs/ {
  ## block one workstation
  deny    192.168.1.1;
 
  ## allow anyone in 192.168.1.0/24
  allow   192.168.1.0/24;
 
  ## drop rest of the world
  deny    all;
}

还可以通过密码保护目录 首先创建密码文件并增加 “user” 用户

mkdir /app/nginx/nginx/conf/.htpasswd/
htpasswd -c /app/nginx/nginx/conf/.htpasswd/passwd user

编辑 nginx.conf, 加入需要保护的目录

location ~ /(personal-images/.*|delta/.*) {
  auth_basic  "Restricted";
  auth_basic_user_file   /usr/local/nginx/conf/.htpasswd/passwd;
}

一旦密码文件已经生成,你也可以用以下的命令来增加允许访问的用户

htpasswd -s /usr/local/nginx/conf/.htpasswd/passwd userName

6.9 拒绝一些 User-Agents

拒绝一些 User-Agents 你可以很容易地阻止 User-Agents,如扫描器,机器人以及滥用你服务器的垃圾邮件发送者。

## Block download agents ##
if ($http_user_agent ~* LWP::Simple|BBBike|wget) {
return 403;
}
##

6.10 nginx 去外网 IP

nginx 如果有漏洞,可能存在远程执行的行为,通过 ip 下载攻击工具到 nginx 机器,把 nginx 机器做跳板做攻击。 通过 LB 代理 nginx,流量先经过 LB 再到 nginx,不要把 nginx 直接通过外网 ip 对外。

6.11 配置合理响应头

为了进一步加强 Nginx web 的性能,可以添加几个不同的响应头 X-Frame-Options 可以使用 X-Frame-Options HTTP 响应头指示是否应允许浏览器在 \<frame\> 或 \<iframe\> 中呈现页面。 这样可以防止点击劫持攻击。 配置文件中添加:

add_header X-Frame-Options "SAMEORIGIN";

Strict-Transport-Security HTTP Strict Transport Security,简称为 HSTS。它允许一个 HTTPS 网站,要求浏览器总是通过 HTTPS 来访问它,同时会拒绝来自 HTTP 的请求,操作如下:

add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload";

CSP Content Security Policy (CSP) 保护你的网站避免被使用如 XSS,SQL 注入等手段进行攻击,操作如下:

add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;

服务用户提供的内容时,包含 X-Content-Type-Options: nosniff 头选项,配合 Content-Type: 头选项,来禁用某些浏览器的 content-type 探测.

add_header X-Content-Type-Options nosniff;

X-XSS-Protection: 表示启用 XSS 过滤(禁用过滤为 X-XSS-Protection: 0),mode=block 表示若检查到 XSS 攻击则停止渲染页面

add_header X-XSS-Protection "1; mode=block";

6.12 全站 https

将所有 http 跳转至 https

server {
  listen 80 default_server;
  listen [::]:80 default_server;
  server_name .example.com;
  return 301 https://$host$request_uri;
}

6.13 控制并发连接数

可以通过 ngx_http_limit_conn_module 模块限制一个 IP 的并发连接数

http {
    limit_conn_zone $binary_remote_addr zone=limit1:10m;

    server {
        listen 80;
        server_name example.com;
           
        root /apps/project/webapp;
        index index.html;
        location / {
            limit_conn limit 10;
        }
        access_log /data/log/nginx/nginx_access.log main;
    }
}

limit_conn_zone: 设定保存各个键 (例如 $binary_remote_addr) 状态的共享内存空间的参数,zone = 空间名字:大小 大小的计算与变量有关,例如 $binary_remote_addr 变量的大小对于记录 IPV4 地址是固定的 4 bytes,而记录 IPV6 地址时固定的 16 bytes,存储状态在 32 位平台中占用 32 或者 64 bytes,在 64 位平台中占用 64 bytes。1m 的共享内存空间可以保存大约 3.2 万个 32 位的状态,1.6 万个 64 位的状态 limit_conn: 指定一块已经设定的共享内存空间 (例如 name 为 limit1 的空间),以及每个给定键值的最大连接数

上边的例子表示同一 IP 同一时间只允许 10 个连接

当有多个 limit_conn 指令被配置时,所有的连接数限制都会生效

http {
    limit_conn_zone $binary_remote_addr zone=limit1:10m;
    limit_conn_zone $server_name zone=limit2:10m;
    
    server {
        listen 80;
        server_name example.com;
        
        root /data/project/webapp;
        index index.html;
        
        location / {
            limit_conn limit1 10;
            limit_conn limit2 2000;
        }
    }
}

上边的配置不仅会限制单一 IP 来源的连接数为 10,同时也会限制单一虚拟服务器的总连接数为 2000

6.14 连接权限控制

实际上 nginx 的最大连接数是 worker_processes 乘以 worker_connections 的总数。

也就是说,下面的这个配置,就是 4X65535,一般来说,我们会强调 worker_processes 设置成和核数相等,worker_connections 并没有要求。但是同时这个设置其实给了攻击者空间,攻击者是可以同时发起这么多个连接,把你服务器搞跨。所以,我们应该更合理的配置这两个参数。

user  www;
worker_processes  4;
error_log  /data/log/nginx/nginx_error.log  crit;
pid        /data/data/nginx/conf/nginx.pid;
events {
    use epoll;
    worker_connections 65535;
}

不过,也不是完全没有办法限制,在 nginx0.7 开始,出了两个新的模块:

HttpLimitReqModul:    限制单个 IP 每秒请求数
HttpLimitZoneModule:     限制单个 IP 的连接数

这两个模块,要先在 http 层定义,然后在 location, server, http 上下文中作限制,他们用的是限制单 ip 访问的漏桶算法,也就是说超过定义的限制会报 503 错误,这样爆发的 cc 攻击就全部被限制住了。当然,有些时候可能是某个公司同一个 ip 有几十人一起访问网站,这是有可能被误伤的,做好 503 报错回调是很有必要的。

先看 HttpLimitReqModul:

http {
    limit_req_zone $binary_remote_addr zone=test_req:10m rate=20r/s;
     server {
         location /download/ {
            limit_req zone=test_req burst=5 nodelay;
         }
     }
}

上面 http 层的就是定义,这是一个名为 test_req 的 limit_req_zone 空间,用来存储 session 数据,大小是 10M 内存,1M 大约可以存 16000 个 ip 回话,看你访问量有多少就设多少。以 $binary_remote_addr 为 key, 这个定义是客户端 IP,可以改成 $server_name 等其他,限制平均每秒的请求为 20 个,写成 20r/m 就是每分钟了,也是看你访问量。

下面 location 层就是应用这个限制了,对应上面的定义,对访问 download 文件夹的请求,限制每个 ip 每秒不超过 20 个请求,漏桶数 burst 为 5,brust 的意思就是,如果第 1,2,3,4 秒请求为 19 个,第 5 秒的请求为 25 个是被允许的。但是如果你第 1 秒就 25 个请求,第 2 秒超过 20 的请求返回 503 错误。nodelay,如果不设置该选项,第 1 秒 25 个请求时,5 个请求放到第 2 秒执行,设置 nodelay,25 个请求将在第 1 秒执行。

就这个限制定义而言,把每个 IP 限制了请求数,对于海量的 cc 请求攻击,效果明显,例如限制到 1r/s 每秒一次请求,那就更明显了,不过也正如开头所说,对于大公司多人统一 IP 同时访问,难免出现误伤,所以还是得多考虑。

然后再看 HttpLimitZoneModule:

http {
    limit_conn_zone test_zone $binary_remote_addr 10m;
    server {
        location /download/ {
            limit_conn test_zone 10;
            limit_rate 500k;
        }
    }
}

和上面的类似,上面 http 层就是总定义,这是一个名为 test_zone 的 limit_conn_zone 空间,大小也是 10M,key 还是客户端 IP 地址,不过这个没有限制次数,改下面定义去了。

下面 location 层就是真正定义了,因为 key 定义是客户端 ip,所以 limit_conn 就是一个 IP 限制了 10 个连接,如果是 $server_name,那就是一个域名 10 个连接。然后下面 limit_rate 就是限制一个连接的带宽,如果一个 ip 两个连接,就是 500x2k,这里是 10,那就是最多可以有 5000K 速度给到这个 ip 了。

6.15 定期升级

nginx 本身和 nginx 使用的三方类库,随着时间的发展和技术的迭代可能会存在重大漏洞,我们负责 nginx 相关服务的需要定期关注 nginx 版本更新和相关漏洞,选择性的升级。

posted @ 2022-11-09 17:16  youqc  阅读(651)  评论(0编辑  收藏  举报