游客发表
访问日志里突然冒出一串带手机号的测到L存URL,这不是目标什么稀奇事。上周帮一个客户排查问题,电话nginx的号码access.log里出现了这样的记录:/user/profile?phone=13812345678&from=share。运维同事第一反应是泄露"谁把手机号拼到URL里了",第二反应是测到L存"这玩意儿被搜索引擎爬了怎么办"。
问题就出在这。目标URL里的电话参数会被完整记录在nginx日志、浏览器历史、号码代理服务器缓存、泄露甚至Referer头里。测到L存用户A把带手机号的目标链接分享到微信,用户B点开,电话用户B访问的号码页面如果嵌了第三方统计代码,这个手机号就顺着Referer字段流到了统计平台的泄露服务器上。整条链路没人故意泄露,但手机号就是散出去了。
nginx本身不解析URL参数的业务含义。它看到?phone=13812345678,只知道这是一个query string,跟?id=123没有本质区别。所以指望nginx主动"检测"电话号码泄露,得靠配置规则。
常见做法是在log_format里做手脚。默认的combined格式会把$request完整写进日志,包括query string。改成这样:
log_format safe '$remote_addr - $remote_user [$time_local] ' '"$request_method $uri" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent"'; 注意$request换成了$request_method $uri,query string直接丢掉。代价是排查问题时看不到参数了,得权衡。另一个思路是用map指令对特定参数做脱敏:
map $arg_phone $safe_phone { ~^(\d{ 3})\d{ 4}(\d{ 4})$ $1****$2; default $arg_phone; } 然后在日志格式里用$safe_phone替代$arg_phone。这样日志里保留的是138****5678,既不影响排查,又不至于把完整号码写进磁盘。
nginx层面做脱敏是兜底。根子在前端把手机号放进了GET请求。用户分享页面时,浏览器地址栏里带着?phone=13812345678,这个URL被复制、被收藏、被截图,nginx拦不住。
改成POST不是万能药。POST参数虽然不进URL,但一样会进nginx的$request_body,如果开了client_body_in_file_only或者调试日志,照样落盘。更彻底的做法是后端用session或token关联用户身份,前端根本不传手机号。分享链接里放一个短token,服务端根据token查用户信息。URL干净了,日志干净了,Referer里也不会带出手机号。
有个细节容易被忽略:nginx的error_log在debug级别下会记录完整的请求行,包括query string。生产环境别开debug,或者至少确认error_log的级别是warn以上。
如果非要在nginx层做检测,可以用if配合正则匹配$args,命中手机号格式就返回403或者重定向到脱敏后的URL。但if在location里的行为有坑,更稳妥的是用map加return:
map $args $has_phone { ~(^|&)phone=\d{ 11}(&|$) 1; default 0; } server { if ($has_phone) { return 301 $uri?phone=***; } } 这段配置能拦住直接访问带手机号URL的请求,但拦不住已经泄露的日志。而且return 301会把用户重定向到一个参数被替换的地址,体验上有点怪。实际用的时候得想清楚:是阻断访问,还是只做日志脱敏,还是两者都要。
我倾向于日志脱敏必做,阻断访问看业务。有些老系统就是靠URL传参,一刀切会挂。先上日志脱敏,观察一段时间,再决定要不要在前端改传参方式。nginx配置改起来快,但改错了影响面也大,nginx -t之后记得reload,别restart。
友情链接