发布于5小时前5小时 背景网站接入 CDN、WAF 或四层负载均衡后,源站看到的连接地址通常是代理节点 IP。为了恢复访客地址,运维人员会在 Nginx 中启用 realip 模块,再通过 X-Forwarded-For、X-Real-IP 或厂商专用请求头获取客户端 IP。这类配置直接影响访问控制和日志取证。如果源站无条件信任外部传入的地址头,而后台接口又使用客户端 IP 判断内网权限,攻击者可能在无需进入内网的情况下伪造地址并绕过限制。下面以授权测试环境中的 Nginx 反向代理为例说明完整排查过程。典型场景测试环境链路如下:互联网客户端 -> CDN/WAF -> Nginx 源站 -> Web 应用 /admin 访问规则: 仅允许 127.0.0.1 或 10.0.0.0/8 来源访问源站存在如下配置:set_real_ip_from 0.0.0.0/0; real_ip_header X-Real-IP; real_ip_recursive on; location / { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass }问题不在于启用了 realip,而在于 set_real_ip_from 0.0.0.0/0 将任意来源都定义为可信代理。外部请求只要携带 X-Real-IP,Nginx 就可能使用该值重写 $remote_addr。应用最终看到的是攻击者指定的地址。问题定位先确认请求是否能够绕过 CDN 直接到达源站。只要源站公网端口开放,即使正常域名已经接入 CDN,攻击者仍可能通过历史 DNS、证书透明度记录或资产测绘找到源站地址。# 检查 Nginx 实际监听地址 ss -lntp | grep -E ':80|:443' # 展开全部 Nginx 配置,避免只检查主配置文件 nginx -T 2>&1 | grep -nE 'set_real_ip_from|real_ip_header|real_ip_recursive|proxy_set_header|allow|deny' # 检查 realip 模块是否已编译 nginx -V 2>&1 | grep http_realip_module如果源站地址已知,可以在获得授权后固定解析进行验证,避免修改公共 DNS:# 正常请求,预期返回 403 curl -k -i --resolve test.example:443:192.0.2.10 \ # 伪造真实地址头,仅用于授权测试环境 curl -k -i --resolve test.example:443:192.0.2.10 \ -H 'X-Real-IP: 127.0.0.1' \ # 再使用普通公网测试地址做对照 curl -k -i --resolve test.example:443:192.0.2.10 \ -H 'X-Real-IP: 198.51.100.23' \ 403,而第二条变为 200,基本可以确认地址头参与了权限判断。验证时不能只比较状态码,还应核对响应长度、重定向位置和应用日志,排除缓存或统一错误页面造成的误判。技术分析Nginx 启用 realip 后,几个变量的含义容易混淆:$remote_addr:经过 realip 处理后的客户端地址,可能来自请求头。$realip_remote_addr:建立 TCP 连接的原始对端地址,适合保留代理节点或直连来源。$proxy_add_x_forwarded_for:将当前 $remote_addr 追加到已有 X-Forwarded-For。若上游传入值未被可信入口清洗,错误链路可能继续传递到应用。$proxy_protocol_addr:仅在前端负载均衡启用并正确发送 PROXY protocol 时有效,不能与普通 HTTP 地址头混为一谈。更隐蔽的情况是 Nginx 自身配置正确,但后端框架再次解析 X-Forwarded-For,并直接取最左侧地址。例如应用收到以下内容:X-Forwarded-For: 127.0.0.1, 203.0.113.20如果应用把最左侧地址视为真实来源,攻击者仍可能影响权限判断。因此需要同时检查 Nginx 和应用框架的代理信任设置,例如 Express 的 trust proxy、Django 的代理头处理、中间件自定义 getClientIp 函数以及 Java 网关的 ForwardedHeaderFilter。完善日志留痕默认日志如果只记录 $remote_addr,发生伪造后会直接写入假地址,不利于复盘。建议同时记录 TCP 对端、解析后的客户端地址、转发链和上游响应信息:# 放在 http 上下文 log_format proxy_trace escape=json '{' '"time":"$time_iso8601",' '"request_id":"$request_id",' '"peer_ip":"$realip_remote_addr",' '"client_ip":"$remote_addr",' '"xff":"$http_x_forwarded_for",' '"x_real_ip":"$http_x_real_ip",' '"host":"$host",' '"request":"$request",' '"status":$status,' '"upstream":"$upstream_addr",' '"upstream_status":"$upstream_status",' '"request_time":$request_time,' '"upstream_time":"$upstream_response_time"' '}'; access_log /var/log/nginx/proxy_trace.log proxy_trace;修改后先执行语法检查,再平滑加载:nginx -t && nginx -s reload tail -f /var/log/nginx/proxy_trace.log # 使用 jq 检查 TCP 对端与解析后地址不一致的记录 jq -c 'select(.peer_ip != .client_ip)' \ /var/log/nginx/proxy_trace.logpeer_ip 与 client_ip 不一致不一定代表攻击,因为经过可信 CDN 时这是正常现象。排查重点是 peer_ip 不属于代理节点网段,但 client_ip 却是回环、内网或管理网地址的记录。安全配置方法第一步是只信任实际存在的反向代理网段,不要使用 0.0.0.0/0。以下地址仅为文档示例,生产环境应使用 CDN 或负载均衡厂商发布并维护的真实网段:# 只信任明确的代理节点 set_real_ip_from 192.0.2.0/24; set_real_ip_from 2001:db8:100::/48; real_ip_header X-Forwarded-For; real_ip_recursive on;如果 CDN 提供专用地址头,应确认该请求头只可能由可信节点写入。源站仍需限制直连,否则攻击者可以绕过 CDN 自行构造同名请求头。第二步是在安全组、防火墙或边界负载均衡上限制源站入口。以主机防火墙为例,应先确认远程管理链路,避免误封 SSH:# 示例逻辑:仅允许代理网段访问 HTTPS iptables -A INPUT -p tcp -s 192.0.2.0/24 --dport 443 -j ACCEPT iptables -A INPUT -p tcp --dport 443 -j DROP # 检查实际规则与计数器 iptables -L INPUT -n -v --line-numbers云环境中优先使用安全组、负载均衡后端策略或经过认证的源站回源机制。条件允许时,可在 CDN 与源站之间部署双向 TLS,避免仅依赖来源 IP。第三步是规范向后端传递的地址。后端如果没有解析完整代理链的必要,可以传递一个由 Nginx 统一计算的受控请求头:location / { proxy_set_header X-Client-IP $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $host; proxy_pass }应用只读取 X-Client-IP 的前提是应用端口不能被外部直接访问,并且该请求头只能由受控 Nginx 写入。否则只是把信任问题换了一个名字。权限边界与修复建议不要把来源 IP 当作后台登录的唯一认证条件。管理接口仍应启用身份认证、角色授权和必要的多因素认证。内网地址只适合作为附加限制,不能代替会话鉴权。尤其不要用字符串前缀判断 10.、127. 或 192.168.。限制应用监听范围。单机部署可监听 127.0.0.1,多主机部署则通过安全组只允许反向代理访问后端端口。定期同步 CDN 地址段,并在变更前执行配置测试。过期网段既可能造成业务中断,也可能继续信任已被释放的地址资源。审计应用代码中对 X-Forwarded-For、X-Real-IP、Forwarded 和自定义客户端地址头的读取位置。告警规则应关注外部 TCP 对端提交内网地址、回环地址、链路地址,以及同一对端频繁切换客户端地址的行为。保留 request_id,并让 Nginx、网关和应用日志使用同一关联标识,便于还原完整请求链路。修复后的回归验证修复后至少覆盖三组测试:通过正常 CDN 访问时能够获得正确客户端地址;直接访问源站时被网络层拒绝;伪造地址头时不能改变权限结果。还应确认健康检查、证书续期和监控节点没有被新的入口策略误伤。# 配置检查 nginx -t # 通过正常入口访问 curl -k -i # 通过源站直连并伪造头,预期连接失败或仍返回 403 curl -k -i --resolve test.example:443:192.0.2.10 \ -H 'X-Real-IP: 127.0.0.1' \ -H 'X-Forwarded-For: 127.0.0.1' \ IP 配置本质上是信任边界配置。排查时不能只看某一个请求头,需要同时确认源站是否可直连、哪些代理地址被信任、Nginx 如何重写变量、后端框架如何再次解析以及日志是否保留原始 TCP 对端。可靠的修复方式是收紧代理信任范围、限制源站入口、统一地址传递规则,并让管理接口使用独立的身份认证与授权机制。 服务器、云资产与权限边界梳理示意 网络请求、日志与边界流量分析示意
4小时前4小时 这类绕过通常不是“取错了 IP”这么简单,而是应用无条件信任了客户端可控的 X-Forwarded-For,再叠加“内网 IP 可访问后台”的授权逻辑。建议按请求链逐跳确认:客户端 → CDN/LB → Nginx → 应用,明确只有哪一层有资格写真实客户端 IP。 如果 Nginx 是唯一入口,应只信任实际代理节点网段,并让 realip 模块从右向左剥离可信代理: set_real_ip_from 10.20.0.0/16; # 实际 LB 网段 set_real_ip_from 192.168.50.10/32; # 实际反代地址 real_ip_header X-Forwarded-For; real_ip_recursive on; location / { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_pass } 不要配置成 set_real_ip_from 0.0.0.0/0,也不要直接在应用里取 X-Forwarded-For 的第一个值作为客户端地址。若公网请求可以绕过 Nginx 直连后端,还需要在安全组或防火墙上封掉后端端口,只允许反代节点访问。 可以用下面几组请求快速复现和验证: # 伪造内网地址,修复后不应获得后台权限 curl -i \ -H 'X-Forwarded-For: 127.0.0.1' curl -i \ -H 'X-Forwarded-For: 10.0.0.8, 127.0.0.1' # 同时检查 IPv6 和等价表示 curl -i \ -H 'X-Forwarded-For: ::1' 排查日志时最好同时记录连接来源、解析后的地址和原始转发头,避免只看到被改写后的变量: log_format realip '$remote_addr realip_remote=$realip_remote_addr ' 'xff="$http_x_forwarded_for" request="$request" status=$status'; access_log /var/log/nginx/access.log realip; 应用层也要检查框架的代理信任配置。例如 Express 不要写成无条件信任: // 仅信任明确的反代网段 app.set('trust proxy', ['loopback', '10.20.0.0/16']); // 不建议:app.set('trust proxy', true); 更关键的是,后台权限不要以来源 IP 作为唯一授权条件。IP 白名单最多作为附加限制,实际访问仍应要求登录态、角色权限和必要的二次认证。咖啡馆、公网 NAT、VPN、代理链都会让基于 IP 的身份判断变得不可靠。修复后还应回查访问日志,搜索伪造的私网、回环地址以及异常多段 X-Forwarded-For,确认是否已经发生过利用。
创建帐户或登录后发表意见