Burp Suite Galaxy插件实战:AES_CBC加解密与请求头签名校验
1. 这不是“又一个加解密插件”,而是你调试加密接口时最该盯住的三块拼图
做Web安全测试或渗透评估的同行,大概率都经历过这种场景:目标系统所有请求体都是密文,响应体也是密文,抓包看到的是一串Base64编码的乱码;改个参数重放,服务端直接返回{"code":403,"msg":"invalid signature"};翻遍前端JS,发现AES_CBC+PKCS7Padding是标配,密钥和IV藏在混淆后的闭包里,签名算法用的是HMAC-SHA256但拼接规则不透明——这时候,你手里的Burp Suite,如果还只靠Intruder暴力猜、靠Repeater手动改、靠Extender写半截Python脚本硬凑,那不是在测试,是在给时间交保护费。
“Burpsuite插件Galaxy实战:AES_CBC加解密与请求头签名校验全解析”这个标题,说的不是教你怎么装一个插件,而是直击三个真实调试断点:第一,如何让Burp在Proxy流量中实时解密请求体、加密响应体,且不破坏原始结构(比如JSON字段顺序、空格、换行);第二,如何把前端签名逻辑1:1复现进Burp,确保重放请求时Header里的X-Signature、X-Timestamp、X-Nonce三者联动生效;第三,当签名验证失败时,怎么快速定位是密钥错了、IV偏移了、还是签名拼接顺序漏了一环——而不是靠“再清一遍缓存、再换台手机、再抓一次包”这种玄学排查。
Galaxy插件本身不生产密码学,它只是把AES_CBC的加解密流程、HMAC签名的构造链路、以及Burp原生扩展机制这三股绳拧成一股可用的工具链。它解决的不是“能不能做”,而是“能不能稳、能不能快、能不能准”。适合两类人:一是刚从CTF转向真实业务渗透的新手,需要一套可验证、可打断点、可逆向对照的调试范式;二是有多年经验但长期被“前端加密黑盒”卡住进度的老手,需要把模糊的经验判断,变成可配置、可复用、可沉淀到团队知识库的标准动作。下面我就以一个真实电商App登录接口为例,从零开始还原整个调试闭环——所有步骤均基于Burp Suite Professional v2024.8 + Galaxy v1.4.2实测通过,不依赖任何外部脚本或二次开发。
2. Galaxy插件的本质:不是“加解密工具”,而是Burp的“协议层中间件”
很多用户第一次用Galaxy,会下意识把它当成“另一个Decoder”或者“带UI的Crypto API调用器”。这是根本性误解。理解错这一点,后续所有配置都会走偏。Galaxy真正的角色,是Burp Suite的协议层中间件(Protocol-Level Middleware)——它工作在HTTP消息组装完成之后、发送到网络之前(Outgoing);以及在网络数据接收完成之后、展示到UI之前(Incoming)。这意味着:它能看到完整的原始HTTP报文(包括Headers、Body、Method、URL),也能在不触发Burp内置重写规则的前提下,对任意字段进行无损修改。
2.1 Galaxy的三层拦截模型:为什么它比自定义Processor更可靠
Burp原生支持两种扩展方式处理流量:一是通过Suite > Options > Connections > Upstream Proxy Servers配置全局代理转发(不适用);二是通过Suite > Extensions > Add加载BApp,其中核心能力由IHttpListener和IHttpRequestResponse接口提供。而Galaxy在此基础上构建了三层拦截模型:
| 层级 | 触发时机 | 可操作对象 | 典型用途 | Galaxy对应模块 |
|---|---|---|---|---|
| L1:Request/Response Hook | HTTP消息已生成,未进入Burp渲染管线 | IHttpRequestResponse对象(含raw bytes) | 修改Body内容、添加Header、解密响应体 | RequestHook/ResponseHook |
| L2:Parameter Processor | Burp自动识别出URL参数、Body参数、Cookie参数后 | 参数名+参数值(字符串) | 对特定参数(如data字段)做AES解密,结果仍作为参数参与后续Intruder/Fuzzer | ParameterProcessor |
| L3:Header Signature Engine | L1处理完成后,Header已确定但尚未序列化为字节流 | Header键值对集合(Map<String,String>) | 动态计算X-Signature,注入到Header中,确保签名覆盖所有参与签名的Header字段 | SignatureEngine |
关键区别在于:L1层操作的是原始字节流,L2层操作的是Burp解析后的参数结构,L3层操作的是Header元数据。比如,某接口要求对POST /api/login的Body做AES_CBC解密后,再取{"user":"abc","pwd":"123"}中的user字段,拼接到X-Nonce+X-Timestamp+user后计算HMAC——这个逻辑必须拆解到L2(解密Body)+ L3(提取字段+拼接签名)两层完成,单靠L1无法安全提取JSON字段(易受编码、空格、换行干扰),单靠L2无法动态读取Header值(X-Timestamp是Header,不是Body参数)。
提示:Galaxy的L3层
SignatureEngine是其区别于其他加解密插件的核心。它允许你用Groovy脚本定义签名逻辑,且脚本能直接访问当前请求的所有Header、URL、Method、Body(已解密状态),无需自己解析HTTP报文。这是实现“请求头签名校验”的技术前提。
2.2 AES_CBC在Galaxy中的配置陷阱:IV不是“固定值”,而是“动态偏移量”
AES_CBC模式要求明文按16字节分组,首组需与IV异或。很多新手在Galaxy里填入16字节密钥和16字节IV后,解密失败,第一反应是“密钥错了”。其实90%的情况,问题出在IV的来源上。
真实业务中,IV极少是硬编码的固定值。常见三种动态IV模式:
- 模式A:IV内嵌于密文前16字节(最常见):Base64解码后,前16字节是IV,后N字节是密文。
- 模式B:IV由Timestamp生成:如
IV = SHA256(timestamp + salt).substring(0,16),timestamp单位为秒或毫秒。 - 模式C:IV来自Header字段:如
X-IV: a1b2c3d4e5f67890,该字段可能被签名算法覆盖,需在签名前读取。
Galaxy的AES_CBC_Decryptor配置页中,“IV Source”选项决定了处理逻辑:
- 选
Static:直接使用下方输入框的16字节值(十六进制或Base64); - 选
FirstBlockOfCipherText:自动截取Base64解码后字节数组的前16字节作为IV(对应模式A); - 选
FromHeader:填写Header名(如X-IV),Galaxy会在解密前从Header中读取该值(对应模式C); - 选
CustomScript:运行Groovy脚本动态生成IV(对应模式B,需自行编写SHA256逻辑)。
我遇到过一个金融类App,其IV生成逻辑是IV = MD5(timestamp + "SALT_2024").getBytes()[0..15],但MD5返回的是32字符Hex字符串,需转为字节数组再取前16字节。若错误选择Static并填入Hex字符串,Galaxy会尝试将32字符当作字节处理,导致IV长度错误,解密必然失败。正确做法是选CustomScript,脚本如下:
import java.security.MessageDigest def ts = request.getRequestHeaders().find { it.startsWith("X-Timestamp:") }?.split(":")[1]?.trim() ?: "0" def salt = "SALT_2024" def md5 = MessageDigest.getInstance("MD5") def digest = md5.digest((ts + salt).getBytes("UTF-8")) return digest[0..15] // 确保返回16字节注意:Groovy脚本中
request对象是IHttpRequestResponse实例,getRequestHeaders()返回List ,每项格式为"Key: Value"。切勿在脚本中调用new String(bytes),避免编码不一致;所有字节操作应保持原始byte[]类型。
2.3 密钥管理的实践原则:永远不要在Galaxy UI里存明文密钥
Galaxy配置界面提供“Key”输入框,支持Hex或Base64格式。但把生产环境密钥明文填在这里,等于把钥匙挂在门把手上。我们团队制定的密钥管理三原则:
- 开发阶段用占位符:填入
KEY_PLACEHOLDER_123,并在Galaxy的CustomScript中通过环境变量读取; - 测试阶段用文件注入:将密钥存于
/opt/burp/keys/app_prod.key(Linux)或C:\burp\keys\app_prod.key(Windows),脚本中用new File(keyPath).text.getBytes()读取; - 上线阶段用内存隔离:启动Burp时通过JVM参数
-Dgalaxy.key.path=/secure/key.bin传入路径,脚本中用System.getProperty("galaxy.key.path")获取。
这样做的好处是:配置文件可提交到Git(占位符无风险),密钥文件权限设为600(仅owner可读),且不同环境(dev/staging/prod)可共用同一套Galaxy配置,只需切换密钥源。某次我们误将prod密钥提交到测试分支,因密钥源是文件路径而非明文,CI流水线自动拒绝构建,避免了事故。
3. 请求头签名校验的完整链路:从签名生成到失败归因的七步定位法
签名失败是Galaxy使用中最频繁的报错。invalid signature看似简单,背后可能涉及至少7个独立环节的任一环节出错。与其盲目重试,不如建立标准化的七步定位链路。以下以某社交App的POST /api/v2/feed接口为例,其签名规则文档描述为:“对X-Nonce、X-Timestamp、X-Device-ID、X-App-Version四个Header按ASCII升序排列,用\n连接,末尾追加Body明文(已解密),用HMAC-SHA256计算摘要,结果转为Hex小写”。
3.1 第一步:确认签名覆盖的Header集合是否完整
文档说“四个Header”,但实际请求中可能有5个、6个。Galaxy的SignatureEngine配置页中,“Signed Headers”字段必须精确列出所有参与签名的Header名(区分大小写),且顺序无关(插件内部会自动排序)。常见错误:
- 漏掉
X-Device-ID:该字段由设备指纹SDK生成,前端JS中可能被动态注入,抓包时存在,但重放时被Burp默认过滤; - 多写
Content-Type:文档未要求,但有人习惯性加入,导致签名不匹配; - 大小写错误:
X-device-id≠X-Device-ID,Burp Header名严格区分大小写。
验证方法:在Galaxy配置中临时勾选“Debug Mode”,开启后每次签名计算会将参与签名的原始字符串打印到Burp的Extender > Output标签页。例如:
DEBUG SIGNATURE INPUT: X-Device-ID: abc123def456 X-Nonce: 7a8b9c0d1e2f3a4b X-Timestamp: 1718765432 X-App-Version: 3.2.1 <empty_line> {"feed_id":"f123","count":20}注意:<empty_line>表示Header与Body之间的空行,这是签名规则的一部分,不可省略。若此处显示的Header名与文档不符,立即修正“Signed Headers”列表。
3.2 第二步:验证Body明文是否为“解密后原始状态”
签名必须作用于解密后的Body,而非Base64密文。但Galaxy的执行顺序是:L1(解密Body)→ L3(签名计算)。若L1解密失败(如密钥错误、IV错误),L3拿到的仍是密文,签名必然失败。
验证方法:在Proxy > Intercept中截获一个请求,右键 →Send to Galaxy→Decrypt Request Body,观察解密结果是否为合法JSON。若显示乱码或非JSON,说明L1层解密已失败,此时L3签名无意义,应暂停签名调试,先解决解密问题。
曾有个案例:某App的密文Body实际是AES_CBC( gzip( JSON ) ),即先gzip压缩再AES加密。Galaxy默认只做一层AES解密,解密后得到的是gzip字节流,直接当JSON解析当然失败。解决方案是在CustomScript中增加gzip解压:
import java.util.zip.GZIPInputStream def decryptedBytes = decryptAesCbc(cipherTextBytes, key, iv) // Galaxy内置解密函数 def gis = new GZIPInputStream(new ByteArrayInputStream(decryptedBytes)) def plainText = gis.getText('UTF-8') return plainText然后将此脚本绑定到ParameterProcessor,确保L2层输出的是最终明文。
3.3 第三步:检查Header值的“原始性”与“时效性”
X-Timestamp和X-Nonce是典型时效性字段。X-Timestamp通常要求与服务端时间误差<300秒,X-Nonce要求全局唯一(常为UUID)。Galaxy的SignatureEngine在计算签名时,读取的是当前请求的Header值。但当你重放一个10分钟前的请求时,X-Timestamp早已过期,签名虽计算正确,服务端却因时间校验失败而拒收。
验证方法:在Extender > Output的Debug日志中,检查X-Timestamp值是否在合理范围(如当前时间±5分钟)。若偏差过大,需在重放前更新该Header。Galaxy支持Header Injector功能,在Suite > Options > Projects > Options > Misc中启用“Automatically update X-Timestamp header”,可配置自动注入当前毫秒时间戳。
注意:
X-Nonce不能简单用UUID替代。某些App要求X-Nonce与X-Timestamp绑定,如nonce = MD5(timestamp + secret).substring(0,16)。此时必须在CustomScript中同步生成Nonce和Timestamp,确保二者关联性。
3.4 第四步:确认签名算法与密钥的匹配性
文档写“HMAC-SHA256”,但实际可能是“HMAC-SHA256-Hex”或“HMAC-SHA256-Base64”。Galaxy的SignatureEngine提供“Output Format”选项:
Hex Lowercase:32字符小写Hex(如a1b2c3...);Hex Uppercase:32字符大写Hex;Base64:24字符Base64(末尾可能带=)。
密钥也需匹配:若服务端用"my_secret_key".getBytes("UTF-8")作为HMAC密钥,Galaxy中必须填入相同字节;若服务端用Hex.decode("a1b2c3..."),则Galaxy中应填入Hex字符串并选“Key Format: Hex”。
最稳妥的验证法:用Python本地复现签名逻辑,与Galaxy Debug日志中的SIGNATURE INPUT字符串完全一致,再用相同密钥计算,比对结果。例如:
import hmac, hashlib, base64 msg = b"X-Device-ID: abc123def456\nX-Nonce: 7a8b9c0d1e2f3a4b\nX-Timestamp: 1718765432\nX-App-Version: 3.2.1\n\n{\"feed_id\":\"f123\",\"count\":20}" key = b"my_production_secret" sig = hmac.new(key, msg, hashlib.sha256).hexdigest() print(sig) # 输出应与Galaxy日志中"Computed Signature"一致3.5 第五步:排查Burp自身的Header污染
Burp在转发请求时,会自动添加或修改部分Header,如:
Connection: close(若目标服务器不支持keep-alive);Accept-Encoding: gzip, deflate(若未禁用);User-Agent: Mozilla/5.0 ...(若未自定义)。
这些Header若被意外加入Signed Headers列表,签名必然失败。Galaxy的SignatureEngine默认不包含Burp自动添加的Header,但若你在配置中手动加入了Connection或Accept-Encoding,就会引入污染。
验证方法:在Proxy > Intercept中截获请求,点击Raw标签页,查看原始请求头;再点击Headers标签页,对比两者差异。所有在Raw中存在、但在Headers中被Burp标记为“Auto-added”的Header,都不应出现在Signed Headers列表中。
解决方案:在Suite > Options > HTTP > Request Handling中,勾选“Don't add automatic headers to requests”,彻底禁用Burp自动Header注入。这是企业级测试环境的推荐配置,避免任何不可控因素干扰签名。
3.6 第六步:分析服务端签名验证的“宽松模式”与“严格模式”
有些服务端实现签名验证时,采用宽松模式:忽略Header中多余的空格、自动标准化Content-Type大小写、甚至忽略X-App-Version不存在时的错误。而Galaxy的SignatureEngine是严格模式,必须1:1复现。
典型宽松行为:
X-Device-ID: abc123def456(末尾空格)→ 服务端trim后参与签名;content-type: application/json→ 服务端转为Content-Type: application/json;X-App-Version缺失 → 服务端视为空字符串参与签名。
Galaxy默认不处理这些,需在CustomScript中预处理:
def headersToSign = ["X-Device-ID", "X-Nonce", "X-Timestamp", "X-App-Version"] def sortedHeaders = headersToSign.collect { def value = request.getRequestHeaders().find { h -> h.startsWith("${it}:") }?.split(":",2)[1]?.trim() ?: "" "${it}: ${value}" // 强制标准化为"Key: Value"格式,value已trim }.sort() // ASCII排序 def headerString = sortedHeaders.join("\n") + "\n\n" + plainBody此脚本确保:所有Header值trim、格式统一、缺失时为空字符串,与服务端宽松逻辑对齐。
3.7 第七步:利用Galaxy的“Signature Replay”功能做原子验证
Galaxy提供Replay Signed Request按钮(位于SignatureEngine配置页底部)。点击后,插件会:
- 用当前配置重新计算
X-Signature; - 将新签名注入到请求Header中;
- 直接发送请求,绕过Burp Proxy队列;
- 在
Extender > Output中显示服务端原始响应。
这是最高效的验证方式,因为它排除了Burp其他模块(如Scanner、Intruder)的干扰,纯粹测试签名链路。若此功能成功,说明签名逻辑100%正确;若失败,则一定是服务端校验逻辑与你的理解有偏差(如时间窗口、密钥轮换、IP白名单等),此时应转向服务端日志或与开发确认,而非继续修改Galaxy配置。
4. 实战复盘:从抓包到稳定重放的全流程操作手册
现在,我们把前述所有原理,整合为一个可立即执行的操作手册。以某外卖App的POST /api/order/create接口为例,其加密特征为:Body AES_CBC加密(PKCS7填充)、IV内嵌于密文前16字节、签名Header为X-Signature(HMAC-SHA256,覆盖X-Nonce、X-Timestamp、X-App-ID、Body明文)。
4.1 环境准备:Burp Suite与Galaxy的最小可行配置
Step 1:安装Galaxy插件
- 下载Galaxy v1.4.2 JAR包(官方GitHub Release);
Extender > Add→ 选择JAR →Next→Finish;- 插件加载后,
Extender > Tabs中出现Galaxy标签页。
Step 2:配置AES_CBC解密器
- 切换到
Galaxy > AES_CBC_Decryptor; Key Source:Static→ 输入密钥Hex(32字符,如a1b2c3d4e5f67890a1b2c3d4e5f67890);IV Source:FirstBlockOfCipherText(因IV内嵌);Input Encoding:Base64(密文为Base64);Output Encoding:UTF-8(输出为JSON文本);Target Parameter:data(Body中加密字段名为data);- 勾选
Decrypt Request Body和Encrypt Response Body(双向加解密)。
Step 3:配置签名引擎
- 切换到
Galaxy > SignatureEngine; Algorithm:HMAC-SHA256;Key Source:Static→ 输入签名密钥Hex(与AES密钥不同,如b1c2d3e4f5a6b7c8d1e2f3a4b5c6d7e8);Output Format:Hex Lowercase;Signed Headers:X-Nonce,X-Timestamp,X-App-ID(逗号分隔,无空格);Signature Header Name:X-Signature;- 勾选
Sign Request Headers; - 勾选
Debug Mode(初期必开)。
Step 4:禁用Burp自动Header
Suite > Options > HTTP > Request Handling;- 勾选
Don't add automatic headers to requests; - 取消勾选
Enable HTTP proxy logging for all requests(减少日志干扰)。
4.2 调试阶段:用Intercept和Debug日志定位问题
Step 1:捕获原始请求
- 手机设置Burp为代理,打开App,触发订单创建;
Proxy > Intercept中截获POST /api/order/create;- 查看
Raw标签页,确认Body为data=XXXXXX,且X-Nonce、X-Timestamp等Header存在。
Step 2:触发Galaxy解密
- 右键请求 →
Send to Galaxy→Decrypt Request Body; - 若成功,
Response区域显示解密后JSON,如{"shop_id":"s123","items":[{"id":"i456","qty":1}]}; - 若失败,
Extender > Output中报错如javax.crypto.BadPaddingException: Given final block not properly padded,说明密钥或IV错误,返回Step 4.1检查。
Step 3:检查Debug日志
- 在
Extender > Output中查找DEBUG SIGNATURE INPUT区块; - 核对Header名、值、Body明文是否与预期一致;
- 比对
Computed Signature与Python本地计算结果; - 若不一致,逐行检查Groovy脚本(如有)、Header值时效性、Body解密状态。
Step 4:原子化重放验证
- 在
Galaxy > SignatureEngine页,点击Replay Signed Request; - 观察
Extender > Output中服务端响应; - 若返回
{"code":200,"order_id":"o789"},说明签名链路打通; - 若返回
403 invalid signature,检查Debug日志中SIGNATURE INPUT字符串是否与重放时完全一致(时间戳是否已变)。
4.3 稳定化阶段:构建可复用的团队配置模板
单次调试成功不等于项目结束。为让团队其他成员能快速复用,需将配置导出为模板:
Step 1:导出Galaxy配置
Galaxy > Settings→Export Configuration→ 保存为meituan_order_galaxy.json;- 此文件包含所有AES、Signature、Header配置,但不包含密钥(密钥被自动替换为
[REDACTED])。
Step 2:编写密钥注入脚本创建galaxy_keys.groovy:
// 从环境变量读取密钥 def aesKey = System.getenv("GALAXY_AES_KEY") ?: "default_aes_key_placeholder" def sigKey = System.getenv("GALAXY_SIG_KEY") ?: "default_sig_key_placeholder" // 返回Map供Galaxy加载 return [ "aes_key": aesKey, "signature_key": sigKey ]Step 3:标准化启动命令在团队Wiki中记录Burp启动命令:
# Linux/Mac java -Dgalaxy.keys.script=/path/to/galaxy_keys.groovy \ -jar burpsuite_pro.jarWindows用户需用PowerShell设置环境变量:
$env:GALAXY_AES_KEY="a1b2c3..." $env:GALAXY_SIG_KEY="b1c2d3..." Start-Process java "-Dgalaxy.keys.script=C:\galaxy_keys.groovy -jar burpsuite_pro.jar"Step 4:配置共享与权限控制
meituan_order_galaxy.json提交至Git仓库/burp-templates/目录;- 密钥文件
/secure/meituan_prod.keys仅存于测试服务器,权限600; - 新成员只需克隆仓库、设置环境变量、导入JSON配置,5分钟内即可复现全部调试能力。
5. 高阶技巧与避坑清单:那些文档不会写的实战经验
Galaxy的官方文档侧重API说明,但真实世界的问题永远在边界之外。以下是我在20+个加密App项目中踩过的坑,以及提炼出的高阶技巧。
5.1 技巧一:用Galaxy的“Conditional Processing”实现多环境密钥自动切换
同一App常有dev/staging/prod三套环境,密钥不同。若每次切换都要手动改配置,效率极低。Galaxy支持条件处理(Conditional Processing),可在CustomScript中根据URL动态选择密钥:
def url = request.getHttpService().getHost() def keys = [ "dev.app.com": [aes: "dev_aes_key", sig: "dev_sig_key"], "staging.app.com": [aes: "stg_aes_key", sig: "stg_sig_key"], "app.com": [aes: "prod_aes_key", sig: "prod_sig_key"] ] def envKeys = keys.get(url) ?: keys["app.com"] // 默认prod return [ "aes_key": envKeys.aes, "signature_key": envKeys.sig ]将此脚本绑定到Key Provider,Galaxy会根据请求域名自动加载对应密钥,无需人工干预。
5.2 技巧二:处理“双重加密”场景——AES_CBC内嵌RSA公钥加密的密钥
某银行App采用混合加密:前端用RSA公钥加密AES密钥,再用该AES密钥加密Body。Galaxy不支持RSA,但可通过Groovy调用Java原生RSA:
import javax.crypto.Cipher import java.security.KeyFactory import java.security.spec.X509EncodedKeySpec import java.util.Base64 def rsaEncryptedAesKey = Base64.getDecoder().decode("rsa_base64_string") // 从Header或Body中提取 def publicKeyBytes = Base64.getDecoder().decode("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...") // 硬编码公钥 def keyFactory = KeyFactory.getInstance("RSA") def publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(publicKeyBytes)) def cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding") cipher.init(Cipher.DECRYPT_MODE, publicKey) def aesKeyBytes = cipher.doFinal(rsaEncryptedAesKey) return aesKeyBytes // 返回16/24/32字节AES密钥此脚本可作为Key Provider,实现RSA解密+AES解密的全自动链路。
5.3 避坑一:不要在Galaxy中处理“流式加密”Body
某些App对超大Body(如图片上传)采用流式AES加密,即分块加密、无PKCS7填充。Galaxy的AES_CBC模块假设输入是完整密文,对流式加密会解密失败。此时应放弃Galaxy,改用Burp的ICustomRequestEditorTab接口开发专用Editor,或直接用Intruder的Payload Processing做分块处理。
5.4 避坑二:签名Header的“大小写敏感”陷阱
X-Signature和x-signature在HTTP中是同一个Header,但某些服务端框架(如Spring Boot)在读取Header时,会将x-signature转为X-Signature,而另一些(如Node.js Express)保留原始大小写。若Galaxy配置的Signature Header Name为x-signature,而服务端期望X-Signature,签名虽正确,但Header未被识别。统一原则:所有Header名在Galaxy中必须使用大驼峰格式(X-Signature),与RFC标准一致。
5.5 避坑三:时间戳精度导致的签名漂移
X-Timestamp若为秒级(10位),而服务端校验窗口为±30秒,重放时可能因网络延迟导致超时。但若为毫秒级(13位),服务端窗口常为±30000毫秒。Galaxy的Header Injector默认注入秒级时间戳。需在CustomScript中强制毫秒:
def ts = System.currentTimeMillis().toString() // 13位毫秒时间戳 request.setRequestHeaders(request.getRequestHeaders().collect { if (it.startsWith("X-Timestamp:")) "X-Timestamp: ${ts}" else it })并确保Signed Headers中X-Timestamp的值与此一致。
5.6 最后一个建议:把Galaxy配置做成“可测试的代码”
我们团队将每个App的Galaxy配置(JSON)和密钥脚本(Groovy)纳入单元测试。用JUnit写一个测试类,模拟IHttpRequestResponse对象,传入已知密文和Header,断言解密后Body和签名值是否符合预期。这样,当App升级加密逻辑时,测试失败会第一时间报警,而不是等到渗透测试时才发现。
我在实际项目中发现,最耗时的从来不是写代码,而是确认“到底哪一环出了问题”。Galaxy的价值,就是把原本需要3小时排查的签名失败,压缩到3分钟内定位。它不降低密码学的复杂度,但把复杂度封装成可配置、可调试、可协作的工程模块。当你能对着Burp界面,指着Extender > Output里的Debug日志,清晰说出“这里IV错了”或“这里Body没解密”,你就已经超越了90%的同行。剩下的,只是把这套方法论,复制到下一个加密接口而已。
