Skip to content

fix(security): 默认校验服务器证书,随机串改用 SecureRandom,签名比较改为定时安全 - #4081

Open
devin-ai-integration[bot] wants to merge 2 commits into
developfrom
devin/1785469108-security-tls-and-random
Open

fix(security): 默认校验服务器证书,随机串改用 SecureRandom,签名比较改为定时安全#4081
devin-ai-integration[bot] wants to merge 2 commits into
developfrom
devin/1785469108-security-tls-and-random

Conversation

@devin-ai-integration

Copy link
Copy Markdown

Summary

安全扫描后修复三类问题,均位于对外默认路径上。

1. 默认 HTTP 客户端关闭了 TLS 校验(最严重)

DefaultApacheHttpClientBuilder(mp/miniapp/cp/open/channel/qidian 的默认客户端)无条件信任任意证书;DefaultHttpComponentsClientBuilder(hc5)在此基础上还使用 NoopHostnameVerifier,等于对所有微信接口调用完全放弃 TLS 身份校验,中间人可窃取 access_token、jsapi_ticket、用户数据等。

-sslcontext = SSLContexts.custom().loadTrustMaterial(TrustAllStrategy.INSTANCE).build();
-new DefaultClientTlsStrategy(sslcontext, NoopHostnameVerifier.INSTANCE)
+sslcontext = skipServerCertificateVerification
+  ? SSLContexts.custom().loadTrustMaterial(TrustAllStrategy.INSTANCE).build()
+  : SSLContexts.createSystemDefault();
+new DefaultClientTlsStrategy(sslcontext,
+  skipServerCertificateVerification ? NoopHostnameVerifier.INSTANCE : new DefaultHostnameVerifier())

两个 builder 都新增 skipServerCertificateVerification(默认 false@Data 生成 getter/setter),为抓包代理等自签名证书调试场景保留显式开关,行为变化仅影响此前依赖“忽略证书”的用法。

2. 加解密随机数使用 java.util.Random

WxCryptUtil.genRandomStr() 生成的 16 位随机前缀是 AES-CBC 报文中唯一的每条消息熵(IV 固定为 aesKey 前 16 字节),RandomUtils.getRandomStr() 用于 JSAPI 签名 nonceStr,wxpay v3 SignUtils.genRandomStr() 用于支付请求 nonce_str,三处均改为 SecureRandom

3. 回调签名比较非定时安全

WxCryptUtil.decryptContentsignature.equals(msgSignature) 改为 MessageDigest.isEqual(...)。其余各 service 的 checkSignature 仍使用 equals,属于同类但风险更低的问题,未在本 PR 内改动。

扫描中未发现的问题:硬编码密钥/凭据、SQL 注入、CORS 配置、暴露的调试端点(SDK 内无 Controller/Endpoint);XXE 防护(XmlUtilsWxCryptUtilBaseWxPayResult)与 XStream 白名单已就位;依赖版本未见已知高危 CVE。

验证

  • mvn -DskipTests install(全仓库)通过。
  • 手工用 DefaultApacheHttpClientBuilder / DefaultHttpComponentsClientBuilder 实测:https://self-signed.badssl.comSSLHandshakeException: PKIX path building failedhttps://wrong.host.badssl.comSSLPeerUnverifiedExceptionhttps://api.weixin.qq.com/cgi-bin/getcallbackip 返回 200。
  • 手工验证 WxCryptUtil 加解密回环正常、错误签名仍抛“加密消息签名校验失败”。
  • 新增 ServerCertificateVerificationTest 断言两个 builder 默认不跳过证书校验(TestNG 通过;注意仓库根 POM 中 surefire 配置为 <skip>true</skip>,测试需手工执行)。
  • WxCryptUtilTest 在本机手工执行失败,但在改动前的 develop 上同样失败(commons-codec 对样例 EncodingAESKey 的 base64 严格校验),与本次改动无关。

Link to Devin session: https://app.devin.ai/sessions/cf7c33c129b7468093297a5c3e1e6427
Requested by: @binarywang

- DefaultApacheHttpClientBuilder / DefaultHttpComponentsClientBuilder 默认不再信任所有证书,
  hc5 实现同时改用 DefaultHostnameVerifier,新增 skipServerCertificateVerification 开关供调试场景使用
