☰
Burp扩展实践:伪造X-Forwarded-For实现客户端IP可控
2026/9/29 19:13:34 网站建设 项目流程

简介:这是一份面向Web安全测试人员的Burp Suite自定义插件,名为burpFakeIP,主要解决渗透测试过程中需要模拟不同源IP地址、隐藏测试者真实身份或模拟多用户访问的问题。压缩包共12个文件,体积1.09MB,包含一个Python插件脚本、说明文档、测试文本以及9张界面与效果截图,便于快速了解插件用法和运行效果。已有412人学习下载。配套的README与截图能帮助使用者掌握在Burp Suite中加载自定义插件、配置伪造IP的基本流程,并理解其实现原理;脚本本身结构清晰,适合作为二次开发或学习Burp扩展API的参考。该插件可应用于地理限制测试、基于IP的访问控制策略验证、多用户场景模拟等合规渗透测试场景,同时资源也强调了实际使用中须遵守法律法规与授权边界。对于希望增强Burp Suite功能、开展合法安全评估的研究者和进阶学习者,这份资源兼具实用性与学习价值。

1. BurpFakeIP 是什么:一个把客户端 IP 变成可控变量的 Burp 扩展

做授权测试或前后端联调时,最让人心烦的莫过于:接口都通了,后端却咬定访问来自内网出口或 CDN,凡是按来源地址做的白名单、审计和限流规则全都对不上号。burpFakeIP-master.zip 这个压缩包,就是为解决这个问题出现的——它是一枚 Burp 扩展插件,能在请求发出前把 X-Forwarded-For、X-Real-IP、Client-IP 这些客户端地址头改写为我们指定的值,让“来源 IP”从黑匣子变成可配置的变量。

适合三类人:有授权范围的安全测试人员、在排查 IP 判断逻辑的安服工程师,以及需要模拟多来源地址的前后端开发。下文从原理、安装、参数配置到避坑,把它当作一次普通扩展工程落地来写,保证照着能做通。

2. 原理先立住:X-Forwarded-For 链路与 Burp 扩展的挂载点

2.1 客户端 IP 为什么是最不可信的依赖

所有这类扩展能起作用,前提是同一个事实:TCP 层的源 IP 很难伪造,但 HTTP 层这些地址相关头字段只是文本。只要链路里任何一段能改写请求,X-Forwarded-For 就能被改成任何内容。

正常链路下,客户端请求会经过一层或多层接入网关、负载均衡器。第一层入口往往会把原始 TCP 来源记录进去,写成X-Forwarded-For: 203.0.113.9;如果后面还有一层,就变成X-Forwarded-For: 203.0.113.9, 10.0.0.2,逗号分隔。问题在于后端到底取哪一段,完全没有统一标准——有的框架取最左边第一个值,有的取最右边最后一个值,还有的干脆把整段字符串拿去匹配。

这带来两个坑:第一,如果转发入口自己不带校验地信任现有头,那客户端传什么,后端就收到什么;第二,即使入口强制追加自己的记录,多个值同时存在时,后端取值策略不同,处理结果就完全不同。这也是为什么 burpFakeIP 这类工具的配置里几乎都有“追加还是替换”选项:不是简单加一个头就能通用,必须按目标后端的读取逻辑来对。

2.2 Burp 扩展接口:请求在哪个环节被改写

Burp 的扩展机制对这类需求支持得很直接。Java 扩展通过IBurpExtender注册,再用IHttpListener监听每一笔经过 Burp 的 HTTP 请求。介入发生时,processHttpMessage被调用,扩展在此时拿到原始请求对象,改完头部后再写回去。常见做法的核心代码,大致是下面这种骨架:

