一、前言
近期公司要求对门户网站域名URL信息进行安全整改限制,避免一些敏感信息泄露通过门户网站整体暴露出去,造成用户信息泄露。如某些域名后端的URL路由禁止外网访问,仅允许公司内网用户访问;此时,熟悉Nginx 用户的小伙伴首先第一时间就会想到通过deny\allow进行限制;那么大家有没有想过一个问题,该规则真的适用吗?接下来带大家验证逐一分析演示
二、问题需求
2.1、实际配置
下述是一个很简单的Nginx location规则,当用户访问路由匹配到/visit.html时,默认拒绝所有,仅允许公司内网访问;
location /visit.html {
add_header X-Frame-Options SAMEORIGIN;
alias /var/www/html/ywq/am/index;
index visit.html;
allow x.x.x.x; #公司内网出口IP
deny all; #拒绝所有
}
2.2、分析思路流程
用户访问反馈404页面,那么问题来了,为什么做了deny all\allow 策略只有直接返回404呢?正常情况如果被拒难道不是会返回403状态码吗?
此时不要慌,先确认下匹配路由之后是否真实生效,于是乎打开浏览器控制台近一步检索问题。可以发现
我们模拟用户的请求是返回403的,说明我们的deny限制是真实有效的,只是浏览器页面没有给我们直接返回403拒绝页面而已,那么为什么浏览器没有给返回403呢?接下来我们继续放下看。控制台可以清晰的看到403下面还有其它GET请求信息, 毫无疑问,它就是造成我们用户看到页面404的罪魁祸首。那么为什么我们明明会这样呢?此时我们就需要到Nginx.conf配置文件里面去看了
GET https://www.daixiaorui.com/Public/images/head404.png net::ERR_CERT_DATE_INVALID
GET https://www.daixiaorui.com/Public/images/txtbg404.png net::ERR_CERT_DATE_INVALID
来到Nginx配置这块,发现如下配置,这就能解释为什么我们访问url 403被拒绝了却跳转到其它不知名的域名404页面,下面我们对这些参数分析一波,下述配置在Nginx web服务器上设置错误页面处理和代理拦截;为了便于理解,我直接指定注释
proxy_intercept_errors on; #Nginx在代理过程中拦截错误,从而能够显示自定义的错误页面
error_page 404 403 /404.html; #当发生404或者403错误时,都会统一展示/下的404.html页面
error_page 404 /404.html; # 与上行解析相同,实际上定义了重定向,会将404错误页面指向了/404.html
location = /40x.html { #这里定义了404页面实际的路径
}
error_page 500 502 503 504 /50x.html; #同时这里含义与上述一致
location = /50x.html {
}
location = /404.html { #定义了404错误页面位置,并在返回的时候添加了X-Frame-Options头部信息以及指定页面的根目录位置/var/www/html/;
add_header X-Frame-Options SAMEORIGIN always;
root /var/www/html;
}
经过上述一系列分析,我们知道配置上deny策略之后返回了404页面原因;实际上我们配置的限制策略是正确的,对于URL来说,确实是403拒绝了,无非是将403重定向404页面而已。接下来我们验证一下内网发起访问请求,并追踪具体日志;
结果令人感到意外,在内网同样无法正常访问,仍然是拒绝URL请求。下述是捕获的三段不同的日志信息;
内网访问日志输出
[11/Apr/2024:18:07:12 +0800] "-" 100.122.16.18 "219.142.137.159, 221.195.20.60" 403 1524 "-" "GET /visit.html HTTP/1.1" 80 "PostmanRuntime/7.37.3" 0.000 - 726 "-" "-" "-" "-" "-" "-" "-" "-" "-"access_log/var/log/nginx/accelogdebugss.logdebug
外网访问日志输出
[11/Apr/2024:18:08:42 +0800] "-" 100.122.16.47 "2409:8a00:54ce:ff20:1457:d753:b7ea:16d9, 221.195.20.60" 403 1524 "-" "GET /visit.html HTTP/1.1" 80 "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" 0.000 - 1331 "-" "-" "-" "-" "-" "-" "-" "-" "-"access_log/var/log/nginx/access.
外网iphone访问
[11/Apr/2024:19:06:36 +0800] "-" 100.122.19.48 "2409:8900:5420:14b4:98e7:2a8d:9a01:325f, 221.195.20.60" 403 1524 "-" "GET /visit.html HTTP/1.1" 80 "Mozilla/5.0 (iPhone; CPU iPhone OS 15_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 wxwork/4.1.20 MicroMessenger/7.0.1 Language/zh ColorScheme/Dark" 0.000 - 946 "-" "-" "-" "-" "-" "-" "-" "-" "-"access_log/var/log/nginx/access.logdebug
为什么允许公司内网访问IP同样做403处理呢?难道是因为我限制的IP地址不对还是因为我限制方式有问题呢?
于是乎模拟在不同网络环境下访问,并对其输出的日志信息进行分析
[11/Apr/2024:18:07:12 +0800]:这是记录的时间戳,表明记录发生的时间为2024年4月11日18点07分12秒,时区为+0800。
"-":这个字段通常包含了用户身份验证信息,因为是"-",说明这里没有提供身份验证信息。
100.122.16.18:这是客户端的IP地址,表示发起请求的客户端IP地址。
"219.142.137.159, 221.195.20.60":这是表示请求经过的代理服务器IP地址,第一个IP表示最近一级代理,第二个IP表示更早之前的代理。这表明请求通过了两个代理服务器。
403:这是HTTP状态码,表示服务器拒绝了请求。
1524:这是响应的大小,以字节为单位。
"-" "GET /visit.html HTTP/1.1":这是发起的请求行,GET请求访问了/visit.html页面,使用的是HTTP/1.1协议。
80:这是指定的端口号。
"PostmanRuntime/7.37.3":这是用户代理(User-Agent)字符串,指明了发起请求的客户端应用程序。
0.000:这是最后一个参数,它表示处理请求所花费的时间,以秒为单位。
通过上述日志分析可以看到,我们能允许的只有两组IP地址,如下
100.122.16.18
219.142.137.159 221.195.20.60
要想确定我们要允许什么字段的IP才能真正实现我们内网访问,前提就是我们要弄明白日志中携带的两组IP地址是什么意思
Ps: 上述我们截图我们是为了便于理解,通过阿里云日志系统中做了过Nginx日志配置,通过log_format做了提取;这样可以在平台上更直观的看到日志输出的情况,如下所示
log_format debug '[$time_local] "$upstream_addr" $remote_addr "$http_x_forwarded_for" '
'$status $body_bytes_sent "$http_referer" "$request" $server_port '
'"$http_user_agent" $request_time $upstream_response_time $request_length "$request_body" "$http_version" "$http_deviceid" "$http_clientId" "$http_userId" "$http_phoneBrand" "$http_phoneVersion" "$http_phoneModel" "$http_deviceType"'
http_x_forwarded_for: 219.142.137.159,221.195.20.60
该字段是用来记录经过代理服务器的IP地址,由于http请求可能会经过多个代理服务器,这个字段可能会包含多个IP,通过逗号分隔,在一些设置合理的代理服务器中,代理服务器可以将请求的原始IP地址添加到请求头的“x_forwarded_for”字段中,以便服务器能够获取到最初发起的请求客户端的真实IP地址
remote_addr: 100.122.18.76
:
该字段是用来记录发起请求的客户端的IP地址,主要记录实际发起请求的客户端IP地址,该地址在Tcp层传递过来的客户端真实地址,相对可靠。
当你允许对应的IP地址访问时,如果你希望允许最初发起请求的客户端的真实IP地址,那么应该允许“remote_addr”对应的IP,如果你希望允许代理层传递过来的IP地址,那么你就允许"http_x_forwarded_for"对应的IP地址,在实际应用中,为了确保安全性,通常会结合使用这两个字段来获取客户端的真实IP地址,在验证"http_x_forwareded_for"中的IP地址是否在你信任的代理服务器IP列表中,并确保"remote_addr"中的IP地址也符合个人预期。
三、解决方案
我们说要允许在某一局域网的客户端的IP,我们一般怎么限制呢?我们不可能把某一网络环境下所有的客户端IP地址都一一对应写到白名单列表吧?这不现实,如果对局域网限制一般会限制该网络的出口IP,那么出口IP怎么查询呢,很简单,直接百度搜IP即可查询到当前所在网络的IP地址,如下图所示:
location /visit.html { #指定了对访问/visit.html页面的处理规则。
if ( $http_x_forwarded_for !~ "(x.x.x.x|x.x.x.x)" ) 使用条件判断,如果"http_x_forwarded_for"中的IP地址不匹配给定的正则表达式就会执行接下来的操作
{
return 403; #如果条件判断成立,即请求的"$http_x_forwarded_for"中的IP不在指定的IP列表中,那么返回状态码403,拒绝访问
}
add_header X-Frame-Options SAMEORIGIN; #响应头中增加"X-Frame-Options"字段,设置为"SAMEORIGIN",这是为了防止页面钱遇到其它网站中
alias /var/www/html/ywq/am/index; #指定了请求文件被映射到具体的路径
index visit.html; #指定了默认索引文件
}
四、总结
在面对上述需求,我们只需限制局域网的出口IP即可,而该局域网出口IP一般都是固定的,在生产代理配置中该IP会经过代理层,既然如此,http_x_forwarded_for字段必然会将出口IP进行绑定,因此我们只需要对其http_x_forwarded_for对应的IP做放心策略即可,其它默认拒绝,相对一开始提到的allow x.x.x.x/dent allow这样的策略更具灵活性。