发布于21小时前21小时 背景在一次授权内网渗透测试中,目标系统将管理接口限制为“仅办公网 IP 可访问”。外部访问 /admin/ 时返回 403,但应用前方同时存在负载均衡与 Nginx 反向代理。此类架构常见的问题是:后端直接信任客户端可控的 X-Forwarded-For、X-Real-IP 等请求头,导致基于来源 IP 的权限边界失效。本文只讨论授权环境中的边界验证。测试目标是证明访问控制是否可绕过,不应读取、修改管理端业务数据。问题定位首先建立正常请求基线,记录状态码、响应长度和重定向位置,避免将 WAF 页面、登录跳转或缓存结果误判为访问成功。curl -sk -o /dev/null -D - curl -sk -w 'status=%{http_code} size=%{size_download} redirect=%{redirect_url}\n' \ -o /tmp/admin.base 403,可在限定范围内分别测试常见代理头。不要一次加入多个头,否则无法判断究竟是哪一项影响了结果。for header in \ 'X-Forwarded-For: 10.10.20.15' \ 'X-Real-IP: 10.10.20.15' \ 'Forwarded: for=10.10.20.15' \ 'X-Client-IP: 10.10.20.15'; do echo "[*] $header" curl -sk -H "$header" \ -w 'status=%{http_code} size=%{size_download}\n' \ -o /tmp/admin.test done测试地址应选择文档中明确允许访问后台的测试网段,不要根据线上日志猜测真实管理员地址。如果加入某个请求头后状态码由 403 变为 200 或 302,还需要比较页面标题、响应摘要和认证流程,确认不是统一错误页。curl -sk -o /tmp/denied.html curl -sk -H 'X-Forwarded-For: 10.10.20.15' \ -o /tmp/header.html wc -c /tmp/denied.html /tmp/header.html sha256sum /tmp/denied.html /tmp/header.html diff -u /tmp/denied.html /tmp/header.html | head -80技术分析客户端连接经过多层代理时,请求头可能形成以下链路:测试客户端 -> 公网负载均衡 -> Nginx 入口代理 -> Java/PHP/Node.js 应用 -> 基于客户端 IP 的后台 ACL漏洞通常不是“存在 X-Forwarded-For”本身,而是信任边界配置错误。常见情况包括:Nginx 对任意来源启用了 set_real_ip_from 0.0.0.0/0。应用直接读取请求头,而不是读取代理校验后的连接地址。最外层代理没有覆盖客户端提交的同名头,只是在原值后追加地址。多级代理启用了 real_ip_recursive,但可信代理网段配置过宽。后台仅依赖来源 IP,没有独立身份认证和操作授权。以下 Nginx 配置会把所有客户端视为可信代理,属于典型错误:set_real_ip_from 0.0.0.0/0; real_ip_header X-Forwarded-For; real_ip_recursive on; location /admin/ { allow 10.10.20.0/24; deny all; proxy_pass }攻击者能够自行设置 X-Forwarded-For: 10.10.20.15。Nginx 重写客户端地址后,后续 allow 判断可能把请求识别为办公网来源。配置复核取得配置审计权限后,应检查生效配置而不是只查看某个配置文件。Nginx 的 include、容器挂载和自动化模板可能导致实际配置与预期不同。nginx -T 2>&1 | grep -nE \ 'set_real_ip_from|real_ip_header|real_ip_recursive|allow |deny |proxy_set_header' nginx -T 2>&1 | sed -n '1,240p'重点核对以下项目:检查项安全预期set_real_ip_from只包含实际负载均衡器或入口代理的固定地址、专用网段real_ip_header与上游代理写入的标准头一致real_ip_recursive仅在完整掌握代理链且可信网段准确时启用proxy_set_header由入口代理覆盖外部提交的身份相关请求头后台 ACLIP 限制只作为附加控制,不替代登录认证和细粒度授权最小化漏洞验证验证时建议使用无副作用接口,例如后台登录页、健康检查页或专门准备的测试路径。一个可审计的验证过程至少应保留以下证据:不带伪造头时的请求时间、状态码、响应长度。仅增加单个代理头后的请求结果。对应时间点的负载均衡、Nginx 与应用日志。测试源地址、授权工单编号和使用的测试网段。未执行新增用户、导出数据、修改配置等高影响操作的说明。如果有服务器日志权限,可以通过唯一请求标识关联各层日志:REQ_ID="pt-$(date +%Y%m%d%H%M%S)" curl -sk -H "X-Pentest-ID: $REQ_ID" \ -o /dev/null curl -sk -H "X-Pentest-ID: $REQ_ID" \ -H 'X-Forwarded-For: 10.10.20.15' \ -o /dev/null sudo grep -R "$REQ_ID" /var/log/nginx/ /var/log/app/ 2>/dev/null日志留痕与排查默认访问日志通常只记录重写后的 $remote_addr,不利于区分 TCP 对端地址与请求头中的声明地址。建议同时记录两者:log_format proxy_audit '$time_iso8601 request_id=$request_id ' 'peer=$realip_remote_addr client=$remote_addr ' 'xff="$http_x_forwarded_for" xreal="$http_x_real_ip" ' 'method=$request_method uri="$request_uri" status=$status ' 'host="$host" ua="$http_user_agent"'; access_log /var/log/nginx/proxy_audit.log proxy_audit;其中 $realip_remote_addr 可保留启用 realip 模块前的连接地址,$remote_addr 则可能是处理代理头后的地址。排查时可关注以下异常:公网对端地址请求后台路径,但处理后的客户端地址落入办公网段。X-Forwarded-For 中出现重复、私网与公网地址顺序异常。同一 TCP 来源在短时间内声明多个不同内网地址。后台访问成功,但身份系统没有对应登录或二次认证记录。awk '/uri="\/admin\// && /status=200|status=302/' \ /var/log/nginx/proxy_audit.log | tail -100 grep 'uri="/admin/' /var/log/nginx/proxy_audit.log | \ grep -E 'peer=([0-9]{1,3}\.){3}[0-9]{1,3} client=10\.10\.20\.'修复建议第一步是收紧可信代理范围,并让边界代理覆盖客户端提交的转发头。示例配置如下:# 只信任实际负载均衡器地址 set_real_ip_from 192.0.2.10; set_real_ip_from 192.0.2.11; real_ip_header X-Forwarded-For; real_ip_recursive on; location / { # 使用 Nginx 校验后的地址重新生成请求头 proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header Host $host; proxy_pass } location /admin/ { allow 10.10.20.0/24; deny all; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_pass }如果必须保留完整代理链,最外层受控代理应先删除外部同名头,再按明确顺序重建。后端应用不应自行遍历请求头并选择“第一个看起来像内网地址”的值。第二步是调整权限模型。后台接口至少应同时具备独立身份认证、会话校验、角色授权和高风险操作二次确认。IP 白名单适合缩小暴露面,但不能作为唯一认证因素。第三步是上线前验证配置并平滑重载:sudo nginx -t sudo systemctl reload nginx sudo journalctl -u nginx --since '10 minutes ago' --no-pager修复后应重复原始请求,确认伪造头不再改变访问结果,同时从真实办公网或指定测试代理验证合法访问未被误伤。风险评估若绕过后只能访问登录页,风险通常表现为后台攻击面暴露、版本信息泄露及口令攻击入口增加;若后台将来源 IP 直接视为已认证身份,则可能进一步导致未授权配置修改、敏感数据读取或管理功能调用。最终等级应依据实际可达功能、是否需要后续认证以及数据影响确定,不能仅凭状态码变化直接定为高危。总结反向代理场景中的 IP 白名单,本质上依赖一条准确、不可伪造的来源地址传递链。渗透测试时应从基线响应、单请求头变量、代理生效配置和多层日志四个方向交叉验证;修复时则要同时处理可信代理范围、请求头重建、日志审计与后台身份认证,避免只在应用层增加一次字符串判断。 网络请求、日志与边界流量分析示意 代码审计、调用链与关键函数定位示意
21小时前21小时 这个问题通常不只是“读取了错误的请求头”,而是代理链的信任边界没有闭合:应用直接把 X-Forwarded-For 或 X-Real-IP 当成客户端地址,而最外层代理又保留了用户自行提交的同名请求头,于是基于 IP 的后台白名单就可能被伪造。 建议先确认请求实际经过哪些节点,并分别对比代理访问日志与应用看到的地址: curl -i \ -H 'X-Forwarded-For: 127.0.0.1' \ -H 'X-Real-IP: 127.0.0.1' curl -i \ -H 'Forwarded: for=127.0.0.1;proto=https' 同时检查后端端口是否能绕过反向代理直接访问: ss -lntp nft list ruleset curl -i Nginx 侧应只信任明确的上游代理网段,不能配置成信任所有来源;并确保传给应用的地址由代理重新生成: set_real_ip_from 10.10.0.0/16; set_real_ip_from 192.0.2.10; 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、泛化的容器网段,以及应用框架中的全局 trust proxy = true。如果前面还有 CDN 或负载均衡器,应只加入它们公布且实际使用的出口地址,并确认入口只允许这些节点访问;否则攻击者直连源站时仍能自己构造转发头。 应用层也不要通过手工拆分 X-Forwarded-For 后取第一个地址来鉴权。以 Express 为例,可以显式限定代理地址: app.set('trust proxy', [ 'loopback', '10.10.0.0/16', '192.0.2.10/32' ]); app.use('/admin', (req, res, next) => { const allowed = new Set(['203.0.113.20']); if (!allowed.has(req.ip)) { return res.sendStatus(403); } next(); }); 修复后至少应验证三条路径:正常经过代理时能识别真实地址;伪造各类转发头时不能进入白名单;源站或后端端口不能被外部直接访问。最后,后台访问控制不宜只依赖来源 IP,IP 白名单应作为附加限制,仍需保留独立认证、授权与审计日志。
创建帐户或登录后发表意见