# NGINX Security **Repository Path**: michaelwang19/0917_NGINX_OSS_Security ## Basic Information - **Project Name**: NGINX Security - **Description**: No description available - **Primary Language**: Shell - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 3 - **Forks**: 0 - **Created**: 2022-09-03 - **Last Updated**: 2023-09-27 ## Categories & Tags **Categories**: Uncategorized **Tags**: Nginx, 开源, 安全, 限速, 限流 ## README # NGINX Security实验手册 --- ## 0-访问UDF环境 ### 注册UDF账号及登录UDF环境 无需操作,在上一个实验《容器化应用的动态发布实验手册》中已经完成此操作。 ### 登录UDF课程 使用chrome浏览器访问 https://udf.f5.com/ ,选择 Invited Users: ### 启动环境,登录NGINX Security虚拟服务器 一切就绪后,您可以点击课程右端的launch 启动您的课程,您将进入课程文档的界面。 ![coursedoc](images/udf_course_doc.png) 打开Lab Guide中的链接:https://gitee.com/michaelwang19/0917_NGINX_OSS_Security 该文档详细说明了实验操作步骤。 您也可以点击DEPLOYMENT打开部署环境,您将看到环境正在被拉起,耐心等待,直到各组件前出现绿色三角,表明环境就绪: ![deployment](images/udf_course_deployment.png) 接下来,您就可以通过Web Shell或者SSH客户端访问NGINX Security: ![nginxsecurity](images/udf_nginx_security.jpg) ### 登录Client客户端虚拟机Window 10 下载RDP远程桌面配置: ![下载RDP](images/udf_rdp.png) 远程桌面登录Client客户端,账号密码为: ``` 账号:Administrator 密码:gUyLsoBjoUjaD ``` --- ## 1-实验环境简介 - 系统版本:CentOS 7 - NGINX版本:1.23.1 Web Shell或SSH登录NGINX Security虚拟机之后,首先确认一下NGINX的运行状态是active (running) : ```log [root@2370ed03-0df5-471a-bc80-3928ece95a4c sbin]# systemctl status nginx ● nginx.service - nginx - high performance web server Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; vendor preset: disabled) Active: active (running) since Wed 2022-08-31 11:58:03 UTC; 3h 52min ago Docs: http://nginx.org/en/docs/ Process: 1898 ExecStop=/usr/local/nginx/sbin/nginx -s quit (code=exited, status=0/SUCCESS) Process: 1902 ExecStart=/usr/local/nginx/sbin/nginx (code=exited, status=0/SUCCESS) Main PID: 1903 (nginx) CGroup: /system.slice/nginx.service ├─1903 nginx: master process /usr/local/nginx/sbin/nginx └─2272 nginx: worker process ``` 为方便不同技术栈背景的学员的学习,降低实验环境带来的时间消耗,将实验环境简化。 并对实验涉及到的配置全部编写在配置文件中,通过配置文件中添加注释和取消注释的方式做不同的实验。 每次NGINX配置修改后请执行配置文件校验是否存在错误: ```bash [centos@2370ed03-0df5-471a-bc80-3928ece95a4c ~]$ sudo su [root@2370ed03-0df5-471a-bc80-3928ece95a4c centos]# cd /usr/local/nginx/sbin [root@2370ed03-0df5-471a-bc80-3928ece95a4c sbin]# ./nginx -t nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful ``` 提示successful之后,执行NGINX配置重新加载指令: ```bash [root@2370ed03-0df5-471a-bc80-3928ece95a4c sbin]# ./nginx -s reload ``` ### 实验拓扑结构图 ![实验拓扑图](images/lab_top.png) ### 相关配置文件目录 - NGINX配置文件:/usr/local/nginx/conf/nginx.conf - SSL证书和密钥文件目录:/usr/local/nginx/conf/ssl/ --- ## 2-访问控制实验章节 ### 2.1 IP地址限制 本实验小节,NGINX通过限制客户端Client的IP地址10.1.1.6的访问,但是同网段10.1.1.0/24其他IP允许访问。 修改NGINX配置: ```bash [centos@2370ed03-0df5-471a-bc80-3928ece95a4c ~]$ sudo su [root@2370ed03-0df5-471a-bc80-3928ece95a4c centos]# cd /usr/local/nginx/sbin [root@2370ed03-0df5-471a-bc80-3928ece95a4c sbin]# vim /usr/local/nginx/conf/nginx.conf ``` 将以下两行deny 10.1.1.6和allow 10.1.1.0/24前面的注释符#去掉,并保存配置。 ```bash server { listen 80; server_name www.example.com; #charset koi8-r; access_log logs/host.access.log main; error_log logs/error.log info; #deny 10.1.1.6; #allow 10.1.1.0/24; ``` 验证配置是否有错误: ```bash [root@2370ed03-0df5-471a-bc80-3928ece95a4c sbin]# ./nginx -t nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful ``` 提示successful之后,执行NGINX配置重新加载指令: ```bash [root@2370ed03-0df5-471a-bc80-3928ece95a4c sbin]# ./nginx -s reload ``` 使用Client客户端(10.1.1.6)访问NGINX的服务(10.1.1.5:80): - 命令行中输入: ``` curl http://10.1.1.5 ``` - Chrome浏览器访问http://10.1.1.5 拦截效果: ![IP限制](images/lab_2.1_deny_ip.png) 检查日志: ```log [root@2370ed03-0df5-471a-bc80-3928ece95a4c sbin]# tail -f /usr/local/nginx/logs/error.log 2022/09/02 18:17:49 [notice] 1671#0: signal process started 2022/09/02 18:21:01 [notice] 1676#0: signal process started 2022/09/02 18:29:25 [notice] 1680#0: signal process started 2022/09/02 22:50:56 [notice] 2028#0: signal process started 2022/09/03 08:36:35 [emerg] 1414#0: unknown "accessip" variable 2022/09/03 08:44:57 [notice] 1509#0: signal process started 2022/09/03 08:50:39 [error] 1510#0: *4 access forbidden by rule, client: 10.1.1.6, server: www.example.com, request: "GET / HTTP/1.1", host: "10.1.1.5" 2022/09/03 08:50:39 [info] 1510#0: *4 client 10.1.1.6 closed keepalive connection 2022/09/03 08:51:45 [error] 1510#0: *5 access forbidden by rule, client: 10.1.1.6, server: www.example.com, request: "GET / HTTP/1.1", host: "10.1.1.5" 2022/09/03 08:51:46 [error] 1510#0: *5 access forbidden by rule, client: 10.1.1.6, server: www.example.com, request: "GET /favicon.ico HTTP/1.1", host: "10.1.1.5", referrer: "http://10.1.1.5/" ``` 日志截图如下: ![IP限制log](images/lab_2.1_deny_ip_log.png) --- ### 2.2 地域限制 本实验小节,由于实验环境是纯内网环境,无法通过互联网直接访问NGINX,因此,NGINX上检测到的IP地址都是内网IP。 而且真实应用场景中,NGINX多数部署在内网,数据包在进入NGINX的时候,前段会存在多次的IP地址转换NAT的设备,因此,实际应用场景中,NGINX在网络数据包层面看到的多数也是内网IP地址。这就是为什么实际应用场景中,NGINX上经常会启用一些技术手段来获取真实的客户端IP地址,比较常用的就是通过X-Forwarded-For字段来传递真实客户端IP。 为了验证该实验效果,我们通过在HTTP包头中插入标准的X-Forwarded-For字段,来模拟真实环境中NGINX通过限制客户端IP所属的地域属性。 实验中用到的两个IP分别为: - 202.96.209.5:所属的国家地域代码为CN。允许访问。 - 8.8.8.8:所属的国家地域代码为US。禁止访问。 可以通过以下网站查询IP的地域信息: ``` https://www.maxmind.com/en/geoip2-precision-demo ``` 修改NGINX配置: ```bash [root@2370ed03-0df5-471a-bc80-3928ece95a4c sbin]# vim /usr/local/nginx/conf/nginx.conf ``` 将以下4行geoip开头和map这部分配置前面的注释符#去掉,参考如下: ```bash http { include mime.types; default_type application/octet-stream; geoip_country /usr/share/GeoIP/GeoIP.dat; geoip_city /usr/share/GeoIP/GeoIPCity.dat; geoip_proxy 10.1.1.6; geoip_proxy_recursive on; map $geoip_country_code $accessip { default false; US false; CN true; } ``` 将上一个实验《IP地址限制》中两行deny 10.1.1.6和allow 10.1.1.0/24注释掉。 并将下面的if语句和proxy_set_header前面的注释取消,参考如下。 ```bash server { listen 80; server_name www.example.com; #charset koi8-r; access_log logs/host.access.log main; error_log logs/error.log info; # deny 10.1.1.6; # allow 10.1.1.0/24; if ( $accessip = 'false') {return 403;} proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; ``` 验证配置是否有错误: ```bash [root@2370ed03-0df5-471a-bc80-3928ece95a4c sbin]# ./nginx -t nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful ``` 提示successful之后,执行NGINX配置重新加载指令: ```bash [root@2370ed03-0df5-471a-bc80-3928ece95a4c sbin]# ./nginx -s reload ``` 使用Client客户端(10.1.1.6)访问NGINX的服务(10.1.1.5:80): - 命令行中输入: ``` curl -H "X-Forwarded-For:202.96.209.5" http://10.1.1.5 ``` 可以看到允许正常访问: ![地域限制ok](images/lab_2.2_geoip_ok.png) 检查日志: ![地域限制ok_log](images/lab_2.2_geoip_ok_log.png) - 命令行中输入: ``` curl -H "X-Forwarded-For:8.8.8.8" http://10.1.1.5 ``` 可以看到访问被拦截,返回403: ![地域限制fail](images/lab_2.2_geoip_fail.png) 检查日志: ![地域限制fail_log](images/lab_2.2_geoip_fail_log.png) **恢复环境:** 参照以下将对应的配置注释掉: ```bash http { include mime.types; default_type application/octet-stream; #geoip_country /usr/share/GeoIP/GeoIP.dat; #geoip_city /usr/share/GeoIP/GeoIPCity.dat; #geoip_proxy 10.1.1.6; #geoip_proxy_recursive on; #map $geoip_country_code $accessip { # default false; # US false; # CN true; #} ``` ```bash server { listen 80; server_name www.example.com; #charset koi8-r; access_log logs/host.access.log main; error_log logs/error.log info; #deny 10.1.1.6; #allow 10.1.1.0/24; # if ( $accessip = 'false') {return 403;} # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; ``` 删除log格式中2个变量geoip_country_code和accessip的调用,即保留如下: ```bash log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; ``` --- ### 2.3 连接数限制 在上一个实验《容器化应用的动态发布实验手册》的第5个实验中,已经了解到通过KIC实现连接数限制的方法。 本实验小节,通过NGINX来实现连接数的限制。 修改NGINX配置: ```bash [root@2370ed03-0df5-471a-bc80-3928ece95a4c sbin]# vim /usr/local/nginx/conf/nginx.conf ``` 将以下limit_conn_zone $remote_addr zone=ip:10m前面的注释符#去掉: ```bash http { include mime.types; default_type application/octet-stream; # geoip_country /usr/share/GeoIP/GeoIP.dat; # geoip_city /usr/share/GeoIP/GeoIPCity.dat; # geoip_proxy 10.1.1.6; # geoip_proxy_recursive on; # map $geoip_country_code $accessip { # default false; # US false; # CN true; # } limit_conn_zone $remote_addr zone=ip:10m; #limit_req_zone $remote_addr zone=reqzone:10m rate=2r/m; ``` 将以下limit_conn_status, limit_rate, limit_conn_log_level, limit_conn ip这4个配置前面的注释符取消掉: 说明: 由于实验环境是内网,而且NGINX高性能的处理效率,所以为了验证连接数限制的效果,通过limit_rate参数将每秒返回50个字节的数据量,将限制的并发连接数调小。 实际环境中的应用,根据实际情况设置连接数。 配置如下: ```bash location /limit { root html; index limit_conn.html; #proxy_pass http://127.0.0.1/limit/; # limit_except GET { # deny all; # } #This is limit connection section limit_conn_status 509; limit_rate 50; limit_conn_log_level warn; limit_conn ip 1; ``` 验证配置是否有错误,提示successful之后,执行NGINX配置重新加载指令。 使用Client开启2个命令行工具,发送curl验证连接数限制: ```bash curl -I http://10.1.1.5/limit/ ``` 可以看到超过1个连接之后,第二个连接请求被返回在配置中定义好的509状态码: ![连接数限制](images/lab_2.3_limit_conn.png) 检查日志,看到请求被连接数限制: ![连接数限制log](images/lab_2.3_limit_conn_log.png) --- ### 2.4 请求速率限制 本实验小节,通过对每秒或者每分钟的请求速率进行限制。 限制基于源IP来限制请求速率,每分钟请求不能超过2次,突发请求缓冲3个,超出3个缓冲连接后即返回503响应码。 修改NGINX配置: 将http配置块中的limit_req_zonet这部分注释取消,参考如下配置: ```bash http { include mime.types; default_type application/octet-stream; # geoip_country /usr/share/GeoIP/GeoIP.dat; # geoip_city /usr/share/GeoIP/GeoIPCity.dat; # geoip_proxy 10.1.1.6; # geoip_proxy_recursive on; # map $geoip_country_code $accessip { # default false; # US false; # CN true; # } limit_conn_zone $remote_addr zone=ip:10m; limit_req_zone $remote_addr zone=reqzone:10m rate=2r/m; ``` 将location配置块中的This is limit request section后面4行配置的注释取消,参考如下配置: ```bash location /limit { root html; index limit_conn.html; #proxy_pass http://127.0.0.1/limit/; # limit_except GET { # deny all; # } #This is limit connection section limit_conn_status 509; limit_rate 50; limit_conn_log_level warn; limit_conn ip 1; #This is limit request section limit_req zone=reqzone; limit_req zone=reqzone burst=3 nodelay; limit_conn_status 503; limit_conn_log_level warn; } ``` 使用Client客户端测试: ```bash curl -I http://10.1.1.5/limit/ ``` 测试效果: ![请求限制](images/lab_2.4_limit_req.png) 检查log日志: 可以看到日志中显示是请求request超过了限制。 ![请求限制log](images/lab_2.4_limit_req_log.png) --- ### 2.5 访问方式限制 本实验小节,通过对HTTP请求方式Method进行限制,如POST、GET、HEAD、PUT、PATCH、OPTIONS、DELETE、CONNECT、TRACE。 如针对不同的uri只允许指定的GET方法使用,其他的方法都拒绝。 修改NGINX配置: 将limit_except这3行配置注释取消,参考如下配置: ```bash location /limit { root html; index limit_conn.html; #proxy_pass http://127.0.0.1/limit/; limit_except GET { deny all; } ``` 使用Client客户端测试: ```bash curl -X POST http://10.1.1.5/limit/ ``` 测试效果: ![方式限制](images/lab_2.5_limit_method.png) 检查log日志: 可以看到日志中显示POST方法不被允许使用,返回了405响应码。 ![方式限制log](images/lab_2.5_limit_method_log.png) --- ## 3-SSL/TLS加密实验章节 本章节涉及的证书已经通过openssl自签发,用于临时测试使用,所以证书属于浏览器不受信任的证书,在测试过程中看到浏览器报证书不受信的提示时,可以忽略。 ### 3.1 HTTPS加密 - 单域名证书 #### 3.1.1 默认NGINX HTTPS配置 修改配置,https配置部分加载证书和key,其他配置采用NGINX默认,不作额外修改: ```bash server { listen 443 ssl; server_name www.test.com; ssl_certificate /usr/local/nginx/conf/www.test.com.crt; ssl_certificate_key /usr/local/nginx/conf/www.test.com.key; location / { root html; index index.html index.htm; ``` 客户端Client访问: ``` https://www.test.com ``` 检查证书信息: ![3.1.1证书信息](images/lab_3.1.1_cert.png) 检查所支持的SSL/TLS相关算法: ![3.1.1算法信息](images/lab_3.1.1_cipher.png) --- #### 3.1.2 调整HTTPS所支持的TLS协议 修改配置,增加TLSv1.3协议的支持: 取消ssl_protocol前面的注释,参考如下: ```bash #HTTPS Server server { listen 443 ssl; server_name www.example.com; ssl_certificate /usr/local/nginx/conf/www.example.com.crt; ssl_certificate_key /usr/local/nginx/conf/www.example.com.key; # ssl_session_cache shared:SSL:1m; # ssl_session_timeout 5m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; #ssl_ciphers ALL:!IDEA; # ssl_ciphers HIGH; # ssl_ciphers HIGH:!aNULL:!MD5; # ssl_prefer_server_ciphers on; location / { root html; index index.html index.htm; ``` 客户端Client浏览器访问: ``` https://www.example.com ``` 检查证书信息: ![3.1.2cert信息](images/lab_3.1.2_cert.png) 使用客户端Client桌面上Zenmap软件测试算法支持情况: ```bash nmap -sV -p 443 --script ssl-enum-ciphers www.example.com ``` 检查所支持的SSL/TLS相关算法,扫描发现TLSv1.3算法: ![3.1.2cert信息](images/lab_3.1.2_cipher.png) --- #### 3.1.3 调整SSL算法 修改配置,调整当前NGINX版本中所有SSL算法支持: 取消ssl_protocol前面的注释,参考如下: ```bash #HTTPS Server server { listen 443 ssl; server_name www.example.com; ssl_certificate /usr/local/nginx/conf/www.example.com.crt; ssl_certificate_key /usr/local/nginx/conf/www.example.com.key; # ssl_session_cache shared:SSL:1m; # ssl_session_timeout 5m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; ssl_ciphers ALL; #ssl_ciphers ALL:!IDEA; # ssl_ciphers HIGH; # ssl_ciphers HIGH:!aNULL:!MD5; # ssl_prefer_server_ciphers on; location / { root html; index index.html index.htm; ``` 使用客户端Client桌面上Zenmap软件测试算法支持情况: ```bash nmap -sV -p 443 --script ssl-enum-ciphers www.example.com ``` 检查所支持的SSL/TLS相关算法,扫描发现存在warning信息,提示IDEA算法漏洞存在SWEET32攻击风险: ![3.1.3cipher信息](images/lab_3.1.3_cipher.png) 禁用IDEA算法,可以通过调整算法组合,也可以直接禁用该算法,如下是直接禁用该算法的方式: 修改配置: ```bash #HTTPS Server server { listen 443 ssl; server_name www.example.com; ssl_certificate /usr/local/nginx/conf/www.example.com.crt; ssl_certificate_key /usr/local/nginx/conf/www.example.com.key; # ssl_session_cache shared:SSL:1m; # ssl_session_timeout 5m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # ssl_ciphers ALL; ssl_ciphers ALL:!IDEA; # ssl_ciphers HIGH; # ssl_ciphers HIGH:!aNULL:!MD5; # ssl_prefer_server_ciphers on; location / { root html; index index.html index.htm; ``` 保存配置,并加载配置。 再次使用Zenmap软件测试算法支持情况: 发现已经无法扫描出IDEA算法: ![3.1.3noidea](images/lab_3.1.3_noidea.png) --- ### 3.2 HTTPS强制加密 本实验小节,通过在http的服务中启用301重定向的策略,将http的请求强制跳转至https的请求,来实现强制加密访问的需求。 修改配置文件,在80服务中启用强制HTTPS跳转访问的策略: ```bash server { listen 80; server_name www.example.com; #charset koi8-r; access_log logs/host.access.log main; error_log logs/error.log info; #deny 10.1.1.6; #allow 10.1.1.0/24; # if ( $accessip = 'false') {return 403;} # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; #limit_conn_status 509; #limit_conn_log_level warn; #limit_rate 50; #limit_conn ip 1; return 301 https://$server_name$request_uri; ``` 保存配置,并加载配置。 客户端Client使用Chrome浏览器,并开发者工具,访问: ```bash http://www.example.com ``` 可以看到http请求被301重定向访问至https ![3.2redirect](images/lab_3.2_301.png) ![3.2https](images/lab_3.2_https.png) **恢复环境:** 修改配置,将强制https跳转的配置注释: ```bash server { listen 80; server_name www.example.com; #charset koi8-r; access_log logs/host.access.log main; error_log logs/error.log info; #deny 10.1.1.6; #allow 10.1.1.0/24; # if ( $accessip = 'false') {return 403;} # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; #limit_conn_status 509; #limit_conn_log_level warn; #limit_rate 50; #limit_conn ip 1; #return 301 https://$server_name$request_uri; ``` --- ### 3.3 HTTPS加密 - 多域名证书共用IP 本实验小节,用来验证实际应用场景中,会存在不同的域名,但是提供的服务是相同的。所以就需要在同一个IP中启用不同的域名的证书。 确认以下2个443监听端口的配置中,分别挂载了2套不同的证书: ```bash #HTTPS Server server { listen 443 ssl; server_name www.example.com; ssl_certificate /usr/local/nginx/conf/www.example.com.crt; ssl_certificate_key /usr/local/nginx/conf/www.example.com.key; # ssl_session_cache shared:SSL:1m; # ssl_session_timeout 5m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; #ssl_ciphers ALL; ssl_ciphers ALL:!IDEA; # ssl_ciphers HIGH; # ssl_ciphers HIGH:!aNULL:!MD5; location / { root html; index index.html index.htm; } } server { listen 443 ssl; server_name www.test.com; ssl_certificate /usr/local/nginx/conf/www.test.com.crt; ssl_certificate_key /usr/local/nginx/conf/www.test.com.key; location / { root html; index index.html index.htm; } } ``` 保存配置,并加载配置。 客户端Client使用Chrome浏览器,分别访问2个域名: ```bash https://www.example.com ``` ```bash https://www.test.com ``` 检查每个域名对应的加载的证书信息: - www.example.com域名的证书签发有效期是10年 - www.test.com域名的证书签发的有效期是100天 ![3.3example](images/lab_3.3_example.png) ![3.3test](images/lab_3.3_test.png) --- ## 4-认证实验章节 ### 4.1 HTTP Basic 用户名密码认证 本实验小节,通过NGINX对访问请求做基本的用户名密码认证。 检查配置,参考如下: ```bash location /login { auth_basic "Login Auth"; auth_basic_user_file /usr/local/nginx/conf/plain_pass; root html; index login_auth.html; } ``` 其中,用户名密码文件已经提前通过htpasswd命令生成好,放在/usr/local/nginx/conf/plain_pass文件中,如下格式: ```bash [root@2370ed03-0df5-471a-bc80-3928ece95a4c sbin]# cat /usr/local/nginx/conf/plain_pass #plain password #test:password123 test:$apr1$eWZ.7ICp$a533nPfihwalfGANpy6oE/ ``` 客户端Client使用Chrome浏览器,访问测试: ```bash http://www.example.com/login/ ``` 弹出用户名密码认证窗口: - 输入用户名:test - 输入密码:password123 ![4.1auth](images/lab_4.1_auth.png) 登录成功后,页面提示: ![4.1authok](images/lab_4.1_auth_ok.png) 如果输入用户名密码错误,无法登录: ![4.1authfail1](images/lab_4.1_auth_fail_1.png) 检查log日志信息: ![4.1authfaillog](images/lab_4.1_auth_fail_log.png) --- ## Game Over 恭喜您已经完成本次培训的所有实验内容! :+1: :+1: :+1: Cheers!