南琴浪博客

本博客 Nginx 配置(系列一) 安全篇

10/23/2017

首先提一下,本文标题所提到的安全并不是指防C、防D的安全,而是 Web 端和客户端间的数据传输的安全。

我计划以三篇文章介绍一下本博客所用 Nginx 的相关配置,现在先从安全篇开始。

隐藏不需要的信息

Nginx 默认会在响应头中加入“表明自己是 “Nginx” 和自己当前的版本号”的信息,这些信息通常并不必要、显得多余,却往往会让攻击者了解到你的 Web 服务对应的漏洞,让攻击者有机可乘。隐藏版本号可以提高安全性。要隐藏 Nginx 的版本号,只需加入下面这行配置:

server_tokens off;

若想要彻底隐藏所用 Web Server 软件的信息,需要修改 Nginx 源码,把 Server Name 对应部分更改后再编译,具体步骤请咨询谷歌老爹(或许我之后也会写个帖子说说)。

并且,不仅仅是软件版本号,如果想要更加彻底的隐藏 Web Server 相关信息,例如 404、502 等状态页也需要自定义。

禁用不需要的方式

由于我的博客只需要 GET、POST 这两种 Request Method,我完全没必要放开例如 TRACE(用于网络诊断)这样的请求方式,说不定哪天就翻车。所以我指定 GET 和 POST 之外的请求,均直接返回 444 状态码(Nginx 的 444 状态码,表示不返回响应正文,立即断开连接)。

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

限制并发连接数

Nginx 默认的配置文件 nginx.conf 中有如下描述

#If enable limit_conn_zone,add "limit_conn perip 10;" to server section.
#limit_conn_zone $binary_remote_addr zone=perip:10m;

当然,默认配置文件中这项配置是注释掉的。为了限制并发连接数(也能防范下 CC 攻击),我进行了如下配置

# 在全局配置 nginx.conf 中
http {
    #If enable limit_conn_zone,add "limit_conn perip 10;" to server section.
    limit_conn_zone $binary_remote_addr zone=perip:10m;
    . . .
    include sometimesnaive.conf;
}

# 在站点配置 sometimesnaive.conf 中
server {
    limit_conn perip 10;
}

关于 limit 模块的更多说明,也可参考我的 这篇文章

禁止指定 IP 访问

Nginx 通过 allowdeny 参数,放行或禁止特定 IP 访问

location / {
    allow 104.27.148.54;
    allow 104.27.149.54;
    allow 2400:cb00:2048:1:0:0:681b:9436;
    allow 2400:cb00:2048:1:0:0:681b:9536;
    deny all;
}

allow 和 deny 按照声明的顺序进行匹配,首条匹配 IP 的访问规则将被启用。在以上例子中,我以 cloudflare 的四个 IP 作为例子。如果访问者是这四个 IP 中的一个就会被放行,其它所有 IP 均会被禁止访问,返回 Forbidden 状态页。

也可以利用 blacklist.conf 来设置禁止访问站点的黑名单:

include blacklist.conf;

blacklist.conf 中写入如下内容

allow ip;
allow ip;
allow ip;
. . .
deny all;

当然,如果不是特殊需求,更建议直接利用 iptables 等防火墙,drop 掉黑名单 IP 的连接。Nginx 的匹配也会消耗 Server 的性能。

禁止网络爬虫

if ($http_user_agent ~* "qihoobot|Baiduspider|Mediapartners-Google|Adsbot-Google|Feedfetcher-Google|Yahoo! Slurp|Yahoo! Slurp China|YoudaoBot|Sosospider|Sogou spider|Sogou web spider|MSNBot|ia_archiver|Tomato Bot") {
    return 444;
}

关于使用 if 禁止网络爬虫的更多说明,也可参考我的 这篇文章

加入响应头配置

HTTP 响应头(HTTP Response Header),是 Web 端在接受客户端的合法请求后,返回给客户端的消息头部分。从安全方面考虑,我为本站点加入了以下响应头:

add_header Public-Key-Pins 'pin-sha256="sE6SdAkyKReY/Yj6Jt4h2JeD9SHYlqiEi1nly8fgCh4="; pin-sha256="Prac0f3S3Qj0Okc3wmWeJFxT3/8kgAKf1XpZh0SjdVQ="; pin-sha256="YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg="; max-age=31536000; includeSubDomains' always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "deny" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Xss-Protection "1;mode=block" always;
add_header Content-Security-Policy "script-src 'self' https://www.google-analytics.com https://github.com 'unsafe-inline' 'unsafe-eval'; style-src 'self' https://github.com; img-src 'self' https://github.com; font-src 'self' https://github.com; media-src 'none'; object-src 'none'; connect-src 'none'; child-src 'self' https://github.com http://www.w3.org; sandbox 'none'";

关于 HTTP 响应头的更多细节,请参看我的另一篇文章 那些站点安全相关的 HTTP 响应头

启用 HTTPS

超文本传输安全协议(Hypertext Transfer Protocol Secure),简称 HTTPS,是现在各个站点的标配了。加上了 HTTPS 的站点也能在搜索引擎中获得更高的权重。

ssl  on;
ssl_protocols  TLSv1.2 TLSv1.3;
ssl_certificate  /home/site/crt/naive/LetsEncryptecc-chained.cer;
ssl_certificate_key  /home/site/crt/naive/LetsEncryptecc.key;
ssl_prefer_server_ciphers  on;
ssl_dhparam  /home/site/crt/naive/dh.4096.pem;
ssl_ciphers  'TLS13-AES-128-GCM-SHA256 TLS13-CHACHA20-POLY1305-SHA256 TLS13-AES-256-GCM-SHA384 TLS13-AES-128-CCM-8-SHA256 TLS13-AES-128-CCM-SHA256 EECDH+ECDSA+AES128 EECDH+aRSA+AES128 RSA+AES128 EECDH+ECDSA+AES256 EECDH+aRSA+AES256 RSA+AES256';

关于 HTTPS 的更多细节,请参看我的另一篇文章 Nginx 启用 HTTPS 站点

当然,还有其它一些本帖尚未谈及的 HTTPS 安全相关的方案,例如 Certificate Transparency 和 OCSP Stapling,我会以后补充进来(已写完,传送门如下:
2017-10-24 更新:Nginx 配置 OCSP Stapling
2017-10-28 更新:Certificate Transparency 概述

那么,本文就先这样吧。