jasypt-spring-boot 可预测盐值漏洞CVE-2026-9370
jasypt-spring-boot 是基于 Jasypt(Java Simplified Encryption)开发的 Spring Boot 集成组件,能够无缝整合 Spring Boot 配置体系。
一、基本情况
jasypt-spring-boot 是 Spring Boot 配置加密的主流解决方案,核心价值是低成本、无侵入地保护敏感配置,可以广泛应用于生产环境。

jasypt-spring-boot 支持主流加密算法,可以对 Spring Boot 配置文件中的敏感信息(数据库密码、API 密钥、令牌等)进行加密存储。
栋科技漏洞库关注到 jasypt-spring-boot 受影响版本中存在的可预测盐值漏洞,该漏洞现已追踪为CVE-2026-9370,CVSS 3.X评分3.7。
二、漏洞分析
CVE-2026-9370漏洞可导致所有使用该组件、按照官方密码加密方案配置的用户,在全版本中都使用了密码学层面不安全的默认配置。
受影响版本SimpleGCMConfig.getSecretKeySaltGenerator() 方法在未配置盐值时返回 ZeroSaltGenerator,生成固定的 16 字节 0x00。
导致使用相同主密码的所有部署生成相同的 AES-256 密钥,攻击者可在跨部署场景中利用此问题。
com.ulisesbocchio.jasyptspringboot.encryptor.SimpleGCMConfig#getSecretKeySaltGenerator() 方法在用户未显式配置
jasypt.encryptor.gcm-secret-key-salt 参数时,会默认返回 org.jasypt.salt.ZeroSaltGenerator(一个固定的 16 字节全零盐值)。
项目说明文档将该参数标注为可选,默认值为 0,无盐值。
因此,按照官方文档配置AES/GCM 主密码加密的用户,会使用固定不变的密钥派生方式,且部署间无唯一标识。
该组件中PBKDF2默认迭代次数仅1000次(远低于OWASP 2023推荐的60万次),该配置同时存在于GCM主密码模式和 PBE 模式中。
所有使用该组件、按照官方密码加密方案配置的用户,在全版本中都使用了密码学层面不安全的默认配置。
1、以上两个问题共同导致:
跨部署环境的 AES-256 密钥复用:使用相同主密码的不同部署环境,会生成完全一致的密钥;任意环境的密文可在其他环境直接解密。
针对弱主密码的离线暴力破解攻击成本大幅降低:一套预计算的彩虹表可用于全球所有部署环境。
2、受影响组件
jasypt-spring-boot 中包含 SimpleGCMConfig 类的所有发行版本
3、存在漏洞的代码路径:
(1)GCM 主密码模式
文件路径:jasypt-spring-boot/src/main/java/com/ulisesbocchio/jasyptspringboot/encryptor/SimpleGCMConfig.java
第 75–81 行:getSecretKeySaltGenerator() 方法在未配置盐值时,直接返回全零盐生成器(ZeroSaltGenerator)
第 35 行:secretKeyIterations 密钥迭代次数默认值为 1000
(2)PBE 模式
文件路径:jasypt-spring-boot/src/main/java/com/ulisesbocchio/jasyptspringboot/configuration/StringEncryptorBuilder.java
第 97 行:config.setKeyObtentionIterations("1000") 密钥获取迭代次数默认值为 1000
问题 1:GCM 主密码密钥推导过程中使用硬编码全零盐值
// SimpleGCMConfig.java
public SaltGenerator getSecretKeySaltGenerator() {
return saltGenerator != null ?
saltGenerator :
(secretKeySalt == null ?
new ZeroSaltGenerator() : // <-- vulnerability
new FixedBase64ByteArraySaltGenerator(secretKeySalt));
}
当未配置jasypt.encryptor.gcm-secret-key-salt(默认状态)时,盐值生成器会回退使用org.jasypt.salt.ZeroSaltGenerator,
该生成器会产生 16 字节的 0x00 数据。此盐值会被传入 PBKDF2-HMAC-SHA256 算法以派生 AES-256 密钥。
由于使用固定盐值的 PBKDF2 算法具有确定性,所有使用相同主密码的部署环境都会生成完全一致的 AES-256 密钥。
项目 README 文件将该盐值属性标注为 “可选,默认值为 0,无盐值”,因此按照文档说明进行配置的用户会使用存在漏洞的代码逻辑。
程序启动时不会输出任何警告信息。
4、调用链(默认配置的用户路径):
StringEncryptorBuilder.createGCMDefault()
└─ SimpleGCMConfig (user sets only secretKeyPassword)
└─ SimpleGCMByteEncryptor.loadSecretKey(config)
└─ getAESKeyFromPassword(password,
ZeroSaltGenerator, // deterministic 16×0x00
1000,
"PBKDF2WithHmacSHA256")
问题2:两种基于密码的模式均使用不安全的默认迭代次数(1000)
// SimpleGCMConfig.java (GCM mode)
private int secretKeyIterations = 1000;
// StringEncryptorBuilder.java line 97 (PBE mode)
config.setKeyObtentionIterations(
get(configProps::getKeyObtentionIterations,
propertyPrefix + ".key-obtention-iterations", "1000"));
默认的 1000 次迭代同时适用于 GCM 主密码模式和 PBE 模式(PBEWITHHMACSHA512ANDAES_256)。
OWASP 2023 密码学指南建议 PBKDF2-HMAC-SHA256 使用 600000 次迭代。
在 1000 次迭代情况下,普通 GPU 每秒可针对截获的密文计算约 1 亿个PBKDF2 候选值 —— 足以对弱/中等强度主密码实施字典攻击。
关于 README 中 “可选” 的描述
对这份报告最常见的第一反应是:
“README 将 gcm-secret-key-salt 标注为可选,未设置该参数的用户接受了默认值。这属于配置问题,而非漏洞。”
本报告直接回应该异议。
README 的描述 ——“可选,默认值为 0,无盐值。
若提供,请以 ba64 格式指定盐值字符串”—— 并不会让该问题不再成为漏洞。
这是典型的 CWE-1188(使用不安全默认值初始化资源)。
MITRE CWE 分类专门添加 CWE-1188,用于覆盖库提供不安全默认值并将其标注为 “可选” 而非 “必填” 的情况。
5、文档描述无法免除该库责任的三个原因:
(1)README 的描述存在实质性误导。
“默认值为 0,无盐值” 暗示盐值步骤被跳过。
实际行为是 PBKDF2 使用 16 字节全零盐值运行 —— 一个在全球所有该库部署中共享的硬编码常量。
阅读 README 的用户无法从描述中判断密码学后果(确定性密钥派生;跨部署密钥复用)。
他们会合理认为 “盐值只是一项额外加固选项”,而非 “不设置该参数,我的 AES 密钥将与所有使用相同密码的其他部署完全一致”。
(2)未记录启动警告。
按照文档说明配置的用户不会收到任何运行时提示,告知其正使用存在漏洞的代码路径。
许多同类库会输出类似 “警告:
未配置盐值;派生密钥具有确定性,所有使用该密码的安装实例将共享相同密钥” 的警告;该库未实现此功能。
(3)实例级盐值是安全 PBKDF2 的必填参数,而非风格偏好。
NIST SP 800-132 和 OWASP 2023 均将唯一随机盐值指定为 PBKDF2 保障安全性的前提条件。
在面向用户的文档中将盐值标注为 “可选”,错误表述了密码学标准。
CWE-1188 已成为许多库相关 CVE 编号的依据,这些库均有意提供被文档标注为 “可选” 或 “可配置” 的不安全默认值。
维护者知晓该选项的事实,是 CWE-1188 问题认定的前提,而非抗辩理由。
三、POC概念验证
附件为可运行的 JUnit 5 概念验证代码:ZeroSaltKeyReusePoCTest.java。
1、三项测试验证:
defaultSaltGeneratorIsZero_whenNoSaltConfigured—— 确认未配置盐值时,默认盐值生成器为全零盐值生成器。
pbkdf2WithZeroSaltIsDeterministic—— 使用相同密码的两次独立 PBKDF2 派生操作生成相同的 AES 密钥。
ciphertextFromDeploymentA_decryptsOnDeploymentB—— 由一个 SimpleGCMStringEncryptor 实例生成的密文,可在仅共享主密码、独立构建的第二个实例中正常解密。
观测输出(JDK 21,jasypt 1.9.3,jasypt-spring-boot 4.0.5-SNAPSHOT)
Derived AES key (deployment A): f8dd1a881bb76f3df8d0482c6815aeb91aa75a2a2c84cba585aecee1103a93bf
Derived AES key (deployment B): f8dd1a881bb76f3df8d0482c6815aeb91aa75a2a2c84cba585aecee1103a93bf
Plaintext : db-admin-password=hunter2
Ciphertext produced by A : pBhukUDHUfMf4IRlMnWkxk9FVLunH9EAm6nhCKtRpmj+p07nvR2xVNVVVHz1u3IcxPfwiqA=
Ciphertext produced by B : ZpJ2gjghaqF+6FlNQDxjtkTzCw73dk2MFeY3/1O9q/b2RzIL7hPu5CH4S7v7T+Im3/UXKDo=
A-ciphertext decrypted on B as : db-admin-password=hunter2
B-ciphertext decrypted on A as : db-admin-password=hunter2
Tests run: 3, Successful: 3, Failed: 0
两个独立加密器实例生成了完全相同的派生密钥(f8dd1a88…3a93bf),且成功实现跨实例解密,具体证实了跨部署密钥复用的影响。
2、复现步骤
# 1. Place the attached PoC at:
# jasypt-spring-boot/src/test/java/com/ulisesbocchio/jasyptspringboot/security/ZeroSaltKeyReusePoCTest.java
# 2. From the inner module directory:
cd jasypt-spring-boot
mvn -pl . -Dtest=ZeroSaltKeyReusePoCTest test
可在任意 JDK 17 及以上版本中复现,
使用 Maven 坐标:com.github.ulisesbocchio:jasypt-spring-boot:4.0.5-SNAPSHOT(或包含 SimpleGCMConfig 的任意早期版本)。
概念验证代码仅使用模拟值(密码 “correct horse battery staple”,明文 “db-admin-password=hunter2”),不含真实密钥。
修复版本中通过移除 ZeroSaltGenerator 回退机制解决问题,但截至报告生成时官方尚未发布修复版本。
四、影响范围
jasypt-spring-boot 3.x:≤ 3.0.5
jasypt-spring-boot4.x:≤ 4.0.4
五、修复建议
暂无
六、参考链接
管理员已设置登录后刷新可查看