标签 nginx 下的文章

反向代理配置:

server {
    listen 80;
    server_name mirrors.maqian.work;

    location / {
        proxy_pass http://mirrors.aliyun.com;
        proxy_redirect off;
        proxy_set_header Host $proxy_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

在浏览器访问mirrors.maqian.work时:

  • $host: mirrors.maqian.work
  • $proxy_host: mirrors.aliyun.com

一、关于Basic Authentication

HTTP本身提供了一种基础的认证方式Basic Authentication,使得访问者在访问时需要输入账号密码认证之后才能访问到页面:

如果没有输入密码访问,服务器将会返回401

当服务端开启认证后,通过认证的方式有两种:

  1. 在访问URL的时候主动代码账号和密码信息,如http://user:password@www.baidu.com,其中user是账户名,password是密码。
  2. 在请求头部加上Authorization头部,并将值设置为Basic xxxx,xxxx是账号和密码的base64编码结果。

一般我们使用的是第二种方式:如果网站需要认证,浏览器会自动会弹出登录框,手动输入账号密码后浏览器在头部带上认证信息访问。以下是一个抓包示例:

最下面一行的Authorization是授权信息,最后的bWFxaWFuOnF3ZTEyMw==即是经过base64加密后的账号密码。使用base64命令解码即可得到认证信息:

> printf "bWFxaWFuOnF3ZTEyMw==" | base64 -d
maqian:qwe123

关于认证状态保持

浏览器如何做到保持状态的呢?根据抓包分析发现,认证成功后,浏览器会自动记住当前页面的账号信息。后续的每一个请求,浏览器都自动加上认证头部,无需每次都再输入账号密码,这样就达到了认证状态保持的效果。

二、使用nginx开启basic authentication

nginx默认提供了ngx_http_auth_basic_module模块以支持basic authentication,该指令为:

location / {
    auth_basic "auth test";
    auth_basic_user_file conf/htpasswd;
}

其中auth_basic认证信息的提示语句,他的值可以是一个字符串或者off,如果值是off表示访问无需认证,如果值是一个字符串,表示需要提供认证信息才能访问。并且大部分浏览器会把这个字符串返回到前端,测试发现chrome不会,edge会:

auth_basic_user_file的值是密钥文件的路径,里面保存了所有的账号密码,内容格式为:

# comment
name1:password1
name2:password2:comment
name3:password3

nginx支持以下密码类型:

  1. encrypted with the crypt() function; can be generated using the “htpasswd” utility from the Apache HTTP Server distribution or the “openssl passwd” command;
  2. hashed with the Apache variant of the MD5-based password algorithm (apr1); can be generated with the same tools;
  3. specified by the “{scheme} data” syntax (1.0.3+) as described in RFC 2307; currently implemented schemes include PLAIN (an example one, should not be used), SHA (1.3.13) (plain SHA-1 hashing, should not be used) and SSHA (salted SHA-1 hashing, used by some software packages, notably OpenLDAP and Dovecot).

通常,我们可以使用htpasswdopenssl命令来生成密码,如:

> openssl passwd -crypt qwe123 # qwe123是生成的密码信息
wMEiqshd7n3YQ

指令放置上下文

这个指令可以放置在:

http, server, location, limit_except

三、参考

Module ngx_http_auth_basic_module

查看nginx日志,发现有报错信息:

2019/07/16 17:34:42 [crit] 4397#0: *349 open() "/fastcgi_temp/5/00/0000000005" failed (13: Permission denied) while reading upstream

查看对应目录的权限,发现所属用户是nobody,而实际运行nginx的是wwwuser

root      1842  0.0  8.5 410716 332500 ?       Ss   17:10   0:01 nginx: master process /usr/local/nginx/sbin/nginx -c /etc/nginx/nginx.conf
wwwuser   4397  0.0  8.5 412288 332828 ?       S    17:33   0:00 nginx: worker process
wwwuser   4398  0.0  8.5 411776 332360 ?       S    17:33   0:00 nginx: worker process
wwwuser   4399  0.0  8.5 412800 330336 ?       S    17:33   0:00 nginx: cache manager process

因此问题原因应该是:nginx最初始时以nobody身份启动过,创建了缓存所属用户是nobody,后面改动nginx的运行用户为wwwuser,导致新用户没有权限写入缓存。

解决方案:修改对应目录下的所属用户为当前nginx启动用户。

一、概述

之前安装好了ModSecurity作为nginx的WAF,但是后续的使用中发现OWASP-CRS规则过于苛刻,很多正常操作都会被阻挡,甚至打开一个正常的页面都会被拦截。每次都要手动排除规则十分麻烦,可以考虑使用第三方规则库:comodo规则库地址(访问需要翻墙)

comodo规则库是免费使用的,支持apache/httpd/nginx等多种web应用。使用前要先注册账号,支付0元之后将会得到为期一年的授权,此时就可以下载最新的规则库使用了,使用方式和OSASP-CRS一样,只需把规则文件加到ModSecurity的配置文件中即可。同时,官方还提供了非常简易的操作面板和详细的操作文档。

二、使用方式

如果没有安装ModSecurity先参考nginx安装modsecurity实现waf功能进行安装,ModSecurity的配置文件放在/etc/nginx/目录下。

当前最新版的comodo规则库版本为1.208,解压规则库,放到/etc/nginx/waf/comodo目录:

tar cwaf_rules_nginx_3-1.208.tgz -C /etc/nginx/waf/comodo

修改/etc/nginx/modsecurity.conf文件,设置新的规则文件位置:

Include /etc/nginx/waf/comodo/rules.conf.main

重启nginx服务,然后测试规则是否生效:

[ma@ubuntu comodo]$ curl -I http://localhost
HTTP/1.1 200 OK
Server: Tomcat/1.0.2
Date: Sun, 02 Jun 2019 01:38:52 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Fri, 14 Dec 2018 14:04:25 GMT
Connection: keep-alive
ETag: "5c13b869-264"
Accept-Ranges: bytes

[ma@ubuntu comodo]$ curl -I 'http://localhost/?search=<scritp>alert('xss');</script>'
HTTP/1.1 403 Forbidden
Server: Tomcat/1.0.2
Date: Sun, 02 Jun 2019 01:39:17 GMT
Content-Type: text/html
Content-Length: 169
Connection: keep-alive

第二个xss攻击的被拦截了,规则生效。

一、ModSecurity和OWASP

ModSecurity是一个免费、开源的Apache模块,可以充当Web应用防火墙(WAF)。ModSecurity从3.0开始支持nginx,配合nginx的灵活和高效,可以打造成生产级的WAF,是保护和审核web安全的利器。

ModSecurity的主要功能在于,提供可靠的保护,远离各种网上威胁。它不是将安全重心偏离应用程序,而是增添了全局级别的功能。想对它进行配置,只需为客户机与服务器之间通信的每一个部分用条件和操作来指定规则,这些部分包括请求头、请求主体、响应头和响应主体。因而,ModSecurity可以预防针对Web服务器、PHP、Perl和ASP等解释器以及Web应用程序发动的攻击。

OWASP是一个安全社区,开发和维护着一套免费的应用程序保护规则,这就是所谓OWASP的ModSecurity的核心规则集(即CRS)。我们可以通过ModSecurity手工创建安全过滤器、定义攻击并实现主动的安全输入验证。

二、nginx中添加ModSecurity模块

2.1 查看当前nginx编译选项

如果设备已经安装了nginx,先记住当前的nginx编译选项。使用nginx -V命令可以查看编译选项:

[art@centos7 ~]$ nginx -V
nginx version: Tomcat/1.0.2
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC) 
built with OpenSSL 1.0.2k-fips  26 Jan 2017
TLS SNI support enabled
configure arguments: --user=www --group=www --prefix=/usr/local/nginx-1.12.2 --with-http_stub_status_module --with-http_ssl_module

最后一行就是nginx安装时的编译选项,先记住。

2.2 下载ModSecurity代码

拉取ModSecurity主代码,当前版本是3.x。ModSecurity在3.0以后已经不止是一个模块了,主代码库编译出来的是一个公共库,可以被第三方代码直接引用,现如今的ModSecurity-nginx就是通过这个lib库来完成的。

因此,整个modsecurity功能目前一共涉及到三个东西:

  1. ModSecurity的lib库
  2. ModSecurity的nginx模块
  3. owasp规则

克隆ModSecurity代码:

git clone https://github.com/SpiderLabs/ModSecurity.git

克隆ModSecurity-nginx代码:

git clone https://github.com/SpiderLabs/ModSecurity-nginx.git

克隆owasp-modsecurity-crs代码:

git clone https://github.com/SpiderLabs/owasp-modsecurity-crs.git

三个代码都克隆完成后,应该存在以下三个目录:

maqian@Inspiron:/usr/src/software$
total 20
drwxrwxr-x  5 root   root   4096 6月   1 19:48 ./
drwxr-xr-x  9 root   root   4096 6月   1 19:41 ../
drwxrwxr-x 13 maqian maqian 4096 6月   1 19:44 ModSecurity/
drwxrwxr-x  5 maqian maqian 4096 6月   1 19:47 ModSecurity-nginx/
drwxrwxr-x  7 maqian maqian 4096 6月   1 19:48 owasp-modsecurity-crs/

进入到ModSecurity目录,初始化子模块,这一步相当重要,否则后面会出错

cd ModSecurity
git submodule init
git submodule update

2.3 集成ModSecurity到nginx

2.3.1 下载依赖项

依赖项注意区分系统环境,除此之外后面的流程都不再依赖系统环境。

CentOS环境:

sudo yum install autoconf automake libtool gcc gcc-c++ make
sudo yum install yum install prce pcre-devel openssl openssl-devel

Ubuntu系统:

sudo apt-get install autoconf automake libtool gcc g++ make
sudo apt-get install openssl libssl-dev libpcre3 libpcre3-dev zlib1g-dev

2.3.2 编译ModSecurity

进入ModSecurity目录:

./build.sh
./configure
make && sudo make install

因为后面的modsecurity-nginxnginx编译都依赖modsecurity的lib库,为了方便起见,直接把编译后的库文件放到默认目录,不手动指定安装目录。

2.3.3 nginx编译安装

如果没有安装nginx,先下载好nginx代码,nginx的编译安装参考:CentOS源码编译安装NGINX。如果已经安装了nginx,记录nginx当前的编译选项,参考步骤2.1

nginx是否安装不重要,不同的是如果没有安装就要先下载源代码,如果安装了就记录下编译选项。

假设安装时候的编译选项为:

--user=www --group=www --prefix=/usr/local/nginx-1.12.2 \
    --with-http_stub_status_module --with-http_ssl_module

进入nginx目录:

./configure --user=www --group=www \
    --prefix=/usr/local/nginx-1.12.2 \
    --with-http_stub_status_module --with-http_ssl_module \
    --add-module=../ModSecurity-nginx/
make && sudo make install

安装好后nginx的配置参考上面的文档即可。

2.3.4 配置OWASP Core Rule Set

owasp-modsecurity-crs开始已经下载到本地,首先拷贝一份到nginx配置目录,假设安装到/etc/nginx/owasp目录下:

cp owasp-modsecurity-crs /etc/nginx/owasp/ -rf
mv /etc/nginx/owasp/crs-setup.conf.example /etc/nginx/owasp/crs-setup.conf

配置ModSecurity:

cp ModSecurity/modsecurity.conf-recommended /etc/nginx/modsecurity.conf
cp ModSecurity/unicode.mapping /etc/nginx/

modsecurity.conf中添加crs-setup.conf的配置:

SecRuleEngine On
Include /etc/nginx/owasp/crs-setup.conf
Include /etc/nginx/owasp/rules/*.conf

在nginx的配置中开启modsecurity模块,可以放在server段或者location段内:

modsecurity on;
modsecurity_rules_file /etc/nginx/modsecurity.conf;

测试配置文件是否正确:

nginx -t -c /etc/nginx/nginx.conf

可能会报错:

2019/06/01 20:56:09 [emerg] 29985#0: "modsecurity_rules_file" directive Rules error. File: /etc/nginx/owasp/rules/REQUEST-910-IP-REPUTATION.conf. Line: 71. Column: 22. This version of ModSecurity was not compiled with GeoIP or MaxMind support.  in /etc/nginx/nginx.conf:40
nginx: configuration file /etc/nginx/nginx.conf test failed

这是因为ModSecurity中的IP检测的功能还没有添加,暂时可以先不考虑这个,直接删掉或者重命名掉不加载即可:

mv /etc/nginx/owasp/rules/REQUEST-910-IP-REPUTATION.conf /etc/nginx/owasp/rules/REQUEST-910-IP-REPUTATION.conf.bak

测试无误后重启nginx服务,务必使用restart而不是reload等操作。

三、测试

3.1 正常访问

maqian@Inspiron:~$ curl http://localhost -I
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Sat, 01 Jun 2019 13:00:38 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Sat, 01 Jun 2019 12:34:27 GMT
Connection: keep-alive
ETag: "5cf270d3-264"
Accept-Ranges: bytes

返回200,正常访问。

3.2 简单SQL注入

maqian@Inspiron:~$ curl 'http://localhost/?id=1 AND 1=1' -I
HTTP/1.1 403 Forbidden
Server: nginx/1.12.2
Date: Sat, 01 Jun 2019 13:01:30 GMT
Content-Type: text/html
Content-Length: 169
Connection: keep-alive

返回403,禁止访问。

3.3 简单xss攻击

maqian@Inspiron:~$ curl 'http://localhost/?search=<scritp>alert('xss');</script>' -I
HTTP/1.1 403 Forbidden
Server: nginx/1.12.2
Date: Sat, 01 Jun 2019 13:01:15 GMT
Content-Type: text/html
Content-Length: 169
Connection: keep-alive

返回403,禁止访问。

四、日志

默认情况下,ModSecurity是开启了审计日志的,会把审计日志打到modsec_audit.log,以下就是上面执行xss攻击时候的日志:

---EmbUKKWv---A--
[01/Jun/2019:21:01:15 +0800] 155939407569.849497 127.0.0.1 58856 127.0.0.1 80
---EmbUKKWv---B--
HEAD /?search=<scritp>alert(xss);</script> HTTP/1.1
Host: localhost
User-Agent: curl/7.58.0
Accept: */*

---EmbUKKWv---D--

---EmbUKKWv---F--
HTTP/1.1 403
Server: nginx/1.12.2
Date: Sat, 01 Jun 2019 13:01:15 GMT
Content-Length: 169
Content-Type: text/html
Connection: keep-alive

同时nginx的日志中也有日志:

2019/06/01 21:01:15 [error] 30005#0: *3 [client 127.0.0.1] ModSecurity: Access denied with code 403 (phase 2). Matched "Operator `Ge' with parameter `5' against variable `TX:ANOMALY_SCORE' (Value: `5' ) [file "/etc/nginx/owasp/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "79"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [data ""] [severity "2"] [ver ""] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-generic"] [hostname "127.0.0.1"] [uri "/"] [unique_id "155939407569.849497"] [ref ""], client: 127.0.0.1, server: localhost, request: "HEAD /?search=<scritp>alert(xss);</script> HTTP/1.1", host: "localhost"
2019/06/01 21:01:30 [error] 30005#0: *4 [client 127.0.0.1] ModSecurity: Access denied with code 403 (phase 2). Matched "Operator `Ge' with parameter `5' against variable `TX:ANOMALY_SCORE' (Value: `7' ) [file "/etc/nginx/owasp/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "79"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 7)"] [data ""] [severity "2"] [ver ""] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-generic"] [hostname "127.0.0.1"] [uri "/"] [unique_id "155939409056.181076"] [ref ""], client: 127.0.0.1, server: localhost, request: "HEAD /?id=1 AND 1=1 HTTP/1.1", host: "localhost"

正式环境下,如果访问量很大,建议关闭审计日志,开启审计日志会导致以下问题:

  1. 写入大量的日志数据,会消耗磁盘容量。
  2. 大量的IO操作会影响设备的性能。

关闭审计日志需要在/etc/nginx/modsecurity.conf中添加配置:

SecAuditEngine off

五、优化

5.1 不检查静态内容

location / {
    modsecurity on;
    modsecurity_rules_file /etc/xxxx/main.conf;
    root html;
}
location ~ \.(gif|jpg|png|jpeg|svg)$ {
    root /data/images; 
}

六、参考文档

ModSecurity3_Nginx 指南

nginx下安装配置modsecurity waf防火墙(附完整编译、配置、排错、详细规则)

Nginx1.14.0+ModSecurity实现简单的WAF

一、Let's Encrypt

Let's Encrypt SSL证书是一个免费的公益项目,由Mozilla、Cisco、Akamai、IdenTrust、EFF等组织人员发起,主要的目是为了推进网站从HTTP向HTTPS过度的进程,目前已经有越来越多的商家加入和赞助支持。

使用Let's Encrypt生成域名证书的前置条件:

  1. 拥有域名,能自主配置DNS记录。或者提供web服务器验证,需要在网站目录下放一个文件,推荐第一种方式。
  2. 获取证书的环境要能访问到DNS服务器,因为中途需要校验DNS解析。
  3. 拥有主机的超级权限,中途需要更新和安装组件。

二、申请流程

第一步:拉代码,代码开源于Github

> git clone https://github.com/letsencrypt/letsencrypt
Cloning into 'letsencrypt'...
remote: Enumerating objects: 29, done.
remote: Counting objects: 100% (29/29), done.
remote: Compressing objects: 100% (27/27), done.
remote: Total 61424 (delta 7), reused 4 (delta 2), pack-reused 61395
Receiving objects: 100% (61424/61424), 20.12 MiB | 5.01 MiB/s, done.
Resolving deltas: 100% (44625/44625), done.

完成后执行命令生成证书:

> ./certbot-auto certonly -d *.maqian.art --manual \
>     --preferred-challenges dns \
>     --server https://acme-v02.api.letsencrypt.org/directory

解释一下各个参数的含义:

  • certonly: 表示当前为安装模式
  • --manual: 表示手动安装插件,不要自动安装了
  • --preferred-challenges dns: 校验方式为dns验证
  • -d *.maqian.art: 要生成的域名列表,可以为多个,如果是多个分别以-d加上即可
  • --server: Let's Encrypt ACME v2版本使用的服务器

接下来的步骤一直接受即可,直到出现添加DNS记录为止:

Requesting to rerun ./certbot-auto with root privileges...
[sudo] password for ma: 
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator manual, Installer None
Enter email address (used for urgent renewal and security notices) (Enter 'c' to
cancel): maqian@dyxmq.cn

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf. You must
agree in order to register with the ACME server at
https://acme-v02.api.letsencrypt.org/directory
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(A)gree/(C)ancel: A

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing to share your email address with the Electronic Frontier
Foundation, a founding partner of the Let's Encrypt project and the non-profit
organization that develops Certbot? We'd like to send you email about our work
encrypting the web, EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y
Obtaining a new certificate
Performing the following challenges:
dns-01 challenge for sinfor.maqian.io

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NOTE: The IP of this machine will be publicly logged as having requested this
certificate. If you're running certbot in manual mode on a machine that is not
your server, please ensure you're okay with that.

Are you OK with your IP being logged?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name
_acme-challenge.maqian.art with the following value:

zw1MeEkmGZOqSqiySp9Ke8S5a9BXC3O4tYzlbjwU-CU

Before continuing, verify the record is deployed.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue

此时需要在DNS服务商处添加dns解析,记录类型为TXT,记录为_acme-challenge,值为zw1MeEkmGZOqSqiySp9Ke8S5a9BXC3O4tYzlbjwU-CU。当DNS记录设置好后,新开一个终端查询解析是否生效:

> nslookup -type=txt _acme-challenge.maqian.art
Server:        100.100.2.136
Address:    100.100.2.136#53

Non-authoritative answer:
_acme-challenge.maqian.art    text = "zw1MeEkmGZOqSqiySp9Ke8S5a9BXC3O4tYzlbjwU-CU"

Authoritative answers can be found from:

当查询到的记录和给定的都一致之后按下任意键执行下一步,如果DNS验证成功就会出现以下信息:

Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/maqian.art-0001/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/maqian.art-0001/privkey.pem
   Your cert will expire on 2019-03-22. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot-auto
   again. To non-interactively renew *all* of your certificates, run
   "certbot-auto renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

此时就表示证书已经申请完成了,存放的路径为:/etc/letsencrypt/live/maqian.art-0001。

> sudo ls /etc/letsencrypt/live/maqian.art-0001/ -l
total 4
lrwxrwxrwx 1 root root  39 Dec 22 23:46 cert.pem -> ../../archive/maqian.art-0001/cert1.pem
lrwxrwxrwx 1 root root  40 Dec 22 23:46 chain.pem -> ../../archive/maqian.art-0001/chain1.pem
lrwxrwxrwx 1 root root  44 Dec 22 23:46 fullchain.pem -> ../../archive/maqian.art-0001/fullchain1.pem
lrwxrwxrwx 1 root root  42 Dec 22 23:46 privkey.pem -> ../../archive/maqian.art-0001/privkey1.pem
-rw-r--r-- 1 root root 692 Dec 22 23:46 README

三、安装到nginx

上面一共生成了四个文件,各自的用途为:

  • cert.pem: Apache服务器端证书
  • chain.pem: Apache根证书和中继证书
  • fullchain.pem: Nginx所需要ssl_certificate文件
  • privkey.pem: 安全证书KEY文件

部署到nginx只需要添加一下指令即可:

ssl on;
ssl_certificate /etc/letsencrypt/live/maqian.art-0001/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/maqian.art-0001/privkey.pem;

打开网站,点开左上角地址栏的https,查看证书:

nginx中只有if关键字,并不支持else语法,if的使用方法为:

if ($xxx = xxx) {
    xxx
}

和代码不同的是:if条件语句判断相等时只要一个等号,不能是==

虽然不支持else,但是可以使用以下的方法来模拟实现else:

server {
    server_name *.maqian.io;
    listen 80;
    
    location / {
        set $is_matched 0;
        if ($host = a.maqian.io) {
            proxy_pass http://127.0.0.1:1001/;
            set $is_matched 1;
        }
        
        if ($host = b.maqian.io) {
            proxy_pass http://127.0.0.1:1002/;
            set $is_matched 1;
        }
        # 没有匹配到,跳转到默认页面
        if ($is_matched = 0) {
            proxy_pass http://127.0.0.1;
        }
        
        # xxx
        # xxx
        # xxx
    }
}

nginx访问限频

一、并发访问限制

ngx_http_limit_conn_module是一个默认安装的内置模块,被用来限制在某一个关键字维度上的最大并发数量,通常情况下,这个维度被设置为访问者的IP。在计算的一个连接当前的并发数量时,不是一连接就会被计数,而是当所有请求头都被读完才计数。它的示例配置为:

http {
    limit_conn_zone $binary_remote_addr zone=addr:10m;
    # ...
    server {
        # ...
        location /download/ {
            limit_conn addr 1;
        }
    }
}

以上配置通过limit_conn_zone指令定义了一个名为addr的并发限制器,它以$binary_remote_addr(即访问的IP地址)作为key,分配一块10m的空间来保存所有的连接数量。

而后的对应的server段中,使用这个addr,限制同一时刻最多只能有1个连接。所以整个配置的意思就是:限制单个IP同一时刻最多有3个访问连接。

相关的参考文档:Module ngx_http_limit_conn_module

1.1 案例一:针对IP的并发数量限制

当前有一个站点d2.maqian.co,希望同一时刻同一IP最多只能3个并发访问:

http {
    limit_conn_zone $binary_remote_addr zone=addr:10m
    server {
        listen       80;
        server_name  d2.maqian.co;
        limit_conn   addr 3;
        limit_rate   1m; # 限制访问速率1M/s
    
        location / {
            root   html;
            index  index.html index.htm;
        }
        
        access_log logs/access.log main;
        error_log logs/error.log;
    }
}

网站的根目录下有个文件master.zip,大小8.8M,在另一台客户端上使用ab命令执行并发测试:

ab -c 5 -n 10 http://d2.maqian.co/master.zip # 同一时刻5个并发连接,一共10个连接

测试过程中Nginx的访问日志:

> cat access.log
192.168.123.101 - - [20/Oct/2018:17:45:19 +0800] GET "d2.maqian.co/master.zip" 503 537 "-" "ApacheBench/2.3" "-"
192.168.123.101 - - [20/Oct/2018:17:45:19 +0800] GET "d2.maqian.co/master.zip" 503 537 "-" "ApacheBench/2.3" "-"
192.168.123.101 - - [20/Oct/2018:17:45:19 +0800] GET "d2.maqian.co/master.zip" 503 537 "-" "ApacheBench/2.3" "-"
192.168.123.101 - - [20/Oct/2018:17:45:19 +0800] GET "d2.maqian.co/master.zip" 503 537 "-" "ApacheBench/2.3" "-"
192.168.123.101 - - [20/Oct/2018:17:45:19 +0800] GET "d2.maqian.co/master.zip" 503 537 "-" "ApacheBench/2.3" "-"
192.168.123.101 - - [20/Oct/2018:17:45:19 +0800] GET "d2.maqian.co/master.zip" 503 537 "-" "ApacheBench/2.3" "-"
192.168.123.101 - - [20/Oct/2018:17:45:19 +0800] GET "d2.maqian.co/master.zip" 503 537 "-" "ApacheBench/2.3" "-"
192.168.123.101 - - [20/Oct/2018:17:45:27 +0800] GET "d2.maqian.co/master.zip" 200 9204205 "-" "ApacheBench/2.3" "-"
192.168.123.101 - - [20/Oct/2018:17:45:27 +0800] GET "d2.maqian.co/master.zip" 200 9204205 "-" "ApacheBench/2.3" "-"
192.168.123.101 - - [20/Oct/2018:17:45:27 +0800] GET "d2.maqian.co/master.zip" 200 9204205 "-" "ApacheBench/2.3" "-"

可以看到,除了3个连接的的返回值为200以外,其他的都返回状态码503。

为什么三个200访问的日志在最后呢?

因为下载的文件内容是8.8M,配置中有限制最大下载速率为1M/s,所以它大概需要9秒才能下载完成,状态码是在连接返回完成才打印出来的,并且可以看到503状态码和200状态码的日志间隔差不多刚好8-9s。

那为什么要限制下载速率呢?

当前在内网环境下,下载速度非常快,8.8M的文件几乎1S内就能下载完成,连接很难并发,即使并发了,10个连接也很有可能有多个成功了。

1.2 针对服务端同一时刻的访问频率限制

和上面同样的环境,我们不限制单个IP的并发访问数量,而希望同一时刻服务器最多处理10个连接:

http {
    limit_conn_zone $server_name zone=web_server:10m;
    server {
        listen       80;
        server_name  d2.maqian.co;
        limit_conn web_server 10;
        limit_conn_log_level info;    
        limit_rate 1m;
    
        location / {
            root   html;
            index  index.html index.htm;
        }
    
        access_log logs/access.log main;
        error_log logs/error.log;
    }
}

执行命令测试:

ab -c 11 -n 15 http://d2.maqian.co/master.zip # 同时产生11个连接,一共访问15次

nginx访问日志:

192.168.123.101 - - [20/Oct/2018:18:23:49 +0800] GET "d2.maqian.co/master.zip" 503 537 "-" "ApacheBench/2.3" "-"
192.168.123.101 - - [20/Oct/2018:18:23:49 +0800] GET "d2.maqian.co/master.zip" 503 537 "-" "ApacheBench/2.3" "-"
192.168.123.101 - - [20/Oct/2018:18:23:49 +0800] GET "d2.maqian.co/master.zip" 503 537 "-" "ApacheBench/2.3" "-"
192.168.123.101 - - [20/Oct/2018:18:23:49 +0800] GET "d2.maqian.co/master.zip" 503 537 "-" "ApacheBench/2.3" "-"
192.168.123.101 - - [20/Oct/2018:18:23:49 +0800] GET "d2.maqian.co/master.zip" 503 537 "-" "ApacheBench/2.3" "-"
192.168.123.101 - - [20/Oct/2018:18:23:58 +0800] GET "d2.maqian.co/master.zip" 200 9204205 "-" "ApacheBench/2.3" "-"
192.168.123.101 - - [20/Oct/2018:18:23:58 +0800] GET "d2.maqian.co/master.zip" 200 9204205 "-" "ApacheBench/2.3" "-"
192.168.123.101 - - [20/Oct/2018:18:23:58 +0800] GET "d2.maqian.co/master.zip" 200 9204205 "-" "ApacheBench/2.3" "-"
192.168.123.101 - - [20/Oct/2018:18:23:58 +0800] GET "d2.maqian.co/master.zip" 200 9204205 "-" "ApacheBench/2.3" "-"
192.168.123.101 - - [20/Oct/2018:18:23:58 +0800] GET "d2.maqian.co/master.zip" 200 9204205 "-" "ApacheBench/2.3" "-"
192.168.123.101 - - [20/Oct/2018:18:23:58 +0800] GET "d2.maqian.co/master.zip" 200 9204205 "-" "ApacheBench/2.3" "-"
192.168.123.101 - - [20/Oct/2018:18:23:58 +0800] GET "d2.maqian.co/master.zip" 200 9204205 "-" "ApacheBench/2.3" "-"
192.168.123.101 - - [20/Oct/2018:18:23:58 +0800] GET "d2.maqian.co/master.zip" 200 9204205 "-" "ApacheBench/2.3" "-"
192.168.123.101 - - [20/Oct/2018:18:23:58 +0800] GET "d2.maqian.co/master.zip" 200 9204205 "-" "ApacheBench/2.3" "-"
192.168.123.101 - - [20/Oct/2018:18:23:58 +0800] GET "d2.maqian.co/master.zip" 200 9204205 "-" "ApacheBench/2.3" "-"

限制了同一时刻服务端只能有10个连接之后,现象也和上面的一样,15个连接有5个返回错误,但是不同的是这些连接可以同时来自于同一个IP。

1.3 多关键字配合使用

上面的基于IP和服务端访问限制可以同时使用,并且不只是这两个字段可以配合使用,其他的变量也都可以同时使用,具体的变量可以参考:Alphabetical index of variables

例如我们可以同时限制单个IP并发连接数为3,并且同时访问服务端的连接数为100:

http {
    limit_conn_zone $server_name zone=web_server:10m;
    limit_conn_zone $binary_remote_addr zone=addr:10m
    server {
        listen       80;
        server_name  d2.maqian.co;
        limit_conn web_server 100; # 同一时刻最多100个连接访问服务端
        limit_conn addr 3; # 同一时刻同一IP最多3个连接访问
        limit_conn_log_level info;    
        limit_rate 1m;
    
        location / {
            root   html;
            index  index.html index.htm;
        }
    
        access_log logs/access.log main;
        error_log logs/error.log;
    }
}

二、限制访问频率

ngx_http_limit_req_module模块用于限制每个IP访问某个关键字维度的请求速率,其参数用法如下:

limit_req_zone key zone=name:size rate=rate;

以上的配置创建一个速率限制器,限制单个IP在key这个维度上的访问速率。和上面一样,这个key也通常被设置成访问者的IP。使用时在对应的server段内设置:

limit_req zone=name [burst=number] [nodelay];

burst表示令牌数量,连接满了之后,给接下来的连接发放令牌进行等待。令牌数量超出后,可以选择继续等待令牌或者直接返回错误状态。

这里的逻辑可以看成去银行办业务:人多的时候需要等号,number可以堪称最大的等号数量,rate可以堪称银行的窗口个数。银行同一时刻处理rate个客户的请求,并且同时允许number个客户排队,超出后,根据nodelay是否被设置来判断该连接是应该被丢弃还是等待。

参考文档:Module ngx_http_limit_req_module

2.1 限制每秒只处理同一个用户的一个请求

http {
    # 以请求IP作为KEY,设置访问频率为1秒1次请求
    limit_req_zone $binary_remote_addr zone=addr:10m rate=1r/s;
    server {
        listen       80;
        server_name  d2.maqian.co;
        # 设置队列为5,最多有5个连接等待,超出的不继续等待
        limit_req zone=addr burst=5 nodelay;
        limit_rate 1m;
    
        location / {
            root   html;
            index  index.html index.htm;
        }

        access_log logs/access.log main;
        error_log logs/error.log;
    }
}

为了避免大文件下载耗时,这里不再和上面一样下载大文件,使用小文件测试:

ab -c 6 -n 100 http://d2.maqian.co # 6个并发连接,访问100次

得到日志后,复制到当前目录下,分别分析200和503响应的次数:

> grep "503" access.log | wc -l
94
> grep "200" access.log | wc -l
6

nginx第一秒处理第一个请求,同时给接下来的5个请求排队,剩下的都直接返回503,所以返回200的次数为6,503的次数为94。

ab返回的结果也能看到成功和失败的数量:

一、nginx目录索引

nginx中内置了目录索引命令 auto_index ,十分方便就能给目录生成web索引:

location /ftp/ {
    alias /data/html;
    autoindex on;
}

效果如下:

PIC20180902_214954.png

两个可选的命令是 autoindex_exact_sizeautoindex_localtime ,分别表示是否精确显示文件大小(以字节方式)和是否显示本地时间,两个都 不开启 的情况下是这样的:

PIC20180902_215504.png

二、使用fancyindex索引

nginx自带的索引功能很单一,界面也很原始。后面有人做了个fancyindex的插件用来强化这个功能,已经被官方采用。官方文档地址,fancyindex代码地址:ngx-fancyindex

2.1 编译fancyindex

安装fancyindex要重新编译nginx,首先下载fancyindex源码

git clone https://github.com/aperezdc/ngx-fancyindex

解压nginx进入目录,重新执行 configure 命令,指定参数 --add-module 添加fancyindex模块,nginx安装的具体流程可以参考源码编译安装nginx

./configure --user=www --group=www \
    --prefix=/usr/local/nginx-1.12.2 \
    --with-http_stub_status_module \
    --with-http_ssl_module \
    --add-module=../ngx-fancyindex # 添加fancyindex模块

执行make和make install即可完成安装。

2.2 使用fancyindex

fancyindex的指令:

location /ftp/ {
    alias /data/software/nginx/;
    fancyindex on; # 使用fancyindex
    fancyindex_exact_size off; # 不显示精确大小
}

其效果如下:

fancyindex.png

点击表头的 File NameFile Size 或者 Date 能对文件进行排序。

2.3 其他用法

fancyindex提供了自定义页头和页脚,分别通过指令 fancyindex_headerfancyindex_footer 完成。

例如在页脚加上一个超链接到百度首页:

location /ftp/ {
    alias /data/software/nginx/;
    fancyindex on;
    fancyindex_exact_size off;
    fancyindex_footer "fancy_footer.html";
}

然后在 网站根目录下 加上一个 fancy_footer.html

<div id="footer">
    <a href="https://www.baidu.com">百度一下<a>
</div>

重新载入后的页面:

PIC20180902_221620.png

一、概述

nginx作为当前最流行的开源web服务器之一,被无数公司或个人在使用。正因为开源,所以任何人都可以获取到它的源码,这其中就包含了世界各地的黑客。他们无时无刻不在想着如何找出nginx的漏洞,以至于在网上随便一搜都能找到无数nginx相关的漏洞:

默认情况下,http响应的Server头部都会携带上服务器的名字和版本信息:

> curl -I http://127.0.0.1
HTTP/1.1 502 Bad Gateway
Server: nginx/1.12.2   # 携带有服务端和版本号
Date: Mon, 12 Mar 2018 01:25:00 GMT
...

而一旦黑客知道了服务端软件的版本信息,很容易就能通过对应版本的漏洞来攻击服务器,引发安全问题。所以针对生产环境的服务器,有必要隐藏或者修改软件版本信息,以避免黑客的指向性攻击。

- 阅读剩余部分 -