package burp; import java.util.List; public class BurpExtender implements IBurpExtender, IHttpListener { private IExtensionHelpers helpers; @Override public void registerExtenderCallbacks(ICallbacks callbacks) { this.helpers = callbacks.getHelpers(); callbacks.setExtensionName("burpFakeIP"); callbacks.registerHttpListener(this); } @Override public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) { if (!messageIsRequest) { return; } IHttpRequest request = messageInfo.getRequest(); List<String> headers = helpers.analyzeRequest(request).getHeaders(); // 核心动作:在原始请求头里追加伪造的客户端地址头 headers.add("X-Forwarded-For: 203.0.113.8"); headers.add("X-Real-IP: 203.0.113.8"); byte[] body = helpers.analyzeRequest(request).getBody(); messageInfo.setRequest(helpers.buildHttpMessage(headers, body)); } }

这段示意代码和仓库里的完整实现不一定完全一致,但接口骨架是共通的。它做了三件事:拿到头部列表、追加指定头、用构造后的新请求覆盖原请求。注意这里只是“追加”,如果原请求本身已带有 X-Forwarded-For,就需要先删除再插入,否则后端可能读到旧值。

从这个骨架还能看出一件事:processHttpMessage会覆盖 Repeater、Intruder、Scanner 等各个工具面板发出的请求,所以扩展一旦挂上,就不需要逐个工具去配置。真正需要区分的是值来源——一个固定值对所有请求生效,还要“随请求动态生成”,那逻辑就要落在每次processHttpMessage调用里,而不是只初始化一次。这个细节是后面排查批量请求翻车的关键。

3. 把 master.zip 跑起来:解压、构建、加载到 Burp 的完整流程

3.1 先看包内结构:它带的是 jar 还是纯源码

拿到 burpFakeIP-master.zip 以后,别急着往 Burp 拖。先用三条命令看清包内结构:

unzip burpFakeIP-master.zip -d burpFakeIP ls -la burpFakeIP find burpFakeIP -maxdepth 3 -type f | head -30

第一条命令把压缩包解压到当前目录下的 burpFakeIP 文件夹;第二条看顶层内容,一般在 master 分支仓库里会看到src、README、pom.xml或build.gradle这类文件;第三条按深度列出全部文件前 30 个,重点确认有没有编译好的 jar 包,注意 jar 可能直接放在目录根下,也可能放在target、lib、dist子目录里。

如果 find 结果里已经出现burpFakeIP.jar,那可以跳过构建直接看 3.2;如果只看到src和构建脚本,那就需要先构建。常见做法是在解压目录执行 Maven 构建:

cd burpFakeIP mvn clean package -DskipTests ls -la target/*.jar

参数-DskipTests意思是跳过单元测试,只做编译打包。构建完成后用ls确认 jar 是否产出。如果机器上没有装 Maven 也不要紧,包内带了gradlew就用./gradlew build,或者直接按 README 里作者写的命令来。这是最值得先看的一步,毕竟各家打包方式五花八门。

提示:如果压缩包本身就只含源码没有 jar,常见原因是作者预期你用 IDE 打开后手动编译。不要为了省事跳过构建,否则第 5 章场景四的报错会找上门。

3.2 在 Burp Extensions 面板加载并验证

jar 在手,加载流程就固定了。打开 Burp,在顶层菜单栏进入 Extensions 面板,按这四步操作:

  1. 在 Extensions 页签里点 Add,弹出加载对话框;
  2. Extension Type 选 Java,不要选 Python 或 Ruby;
  3. 在 Extension Details 区域选择刚才的 jar 文件;
  4. 点 Next,观察下方 Output 与 Errors 信息。

加载成功后,Extensions 列表里会出现一行状态为 loaded 的记录,同时 Output 区域通常会打印一段初始化日志。如果这个扩展实现了自己的 UI,Burp 最上方会多出一个标签页;有些扩展不展示界面,只在日志输出里写一行配置信息,这属于正常现象。

验证加载是否真正生效,最直接的办法是往 Repeater 发一个原始请求,先看返回结果,再打开扩展开关重发一次,对比头部是否变化。我习惯把这当作“加载成功”的判据,比依赖界面更可靠。日志里的 loaded 只能表示类被加载了,不能代表监听接口真的挂上;常见的 NoClassDefFoundError,也往往是在这个步骤才彻底暴露。

4. 配置与实战:三个必调参数和一次伪造 IP 的完整请求

4.1 参数一:注入的头列表与伪造值来源

装上后第一件事是配置三个参数。第一个是“注入给哪些头”,默认常见值有三个:X-Forwarded-For、X-Real-IP、Client-IP。实际测试时不要三个全开,先判断目标到底读哪个头。

判断方法很粗但有效:先在本地验证桩上看被接收的头名(第 6 章会给出验证桩),或者看目标业务日志里记录的是哪个字段。例如一台单独用 Nginx 静态托管的站点,日志通常只认 X-Forwarded-For;某些 Java 框架内置的客户端地址解析器号称自动识别,实际多数看的还是 X-Forwarded-For 的约定。

第二个参数是伪造值来源,常见四类:

  • 固定值:所有请求都同一个 IP,只适合验证单条白名单;
  • 随机值:每次请求或每个批次随机生成,适合观察行为差异;
  • 文件轮询:从列表文件里逐个取,适合模拟一组来源;
  • 自增偏移:从一个种子 IP 按计数递增,适合批量造数据。

包里如果自带配置文件,常见格式是一份 JSON,内容大致长这样:

{ "headers": ["X-Forwarded-For", "X-Real-IP"], "ipMode": "random", "randomCidr": "203.0.113.0/24", "replace": true }

headers指定要注入的头部;ipMode指定来源模式;randomCidr限定随机生成的范围前缀,避免满网段乱跳;replace决定是替换原有头还是追加。注意不同版本字段名会有出入,以包内 README 里的配置项为准;包没有界面时,就是用这种方式在启动时读取的。

最重要的建议是replace一开始就设为 true。否则原请求如果已带有 X-Forwarded-For,后端解析时可能把新旧两个值混在一起,造成“伪造失败”的假象。

4.2 参数二:生效范围与目标过滤

第二个必调参数是“作用范围”。一个挂在 Burp 上的监听器,理论上会处理所有走 Burp 的请求——包括你去查公开文档、顺手打开别的网站时带出的流量。如果扩展没有自己的白名单机制,非目标域很容易也带上伪造头,这在授权测试里是事故。

避免这一点的常见方案有三种:第一种是扩展自带的 scope 字段,配合域名或通配符;第二种是依赖 Burp 自带的 Target Scope;第三种是直接在代码里硬编码域名名单。无论哪种,默认建议手动把目标域名和测试内网网段加进去,再打开开关。

# 先看目标在携带伪造头时返回什么,再到 Burp 里把范围过滤收窄 curl -s http://test.example.cn/api/ip -H 'X-Forwarded-For: 203.0.113.8'

这条命令的作用是让目标返回它解析到的来源地址;确认目标接口本身通了,再回到 Burp 配置里把.example.cn加入白名单,这样只有目标域的流量会被改写。

提示:如果包里找不到 scope 配置项,就改到 Burp 的 Target → Scope,把测试域名放入 Include 列表。这是最不容易翻车的后备方案。

4.3 实战:用 Repeater 与 Intruder 完成一次完整验证

配置好上面两项后,在 Repeater 里做一次最小验证。把请求发到目标接口,头部看到X-Forwarded-For: 203.0.113.8,点发送后,从响应或后端日志确认后端接收到的确实是这个值。

要模拟连续多个来源时,用 Intruder 更直观。把 payload 位置放在源 IP 字段,传入下面这种命令生成的清单:

seq 1 20 | awk '{printf "203.0.113.%d\n", $1}'

生成从 203.0.113.1 到 203.0.113.20 的 20 个地址。Intruder 选 Sniper,把清单里的每项付给 X-Forwarded-For,一次跑完。这验证的不只是头部有没有注入字段,而是后端限流或白名单会不会随这些值发生变化。

整个实战只有三步:观察单体请求 → 批量请求 → 对比后端日志。三步都通,扩展配置才算可靠。最容易漏的是第二步,很多人只验证了一个请求就结束,等到次日批量任务出错再回来查,时间就浪费了。

5. FakeIP 常见问题排查:四个翻车场景与解决记录

5.1 场景一:头已经填了,后端日志仍然是接入层地址

现象:Repeater 里能看到 X-Forwarded-For 带上了伪造值,但后端 access.log 记录的来源 IP 没变。

原因分三类:一是目标系统的接入层强制重写该头,比如自建网关配置了头改写逻辑;二是后端框架出于安全考虑,只对可信来源段内的连接信任 XFF;三是扩展在请求链路上的位置太靠后,请求还没到应用层就被前置鉴权挡掉了。

解决:先看请求是否真正到达应用层。方法是临时在本地验证桩上保持同一个目标 Host,再把 Burp 的请求指到本地验证桩,确认头部能自己发出去;随后去目标网关日志看它有没有覆盖。如果是强制覆盖,伪造点就要从请求头改成“绕过该网关的测试环境直连后端”,这一步不在扩展配置里,而在于测试环境的可访问性。

5.2 场景二:测试目标没翻车,别的域名却带着伪造头出去了

现象:扩展开着时,访问一个无关域名也出现伪造的 XFF,后端可能因此记录错误甚至拒绝。

原因:作用范围没有收紧,监听器对经过 Burp 的所有请求一视同仁。常见误操作是把 scope 关掉、图省事用通配符.*,结果把非目标流量也污染了。

解决:把范围过滤配置成白名单模式,只放行目标域名;如果扩展自身没有白名单设定,就用 Burp 自带 Scope。我在实际项目里的血泪经验是:开 fakeip 之前先保存一份“关闭状态”的配置文件,排查可疑流量时能一键恢复原样,不靠记忆去猜改过哪里。

5.3 场景三:Intruder 批量跑的时候,伪造 IP 始终不变

现象:单条请求正常,但 Intruder 跑了 100 条,后端只看到同一个来源 IP。

原因:值来源被设置为“固定值”,或生成器在扩展加载时只初始化一次。很多伪造扩展的随机 IP 是注册扩展时生成一个副本,之后每个请求都复用,看起来像随机,实际是静态。

解决:检查并切换为“每次请求生成”模式,配置项通常叫 random-per-request、per-request 或 dynamic。如果包内无此模式,只能改代码——在processHttpMessage里每次都调用新的随机生成函数;注意不能放在构造函数或registerExtenderCallbacks里,那是扩展加载时只跑一次的地方。

5.4 场景四:加载 jar 直接报 NoClassDefFoundError

现象:Extensions 面板点完 Next,Output 区域显示 NoClassDefFoundError 或 ClassNotFoundException,列表状态是 error。

原因:jar 编译时用的 JDK 版本高于 Burp 自带 JVM,或者扩展引用了未打包进 jar 的第三方类库。后者常见于 README 写着“把 xxx.jar 也放进同目录”,但用户只加载了主 jar。

解决:先看报错信息里缺的类名,缺第三方库就把它加进 jar,或借助 Burp 的 Libraries 字段一并加载;版本冲突则用低版本 JDK(一般用 8 或 11)重新编译。也有少数情况是 Burp 版本太老,接口对不上,这种直接换新版反而最快。这里没有玄学,纯是工程链版本匹配问题。

6. 进阶验证:两分钟搭一个本地桩,看清请求头真实变化

6.1 验证桩代码:让头变成可见输出

与其拿真实目标反复试错,我更建议先在本地起一个打印请求头的小服务,把 Burp 的 Repeater 目标指到127.0.0.1:8080,就能立刻确认 fakeip 到底改了哪些字段。一段最短实现:

from http.server import BaseHTTPRequestHandler, HTTPServer class Handler(BaseHTTPRequestHandler): def do_GET(self): body = str(self.headers).encode("utf-8") self.send_response(200) self.send_header("Content-Type", "text/plain; charset=utf-8") self.end_headers() self.wfile.write(body) def log_message(self, fmt, *args): pass HTTPServer(("127.0.0.1", 8080), Handler).serve_forever()

这个脚本把收到的完整请求头原样返回。运行后,在 Repeater 里把请求行改成http://127.0.0.1:8080/,发送请求,响应体里哪几行带了伪造头一目了然。它还能用来调试 4.1 里的 replace 参数是不是真的替换原有头部,不需要开分析工具去猜。

6.2 把验证桩接入批量流程:从本地点到在线

本地桩只验证“扩展有没有改头”,在线目标才能验证“后端认不认”。所以我的习惯是把验证桩和批量测试串到一起:先用 curl 批量打本地桩,确认每组值都符合预期,再切回真实测试域名重放一遍。

for i in $(seq 1 5); do curl -s http://127.0.0.1:8080/ -H "X-Forwarded-For: 203.0.113.$i" echo done

这个循环会把每次伪造值对应的请求头发回显出来。注意真实目标测试时不要直接在命令行里堆大量不同来源 IP,而是交给 Intruder 并固定并发数,避免对目标造成无谓压力。一步一步来,先本地后线上,是我这些年在测试里最稳的顺序;把验证桩代码存成一个常用脚本,新项目直接掏出来用,能省掉大多数“改了半天不知改没改对”的返工。希望这个流程能帮你少走一点弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询