背景
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版本更新和相关漏洞,选择性的升级。