简介burpFakeIP 是一款面向 Web 安全测试与渗透测试人员的 Burp Suite 扩展插件核心用途是在请求过程中模拟不同源 IP帮助测试者隐藏真实身份、模拟多用户来源或验证目标站点基于 IP 的访问控制与地理限制策略。压缩包共 12 个文件大小约 1.09MB包含 1 个 Python 脚本、1 份 Markdown 说明、1 份 txt 文档和 9 张 png 截图脚本承载插件主要逻辑文档交代配置与使用方式截图则直观呈现界面和测试效果。目前已有 413 人学习。对于从事授权渗透测试、隐私保护或多源访问模拟的安全从业者这份资料可作为理解 Burp Suite 插件机制、参考 IP 伪装实现思路的实践素材也便于结合截图和说明快速熟悉插件的功能边界使用时需遵守法律法规并取得测试授权。1. 一次靶场演练的意外burpFakeIP 到底值不值得装进 Burp 插件栏某次内部靶场演练后台页面标着“仅内网可访问”我在 Repeater 里手加了一行X-Forwarded-For: 127.0.0.1管理员菜单直接就出来了。漏洞本身不高级问题出在目标把“客户端随手能改的请求头”当成了“网络层的事实”。这类场景在 burp suite 插件生态里有个很直接的解法——burpFakeIPgithub 上能拿到的这份源码包干的事就一句话把伪造 IP 的请求头按规则批量塞进每一个经过 Burp 的请求省掉你一条条手改的功夫。接下来讲清楚四件事什么场景下伪造头才有意义、怎么在授权靶场里跑通第一次复现、限流/白名单/多级代理这三类判定链分别怎么打、以及五个让我白忙半天的坑。新手能照着把环境和验证跑起来熟手可以直接跳到第 4 章的判定链拆解和第 5 章的排查表。2. 先分清三种假 IP应用层伪造才是 burpFakeIP 的战场2.1 网络层、应用层、日志层三条完全不同的路刚接触 IP 伪造的人容易把三件事混成一锅改 TCP 源地址、改 HTTP 头、改日志里记的 IP。这三条路的难度差着数量级搞混了就会在错误的方向上耗掉一整天。网络层伪造指的是让数据包的源 IP 不是自己的。这条路在真实环境里基本走不通你得有中间链路的路由控制权即便包发出去了回程的 SYN-ACK 也回到那个伪造的地址上三次握手根本建立不起来。除非你能在链路上做中间人否则这条路的实战价值接近零。判断依据很简单——只要目标还要求你完成完整 TCP 握手你就没法在网络层骗它。应用层伪造才是 burpFakeIP 的主战场。HTTP 是无状态协议代理链路为了让后端知道“最初的客户端是谁”约定俗成用一堆自定义头来传递原始 IPX-Forwarded-For、X-Real-IP、Client-IP、True-Client-IP、X-Client-IP、CF-Connecting-IP、X-Originating-IP、X-Remote-IP、X-Remote-Addr以及 RFC 7239 定义的标准化写法Forwarded: for1.2.3.4。这些头本质上就是普通请求头——只要中间没有代理把它们剥掉客户端想写什么就写什么。日志层伪造是副产品。很多应用把取到的 IP 写进审计日志、风控流水、登录记录一旦上游采信了伪造的头日志里留下的就是假地址。这一层本身不是攻击面但它决定了事后能不能追溯也决定了防守方复盘时看到的是真相还是幻觉。注意判断一个目标会不会采信请求头唯一可靠的办法是实测响应差异不是猜框架。同一套代码里登录接口可能读真实源地址后台鉴权却读X-Forwarded-For这种情况一点都不罕见。2.2 三步判定法先建基线再单点注入最后看差异我一般不会一上来就把九个头部全塞进去。全塞的结果是响应变了你也不知道是哪个头起的作用等于自己给自己制造黑匣子。稳妥的做法是三步第一步建基线。用最干净的原始请求打三次记录状态码、响应体长度、Location跳转目标、Set-Cookie有没有变化、以及服务端返回的时间戳。这一步的目的是拿到一个“什么都没改时长什么样”的参照物后面所有差异都相对它来比。第二步单点注入。一次只加一个头值统一用127.0.0.1。优先试X-Forwarded-For其次X-Real-IP再次Client-IP最后才是那几个冷门的。多数框架的中间件只认前一到两个按热度排序能省掉一半时间。第三步比对差异。关注的不是“页面有没有变”而是这几个信号状态码从 403 变 200、响应体长度突变、出现新的 Cookie、跳转目标从登录页变成首页、以及响应时间明显变短说明绕过了某个鉴权中间件。任何一个信号出现就说明这个头进了判定链。下面这段是把九个头挨个打一遍的最小脚本只适合打自己的靶场跑之前把TARGET换成你环境里的地址#!/bin/bash # 逐个替换伪造 IP 头观察响应差异仅用于自建靶场 TARGEThttp://127.0.0.1:8080/admin HEADERS( X-Forwarded-For: 127.0.0.1 X-Real-IP: 127.0.0.1 Client-IP: 127.0.0.1 X-Client-IP: 127.0.0.1 X-Originating-IP: 127.0.0.1 X-Remote-IP: 127.0.0.1 X-Remote-Addr: 127.0.0.1 True-Client-IP: 127.0.0.1 Forwarded: for127.0.0.1;protohttp ) # 先取基线状态码 响应体字节数 BASE$(curl -s -o /dev/null -w %{http_code} %{size_download} $TARGET) echo baseline - $BASE for h in ${HEADERS[]}; do # -H 只加一个头其余保持默认保证变量唯一 R$(curl -s -o /dev/null -w %{http_code} %{size_download} -H $h $TARGET) # 与基线不同的才打印避免刷屏 [ $R ! $BASE ] echo [DIFF] $h - $R done脚本里最关键的不是循环是-o /dev/null -w这组参数把响应体丢掉只留状态码和字节数输出量从几十 KB 压到一行几十个请求扫下来肉眼就能看完。[ $R ! $BASE ]做的是差异过滤只有和基线不一样的组合才会打出来。如果你发现所有头都没差异别急着怀疑插件——先按第 2.3 节的办法确认目标到底有没有采信头部。2.3 插件在 Burp 里的定位它是“手动挡的放大器”不是扫描器搞清楚 burpFakeIP 是什么比会用更重要。它不是漏扫不会告诉你哪里有洞它也不是自动化的绕过工具不会自己判断该塞哪个头。它做的事只有一件在你手工测试的过程中把你本来要一条条手敲的请求头按预设规则自动加到每一个流经 Burp 的请求上。这个定位决定了它的正确用法。你应该是先在 Repeater 里手工确认了“某个头能影响判定”然后再开插件把这一条经验批量应用到几十上百个请求上——比如在 Intruder 里跑目录爆破时每个请求都带上伪造头避免因为“IP 变了”导致限流计数被重置或者反过来让每个请求看起来都来自不同 IP。顺序反了就会翻车先开插件再手工测你根本分不清响应变化是插件带来的还是你改的包带来的。判断一个目标会不会采信头部除了 2.2 的实测法还有两个间接信号值得先看一眼。一是响应头里有没有Via、X-Cache、X-Served-By这类代理痕迹——有说明链路上存在会处理转发头的中间件采信概率明显更高。二是错误页面。有些框架在异常时会打印完整的请求头列表翻一眼就知道后端有没有读取X-Forwarded-For这比盲试九个头部省事得多。3. 跑通第一次伪造从 jar 加载到自建靶场验证的四步3.1 加载方式直接挂载 jar 还是本地构建源码拿到burpFakeIP-master.zip之后先解压看一眼目录结构这一步决定了你走哪条路。如果根目录里直接躺着.jar文件最短路径就是用 Burp 的 Extender 挂载如果只有src/和构建脚本那就得自己编。直接挂载的流程是固定的打开 Burp切到Extensions → Installed点AddExtension type 选Java然后在 Extension file 里选中那个 jar。加载成功的标志是 Output 面板里出现一行 “extension loaded”同时列表里多出一条带勾选的记录。加载失败最常见的三种报错extension failed to load多半是 jar 不完整或者下载被截断、NoClassDefFoundError依赖没打进去得用带依赖的 fat jar、UnsupportedClassVersionError编译用的 JDK 比 Burp 自带的运行时新。编译源码的话先看根目录有没有pom.xml或者build.gradle。有的话按项目自带的构建方式走一遍注意一件事编译时用的 JDK 版本不要高于 Burp 自身运行的 Java 版本因为 Burp 用的是它自带的运行时不是系统里那个。版本对不上就是上面那个UnsupportedClassVersionError报错信息里会直接写 “class file version 61.0”61 对应 Java 1755 对应 Java 11对一下就知道该降级还是升级。提示加载完插件别急着开规则。先在 Extender 的 Output 里确认没有红色堆栈再去做第 3.3 节的靶场验证。带病运行只会把问题拖到后面排查成本翻倍。3.2 规则配置注入哪几个头、用什么值、对哪些 host 生效这类插件的配置项大同小异核心就三组注入哪些头、头部的值怎么生成、作用范围多大。下面的表是我一般会配的初始组合可以直接抄后面再按目标微调。配置项作用建议初始值调优依据启用开关全局控制是否注入先关测完再开避免干扰手工基线头部列表决定注入哪些头X-Forwarded-ForX-Real-IP覆盖八成框架IP 取值方式固定值 / 随机 / 网段固定127.0.0.1先验证有效性网段范围随机模式下取值区间10.0.0.0/8匹配目标内网假设作用域名只对哪些 host 生效单个靶场域名防止污染其他流量是否覆盖原值已有同名头时替换还是追加追加保留链路真实性“是否覆盖原值”这一项最容易被忽略但它直接决定你在多级代理场景下能不能复现成功。追加和替换的差别在 XFF 链的解析上就是两个完全不同的结果第 4.3 节会展开。配置完之后建议先在一个无害的接口上做一次冒烟随便发个请求去 Burp 的Proxy → HTTP history里看那条请求的 Raw 面板确认头真的被加进去了。看不到就说明作用域名写错了或者开关没生效别往下走。3.3 一个三十行的自建靶场用来验证头到底生效没有没有靶场就测伪造等于蒙眼开车。下面这个 Flask 服务模拟的是最常见的错误写法——直接把X-Forwarded-For当成客户端 IP 来做白名单判断。它故意写错就是为了让你能观察到“头一改结果就变”的完整因果。# vuln_lab.py —— 故意写错的靶场仅用于本地验证 from flask import Flask, request app Flask(__name__) # 后台允许访问的“内网”网段模拟配置错误的白名单 ALLOWED_PREFIX 10. def get_client_ip(): 错误示范无条件信任 X-Forwarded-For 的第一个值 xff request.headers.get(X-Forwarded-For, ) if xff: # 只取第一个逗号前的部分这正是伪造能生效的根源 return xff.split(,)[0].strip() return request.remote_addr or 0.0.0.0 app.route(/admin) def admin(): ip get_client_ip() if ip.startswith(ALLOWED_PREFIX): return fADMIN OK, your ip is {ip}, 200 return fFORBIDDEN, your ip is {ip}, 403 if __name__ __main__: # 关掉 debug避免堆栈干扰响应体长度比对 app.run(host127.0.0.1, port8080, debugFalse)代码里有三个点值得盯住。get_client_ip()是漏洞的根它没有任何“这个头是否来自可信代理”的判断谁写就信谁。xff.split(,)[0]取第一个值意味着你可以直接把10.1.2.3写进去前面什么都不用拼。request.remote_addr作为兜底只有在完全没有 XFF 头的时候才用得上——也就是说正常从本机访问会返回127.0.0.1触发 403。跑起来之后用 curl 打两次就够验证了不带头发一次应该看到FORBIDDEN, your ip is 127.0.0.1带上-H X-Forwarded-For: 10.1.2.3再打一次应该返回ADMIN OK, your ip is 10.1.2.3。两次结果的差异就是全部证据。3.4 第一次验证从 Repeater 到 Intruder 的观察点靶场跑通之后把 Burp 的代理挂上用浏览器或者 curl 走127.0.0.1:8080。抓到/admin这个请求后右键Send to Repeater先在插件关闭的状态下手工加一次头确认在 Burp 里能得到和前面 curl 一致的结果。这一步是在校准工具不是在测目标。然后开插件回到 Repeater 点 Send看响应。这里有个细节Repeater 里手动加的头和插件自动加的头会叠加如果插件配置成“追加”模式你会看到两个X-Forwarded-For行或者一行里两个逗号分隔的值。第一次见到这个现象很容易以为插件坏了其实是正常行为具体怎么处理见第 5.2 节。再往后是把验证规模放大。把请求丢进 Intruder攻击类型选SniperPayload 位置选在请求行或者某个参数上同时在插件里把 IP 取值方式设成“随机网段”。这时候观察的重点变了不再是“能不能过”而是“过了之后还能不能保持”。很多目标在第一次伪造时会放行但会在响应里种一个带 IP 指纹的 Cookie后续请求只要 IP 和 Cookie 对不上就降权。这种细节只有把请求量拉起来才看得见。到这一步工具算是真正跑起来了。接下来才是正题——三类不同的判定链打法完全不一样。4. 三类判定链复现限流、白名单、多级代理各怎么打4.1 场景一基于 IP 的限流与验证码触发限流是最容易验证、也最容易出成果的场景。逻辑很直白服务端用一个计数器记录“某个 IP 在时间窗口内请求了多少次”超了就返回 429 或者弹验证码。问题在于这个“某个 IP”如果取自请求头计数器就等于形同虚设——你每发一个请求换一个 IP对方的计数永远停在 1。靶场里复现这段很简单给 3.3 的服务加一个按 IP 计数的装饰器# 追加到 vuln_lab.py模拟基于头部 IP 的限流 from collections import defaultdict import time HITS defaultdict(list) # ip - [时间戳列表] WINDOW, LIMIT 60, 5 # 60 秒内最多 5 次 def rate_limited(ip): now time.time() # 只保留窗口内的记录过期的直接丢弃 HITS[ip] [t for t in HITS[ip] if now - t WINDOW] if len(HITS[ip]) LIMIT: return True HITS[ip].append(now) return FalseHITS以 IP 为 keyWINDOW和LIMIT决定阈值。关键在调用处——如果传进去的ip来自get_client_ip()那这个限流器就是死的。用同样的思路验证不带头发六次第六次会被拦带上不断变化的X-Forwarded-For每发一次换一个值六次全部通过。这个场景在 Burp 里的标准玩法是 Intruder 的Pitchfork模式一个 payload 位放业务参数比如用户名列表另一个 payload 位放 IP 列表两列一一对应每个请求配一个不同的伪造 IP。这样跑出来的结果最能说明问题——如果目标真的按 IP 限流你会看到请求成功率从个位数一路拉到接近 100%。需要留个心眼的是限流粒度。有些实现不只按 IP 计数还叠加了账号、设备指纹、UA。这时候单纯换 IP 只能把成功率从 0 拉到 30% 左右剩下 70% 卡在别的维度上。看到这种“部分生效”的现象别急着下结论说插件没用先把 UA 和 Cookie 也一并轮换再看曲线。4.2 场景二后台 IP 白名单的判定链白名单场景比限流复杂因为判定可能发生在三个完全不同的层次而只有其中一层对头部伪造敏感。搞不清这一点你会把大量时间浪费在注定无效的地方。第一层是反向代理层的 ACL。Nginx 的allow/deny、Apache 的Require ip看的都是 TCP 连接的真实源地址也就是remote_addr。这一层完全不吃伪造头因为它在解析 HTTP 之前就做完了判断。看到配置里写allow 10.0.0.0/8; deny all;直接放弃这条路换目标。第二层是应用框架层的白名单。这是最常见也最有价值的战场。典型写法是在过滤器或者中间件里取 IP 然后比对网段。下面这段 Java 是错误写法和正确写法的对照// 错误写法直接从请求头取值任何人写什么就是什么 String ip request.getHeader(X-Forwarded-For); if (ip ! null ip.startsWith(10.)) { chain.doFilter(request, response); // 伪造头直接放行 return; } // 正确写法只在“最近一跳是可信代理”时才采信头部 String remote request.getRemoteAddr(); String ip; if (TRUSTED_PROXIES.contains(remote)) { // 从右往左取跳过可信代理第一个不可信的才是真实客户端 ip parseFromRight(request.getHeader(X-Forwarded-For), TRUSTED_PROXIES); } else { ip remote; // 直连时忽略一切头部 }request.getRemoteAddr()拿到的是 TCP 层的地址客户端改不了TRUSTED_PROXIES.contains(remote)是整段逻辑的关口只有确认“这一跳是我自己的代理”才往下走parseFromRight是从右往左扫描遇到第一个不在可信列表里的地址才停。三段缺一段白名单就是纸糊的。第三层是业务代码层的二次校验。有些系统在登录时校验一次 IP后续每个请求再用 Session 里存的 IP 比对。这一层往往还叠加了 Session 绑定单纯换 IP 反而会触发“异地登录”风控把人踢下线。打这种目标要先确认绑定关系别一上来就开插件狂发。4.3 场景三多级代理下真实 IP 的提取顺序XFF 的拼接规则是这套东西里最容易翻车的地方没有之一。约定是每经过一层代理代理把自己看到的上一跳地址追加到末尾。所以一个正常经过两层代理的请求头会长这样X-Forwarded-For: 客户端真实IP, 第一层代理IP, 第二层代理IP注意最左边那个是客户端自己声明的——也就是说你在最左边写什么都行后面的代理只会往后追加不会回头校验前面那段。这就是伪造能奏效的机制根源也是很多解析代码写错的地方。解析正确姿势是从右往左数跳过所有你认识的可信代理遇到的第一个不认识的地址才是“最接近真实的客户端”。下面这段 Python 是这个逻辑的最小实现# parse_xff.py —— 从右往左剥离可信代理取第一个不可信地址 TRUSTED {10.0.0.1, 10.0.0.2} # 自家代理的地址列表 def real_ip(xff_header, remote_addr): if not xff_header: return remote_addr # 没有头部就只能信 TCP 源地址 # 拆成列表并去掉空白顺序保持原样左 - 右 hops [h.strip() for h in xff_header.split(,) if h.strip()] # 反转后从最近一跳开始往前找 for hop in reversed(hops): if hop not in TRUSTED: return hop # 第一个非可信地址即为客户端 return hops[0] if hops else remote_addr # 全可信时退化为最左值TRUSTED是白名单集合必须由运维侧维护不能从请求里读。reversed(hops)决定了扫描方向——如果写成正序遍历你拿到的就是攻击者随便写的那个值。最后那行兜底看起来无害实际是个坑全链路都可信的情况下退化成最左值等于又信了客户端声明正确做法应该是返回remote_addr。这个场景在 Burp 里的复现手法是 Intruder 的Cluster bomb专门用来打 XFF 的拼接位置。一个 payload 位放“前置伪造值”比如随机的内网地址另一个位放“后缀可信代理地址”组合起来就能测出目标是取最左、取最右还是取某个固定位置。测出规律之后插件的“是否覆盖原值”配置就该跟着调整——追加模式适合打取最左的实现替换模式适合打取唯一值的实现。4.4 复现记录模板把“疑似生效”变成能复现的证据伪造 IP 这类测试最大的问题是不可复现。今天过了明天同样操作就 403你自己都说不清是环境变了还是运气好。我的做法是每次验证都填一张固定格式的记录表字段不多但能逼着我把变量固定下来。记录字段内容示例为什么要记请求时间精确到分钟排除时间窗口类限流干扰目标接口/admin、/api/login不同接口判定链不同注入头部X-Forwarded-For单变量原则头部值10.1.2.3网段匹配是关键基线响应403 / 62 字节差异比对参照物伪造后响应200 / 1284 字节唯一有效证据是否带 Cookie带 / 不带排除 Session 绑定影响插件状态开 / 关区分手工与自动结果这张表的实际价值在于排查。当某次结果和记录对不上时逐行比对就能定位到是哪个变量变了——八成是 Cookie 没清、或者插件状态和上次不一样。血泪经验我曾经因为浏览器缓存了一个旧 Session连续三次复现结果不一致最后发现问题根本不在 IP 上。5. 排查清单五个让我白忙半天的坑5.1 坑一头明明加上了响应一个字节都没变现象在 HTTP history 里确认请求头确实被注入了但响应状态码、长度、跳转目标全部和基线一致反复换 IP 值也没用。原因最可能是目标的判定链根本不看头部。前面说过Nginx 的allow/deny、云厂商的负载均衡 ACL、防火墙的源地址策略全都在 HTTP 解析之前就完成判断你在应用层怎么改都影响不到它们。第二种可能是目标虽然读了头部但只读某一个特定的头而你注入的正好不在列表里。解决先按 2.2 的三步法把九个头部挨个单点打一遍确认有没有任何一个能引起差异。全都无差异就去看响应头里有没有Server: nginx这类标识结合 4.2 的三层模型判断是代理层还是应用层在拦。如果确认是代理层 ACL直接换目标这条路没有捷径。5.2 坑二Repeater 里好使Proxy 里像是没开现象手工在 Repeater 加头能过但浏览器正常访问走 Proxy 时插件像是完全没生效请求里干干净净。原因八成是作用域名配错了。这类插件通常有一个“只对匹配的 host 生效”的配置如果填的是完整 URL 或者带了端口号匹配就会失败。另一种情况是请求走了 HTTPS而 Burp 没有正确解密——插件只能改它看得见的明文请求看不见的自然改不了。解决先去Target → Scope里确认目标在范围内再检查插件的作用域名是不是只填了主机名。HTTPS 的问题看 5.3。最后一种可能是插件挂在Proxy之外的处理链上只对Repeater生效——翻一下插件的说明确认它注册的是哪个扩展点。5.3 坑三HTTPS 站点里插件像是失灵的现象HTTP 站点一切正常换成 HTTPS 之后请求头完全没变化Burp 的 HTTP history 里能看到明文但发出去的包是原始的。原因Burp 处理 HTTPS 靠的是自签证书浏览器或客户端没有信任这个证书时连接会直接失败或者被旁路插件自然没有机会介入。还有一种情况是目标启用了证书固定Certificate Pinning客户端只认特定证书Burp 的中间人证书会被拒绝。解决先确认 Burp 的 CA 证书已经装进系统的信任库并且是在“受信任的根证书颁发机构”里不是普通证书存储。移动端还要额外确认代理设置生效。遇到证书固定的客户端这条路基本走不通换用其他测试手段别在这上面耗时间。5.4 坑四IP 伪造成功了风控还是把我拦下现象响应确实从 403 变成了 200但连续发十几个请求之后账号被锁、验证码弹出、或者直接返回“操作过于频繁”。原因现代风控很少只看 IP 一个维度。设备指纹、UA、Cookie、请求间隔、行为序列、账号历史随便拎一个出来都能独立触发拦截。IP 只是最外层的一道过了这道门里面还有好几道。解决把变量拆开单独测。先固定 IP 只换 UA看会不会触发再固定 UA 只换 IP看会不会触发。找出真正起作用的那个维度再决定插件该怎么配。如果确认是多维联合判定单靠伪造 IP 收益有限得配合请求间隔随机化一起做。5.5 坑五装上插件之后 Burp 开始卡内存一路涨现象导入 jar 之后 Burp 明显变慢Proxy history 滚动卡顿任务管理器里 Java 进程内存持续上涨不回落。原因插件在每一个请求上做字符串处理如果规则里配了随机 IP 生成加上 history 默认不限制条数几千条请求累积下来内存就吃满了。另一种可能是插件本身有 bug在某个特定请求格式上死循环。解决先把Proxy → Options → History里的记录上限调小比如从默认值降到 1000 条。再检查插件配置里有没有“随机生成”这类每请求都要计算的选项测试阶段可以改成固定值。如果卡顿依旧先把插件关掉对比一次确认问题确实来自它。这类问题没什么优雅解法就是控制数据量。6. 收在验证上用三行配置查清 IP 信任链断在哪一环工具用到最后真正有价值的不是“怎么伪造”而是“怎么判断一个系统会不会被骗”。我把这套判断压缩成三个动作每次接手一个带 IP 判定的目标先做这三件事比直接开插件省一半时间。第一件事看响应头里的代理痕迹。有Via、X-Cache、X-Served-By说明链路上存在会处理转发头的中间件采信概率高一个都没有的裸服务大概率直接读remote_addr。第二件事看错误页面。故意触发一个 500很多框架会把完整请求头打印出来那一屏就是最好的情报。第三件事在靶场里跑一遍 4.2 的三层模型确认目标的判定发生在代理层还是应用层——如果是代理层 ACL立刻放弃别浪费两小时。判断依据落实到代码上其实就是一段可以抄走的检查逻辑# trust_check.py —— 判断一条请求的 IP 判定是否可被头部影响 def judge(request): remote request.remote_addr # TCP 源地址客户端改不了 xff request.headers.get(X-Forwarded-For) if xff and remote in TRUSTED_PROXIES: # 只有在可信代理由后才采信 return parse_from_right(xff) # 从右往左剥离 return remote # 其余情况一律信 TCP 源这三行里的每一行都对应一个曾经的翻车现场。第一行我见过太多人直接用request.headers.get(X-Forwarded-For)兜底等于把判定权交给了客户端第二行TRUSTED_PROXIES是硬编码的运维事实不是从请求里推导出来的一旦允许从请求里读整个信任模型就塌了第三行是从右往左方向反了就等于白写。回过头看我在伪造 IP 这件事上最大的收获不是学会了加几个头而是养成一个习惯每遇到一个“根据来源做判断”的逻辑先问一句这个来源是谁给的。从那以后我每次做这类测试都会强制走一遍“基线 — 单点 — 差异”的三步哪怕目标看起来再简单也不跳过因为跳过的那次往往就是白忙一下午的那次。希望这份 burpFakeIP 的复现笔记能帮你在自己的靶场里把这条链路完整跑通一遍。本文还有配套的精品资源点击获取