- WxCryptUtil、RandomUtils、wxpay v3 SignUtils 改用 SecureRandom 生成随机串/nonce
- WxCryptUtil 回调签名比较改为 MessageDigest.isEqual,避免时序攻击

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@binarywang binarywang self-assigned this Jul 31, 2026
@devin-ai-integration

Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ec981e9609

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +275 to +277
if (this.skipServerCertificateVerification) {
//忽略掉对服务器端证书的校验
.loadTrustMaterial((TrustStrategy) (chain, authType) -> true).build();
sslcontext = SSLContexts.custom().loadTrustMaterial((TrustStrategy) (chain, authType) -> true).build();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 将跳过证书开关接入 Apache 连接池

当调用者把 DefaultApacheHttpClientBuilder.get().setSkipServerCertificateVerification(true) 用于自签名抓包或测试环境时,这里创建的 trust-all SSLConnectionSocketFactory 实际不会参与建连:prepare() 已经用注册表里的 this.sslConnectionSocketFactory 构造了 PoolingHttpClientConnectionManager,随后在 builder 上调用的 setSSLSocketFactory(...) 会被该 connection manager 覆盖。因此 Apache 4 默认客户端仍会按默认 trust store 校验证书,和本次改好的 HC5 客户端行为不一致;应把根据开关生成的 socket factory 注册进 connection manager。

Useful? React with 👍 / 👎.

@binarywang binarywang left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

发现两处需要修改后再合并:

  1. Apache HttpClient 4 的 skipServerCertificateVerification 没有接入实际请求使用的连接池。prepare() 先以字段 sslConnectionSocketFactory 创建并注册 PoolingHttpClientConnectionManager,再调用 HttpClientBuilder#setSSLSocketFactory(buildSSLConnectionSocketFactory())。在已显式设置 connection manager 时,后者不会替换 manager registry 的 socket factory,因此开关设为 true 后请求仍会执行系统证书校验,自签名抓包代理无法使用。请在创建 HTTPS registry 时使用按开关构造的 factory,并保留显式自定义 factory 的优先级。

  2. 新增 ServerCertificateVerificationTest 未登记到 weixin-java-common/src/test/resources/testng.xml;该模块 Surefire 固定执行该 suite,CI/常规模块测试不会运行它。且当前只验证 Lombok getter/setter,无法发现第 1 项的 TLS 配置未生效。请将测试接入 suite,并增加真实 TLS 行为验证:默认拒绝无效证书、显式跳过后可连接本地自签名 HTTPS 服务(Apache 4 与 HC5)。

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Author

发现两处需要修改后再合并

两处均已修复,见 3da9f64

1. 证书校验开关接入连接池

DefaultApacheHttpClientBuilder.prepare() 现在先解析出实际使用的 HTTPS 工厂,再同时用于 registry 和 setSSLSocketFactory

SSLConnectionSocketFactory httpsSocketFactory = this.resolveSSLConnectionSocketFactory();
registry = ...register("https", httpsSocketFactory)...
httpClientBuilder.setConnectionManager(connectionManager).setSSLSocketFactory(httpsSocketFactory)

resolveSSLConnectionSocketFactory() 保留使用方自定义工厂的优先级:字段默认值改为静态常量 DEFAULT_SSL_CONNECTION_SOCKET_FACTORY,只有当字段仍是该默认实例(即未调用过 sslConnectionSocketFactory(...))时,才按 skipServerCertificateVerification 构造工厂;构造失败回退默认工厂。

2. 测试接入 suite 并改为真实 TLS 行为验证

ServerCertificateVerificationTest 已登记到 weixin-java-common/src/test/resources/testng.xml。测试内用 BouncyCastle(新增 test scope 的 bcpkix-jdk18on)生成 CN=localhost 自签名证书并启动 com.sun.net.httpserver.HttpsServer,对 Apache HttpClient 4 与 HC5 各断言两件事:默认配置抛 SSLException(校验生效)、skipServerCertificateVerification=true 时返回 200(开关生效)。本地 4 个用例全部通过;把第 1 项改动回退后,Apache 4 的「开关生效」用例会失败,确认该用例能覆盖此问题。

另外发现一个既有问题:testng.xml 中登记的 me.chanjar.weixin.common.bean.WxErrorTest 在仓库里已不存在,直接运行该 suite 会因找不到类而中断(根 POM surefire <skip>true</skip> 掩盖了这一点)。未在本 PR 内改动,需要的话我可以一并清理。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant