惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hacker News: Front Page
P
Palo Alto Networks Blog
T
ThreatConnect
Apple Machine Learning Research
Apple Machine Learning Research
博客园_首页
T
True Tiger Recordings
P
Privacy & Cybersecurity Law Blog
B
Blog
IT之家
IT之家
Last Week in AI
Last Week in AI
F
Full Disclosure
Hacker News: Ask HN
Hacker News: Ask HN
C
Comments on: Blog
Microsoft Azure Blog
Microsoft Azure Blog
C
Cybersecurity and Infrastructure Security Agency CISA
Microsoft Security Blog
Microsoft Security Blog
博客园 - 【当耐特】
N
News and Events Feed by Topic
NISL@THU
NISL@THU
腾讯CDC
雷峰网
雷峰网
Security Latest
Security Latest
李成银的技术随笔
M
Microsoft Research Blog - Microsoft Research
L
LangChain Blog
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Check Point Blog
Y
Y Combinator Blog
Recent Announcements
Recent Announcements
博客园 - Franky
N
News | PayPal Newsroom
V
V2EX
A
About on SuperTechFans
The Register - Security
The Register - Security
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google Online Security Blog
Google Online Security Blog
MyScale Blog
MyScale Blog
Cisco Talos Blog
Cisco Talos Blog
Vercel News
Vercel News
WordPress大学
WordPress大学
C
Cyber Attacks, Cyber Crime and Cyber Security
The Hacker News
The Hacker News
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
爱范儿
爱范儿
A
Arctic Wolf
L
LINUX DO - 最新话题
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

博客园 - 三国梦回

spring boot 项目中oracle datasource设置schema spring cloud项目中,在bootstrap.yml中指定了active的profile,结果不生效 线上服务重启后,从nacos取不到配置了,怎么回事 nginx location没学好,把自己坑了一把 技术问题记录20260125 最近遇到的两个技术问题记录 linux服务器文件上传失败 线上遇到的redis和数据库数据未同步问题、redisson内部实现问题 复杂业务系统线上问题排查过程 nacos中配了一个数字,springboot取回来怎么变了 一个java空指针异常的解决过程 简单记录下最近2个月完成的线上系统迁移工作 centos停服,迁移centos7.3系统到新搭建的openEuler 端口telnet不通排查过程 https证书中的subject alternative name字段作用及如何生成含该字段的证书 linux中如何判断一个rpm是手动安装还是通过yum安装的 对接服务升级后仅支持tls1.2,jdk1.7默认使用tls1.0,导致调用失败 网络抓包文件太大,如何切分 分页查询不加排序有问题,加了排序怎么还有问题 利用mybatis拦截器记录sql,辅助我们建立索引(二) 利用mybatis拦截器记录sql,辅助我们建立索引(一)
sql server版本太老,java客户端连接失败问题定位
三国梦回 · 2025-01-05 · via 博客园 - 三国梦回

背景

最近半路接手了一个系统的优化需求,这个系统有个遗留问题还没解决,随着新需求的上线,系统正式开放使用,这个遗留问题也必须解决。

这个系统大概是下面这样的,支持录入各种数据源的信息(ip、端口、数据库种类、账号密码等):

image-20250104210901249

录入完成后,可以查看这些数据源中的表、表的ddl、表中的列(列名、类型及注释等),也可以查看各个表中的数据。

其中一个数据源,是sql server 2008版本,总是连接失败,更别提获取这个db中的表了。

错误堆栈如下:

image-20250104211502284

定位过程

1、前期处理

在我做新需求的时候,我之前的同事A已经处理过这个问题。这个问题只在线上出现,因为开发测试环境压根没有这么老的数据库版本,在开发测试环境申请一台windows服务器来安装一个这样的老版本数据库,也比较麻烦;所以,同事A在处理的时候,基本是网上查到修改的办法后,直接弄到线上去试试能不能解决。

之前改过两次,第一次是这样:

1、参考附件脚本《配置文件java.security》,修改/usr/local/jdk/bin/java/java.security中的配置项jdk.tls.disabledAlgorithms。
修改内容:删除jdk.tls.disabledAlgorithms配置项的“TLSv1, TLSv1.1”,替换成“DHE”

简单解释下这部分的修改,从前文中的错误堆栈来看,这个问题是和ssl有关系的,我之前猜想的就是,这个sql server和mysql一样,支持使用tls加密传输,保护数据安全;但是,可能sql server 2008版本太老了,不支持tls 1.2/1.3这些,只能使用tls1.1/tls1.0等,但是呢,jdk认为使用tls1.1/1.0不够安全,默认是禁用了的,所以,只要把这个禁用tls1.1/tls1.0的配置给改改,允许jdk使用tls1.1/tls1.0,不就可以连接sql server 2008了吗?

但是,遗憾的是,这个改动之前已经上线试过了,没有生效,还是报错。

另外,在我做新需求的时候,同事A又试了一个改动,把驱动版本升级了下,大家知道java都是使用jdbc去连接数据源的,各个厂商会实现jdbc,之前呢,使用的是sqlserver的4.0版本的驱动,这把,直接弄到了8.4.1,准备搞上去再试试:

image-20250104213141696

2、尝试修改配置禁用加密

我了解到这个情况后,因为需求也比较赶,就没想花大力气来搞这个bug,我们都是内网服务器之间调用,这个加密传输,我感觉不是必要,直接弄成不加密不就行了吗?

我找了下代码,里面有拼接jdbc url的代码:

image-20250104213746728

那直接去掉这个encrypt=true;trustServerCertificate=true,去掉后,本地测试了下连接sql server数据库(不是2008版本),用wireshark抓包看了下,发现客户端发给数据库(sql server常用1433端口)的报文里,还是说加密是开启的:

image-20250104214148592

行吧,我查了下,原来不指定encrypt属性,默认就是true,那我手动指定成false得了。

image-20250104214411744

又抓包看了下,这次有了变化,客户端发过去的报文是说,不使用加密:

image-20250104214503660

服务端返回报文也说,不加密:

image-20250104214556331

但是,我在后续的报文里,发现还是部分加密了的:

image-20250104214722668

这就有得难以理解了。

3、debug驱动代码

所以这时候的思路就是,看看为什么源码里还会加密传输,那只能debug了,看看是不是还有其他选项在控制这块,后面找到如下代码:

image-20250104215227828

在上图中,先是三次握手,再是prelogin(就是前文抓包看到的那部分,如:Encryption: Encryption is available but off (0)),再下来呢,有个if,如果满足这个if,就会开启SSL,此时,就会导致发出去的报文是ssl的,也就是说,只要走了这个if,我们就绕不开ssl,就规避不了这个bug。

那我们看看,怎么绕开这个if吧。

这个if中,左边是个常量,ENCRYPT_NOT_SUP表示不支持加密,image-20250104215651186

右边是个变量,初始化的时候是:

private byte negotiatedEncryptionLevel = TDS.ENCRYPT_INVALID;

后续,什么地方会修改这个变量呢,是在prelogin部分,在处理数据库返回的prelogin响应报文时:

image-20250104215857867

这里,2812行,是直接取响应报文中的值,也就是说,以数据库服务端的为准。

还记得,服务端一般是返回如下值:0。

image-20250104220012549

那这样的话,就会导致那个if条件为true:

image-20250104220149407

这块就有点难办了,这个值是服务端返回的,除非数据库返回ENCRYPT_NOT_SUP,表示不支持加密,否则,这个加密是跑不掉了。但我没太想过要让数据库去改这个配置,毕竟这个库,说是客户端还不少,我不可能去动它,影响太大,可能到时候导致别的客户端要改造。

还有个方向,是通过客户端的传参,去影响服务端的返回值,比如客户端传一个不支持加密,看看服务端的返回值。

但,比较遗憾的是,客户端驱动定死了,只能传下面这两个值,要么ENCRYPT_ON,要么ENCRYPT_OFF:

image-20250104221111123

4、覆盖驱动源码,强行绕过enableSSL方法

当时,我的想法是,把这个if条件改一下,改成:

if (TDS.ENCRYPT_ON == negotiatedEncryptionLevel){
    tdsChannel.enableSSL(serverInfo.getServerName(), serverInfo.getPortNumber());
}

其实,按我这会的想法,改下面这个地方也不错,想办法传ENCRYPT_NOT_SUP给服务端:

requestedEncryptionLevel = isBooleanPropertyOn(sPropKey, sPropValue) ? TDS.ENCRYPT_ON : TDS.ENCRYPT_OFF;

如何才能修改驱动包的代码呢,改是改不了,但是可以想办法覆盖,方法就是在项目中建同包名同类名的java文件(内容直接从源码文件拷贝),然后修改其中的部分代码即可。

但这次有点意思的是,遇到个以前没见过的问题,报如下错误:

java.lang.SecurityException: signer information does not match signer information

最终在网上查了下,(https://blog.csdn.net/weixin_44070655/article/details/129922513),错误的意思是,我们新建的java文件的一些签名信息不太匹配,这块没细看。最终是需要删除jar包中的如下两个文件:

image-20250104223152701

删除的方式,可以直接用压缩软件打开,删除里面的这两个文件即可,另存为即可。然后把改后的jar包发布到私服(可以修改下坐标),或者是使用maven的如下方式:

image-20250104223331747

最终成功绕过enableSSL了,抓包发现,客户端确实没对包进行加密了,但是,服务端不返回任何报文了。我理解的是,服务端当初在进行prelogin协商时,返回的加密选项是:ENCRYPT_OFF,这个按正常流程,后续就是需要加密的,我们现在强行改了客户端源码,导致服务端陷入了迷思:wtf,客户端怎么回事,怎么没加密,这个客户端有问题?行吧,我不返回了。

最终,我还是放弃了这条路。因为,我上网查了下这个encrypt选项。

https://learn.microsoft.com/zh-cn/sql/connect/jdbc/understanding-ssl-support?view=sql-server-ver16

image-20250104224440595

原来,加密分为两个部分,第一个部分是登录部分,建立连接时,会传输用户名密码,我当时发现:在我上面强行改了客户端驱动,收不到服务端响应时,进行了抓包,发现我可以看到明文密码,当时我也有点惊讶,也反应过来了,难怪要弄ssl加密呢;第二个部分是,后续的数据的加密,如传输的sql语句和执行结果。

当encrypt为true时,两个部分都会加密;而当encrypt为false时,登录部分还是会加密,而数据部分不会加密。

所以,不管怎么说,登录部分总是要加密的,所以我还是不要挑战这条路了,毕竟这是协议规定好了的。

5、增加ssl日志

最终,把修改源码部分,全都回退了。最终的jdbc url选项如下,驱动版本也保留着

;encrypt=false;trustServerCertificate=true;
<dependency>
    <groupId>com.microsoft.sqlserver</groupId>
    <artifactId>mssql-jdbc</artifactId>
    <version>8.4.1.jre8</version>
</dependency>

说白了,ssl问题依然会有(毕竟encrypt=false,登录部分还是要走ssl),但是,我们可以想办法把ssl过程中的日志打印出来:

System.setProperty("javax.net.debug","ssl:handshake:verbose");

image-20250104225440075

这个呢,会打印ssl过程中的细节(注意,是打印到标准输出的,日志文件里没有,要看看启动java进程时,把标准输出重定向到哪里去了,不能是 > /dev/null这种),类似下面这种,到时候我们上线了再看看日志情况吧:

image-20250104225659003

6、上线后检查ssl日志

这个问题,现在说白了,就是客户端发了ssl握手消息给服务端,正常来说,服务端是要响应的,像下面这样,返回server hello这个报文,其中包含选定的ssl加密套件、服务端证书链等信息:

image-20250105080602411

然后我预期的是,上线后,这个ssl日志能把服务端报错的原因打印出来,结果并没有。

直接就是说,服务端关闭了连接,终止握手:

image-20250105080917662

从后来我找运维抓的包也能看出来,服务端发了tcp关闭的报文:

image-20250105081152690

7、尝试更换客户端驱动版本

此时,有点陷入僵局了。客户端没日志,网络报文也看不出来,那意思是只能看看服务端有没有日志了吧。

然后去找了dba,我现场演示了下,他看了数据库端的日志文件:啥都没有。

他给了我两个方案,一个是这个库太老,后续会复制一个新库出来,这个要等;再一个是,这个库也有其他的项目在用,也是java客户端的,他说帮我问下相关同事。

然后后续我单独加了那个同事,了解了下,他们用的驱动版本是7.4.1,我们目前线上是8.4.1:

<dependency>
    <groupId>com.microsoft.sqlserver</groupId>
    <artifactId>mssql-jdbc</artifactId>
    <version>7.4.1.jre8</version>
</dependency>

然后,jdbc的url是这样:

image-20250105082000028

最终我们的方向是,换不同版本的驱动看一下,先试上面这个7.4.1.jre8版本。因为之前我也查到过资料,就是xx版本的数据库,需要xx版本的驱动。

https://learn.microsoft.com/zh-cn/sql/connect/jdbc/microsoft-jdbc-driver-for-sql-server-support-matrix?view=sql-server-ver16

image-20250105082313608

从图里能看出来,sql server 2008,需要7.2版本的驱动。我们之前的8.4.1,肯定是高了;其实看上图,7.4也高了,但不知道人家项目为啥能行,就也试试呗。

8、开发测试驱动版本工具类

写了个类来测试:

image-20250105082729476

执行方式就是把jar和class放到同一目录下执行:

[root@news-center-app ~]# ll DbConnectTest.class mssql-jdbc-7.4.1.jre8.jar 
-rw-rw-rw- 1 root root    1631 Dec 30 15:30 DbConnectTest.class
-rw-rw-rw- 1 root root 1209660 Dec 30 15:16 mssql-jdbc-7.4.1.jre8.jar
[root@news-center-app ~]#  java -classpath .:./mssql-jdbc-7.4.1.jre8.jar DbConnectTest "jdbc:sqlserver://1.1.1.1:1433;databaseName=xxx;encrypt=false;trustServerCertificate=true" zhangsan xxx

这样呢,方便我们替换驱动的jar包。

结果呢,运维说必须走流程才能这么玩,理由就是不能在生产上做测试,battle了半天,后面还是提流程了(正好有个小需求又要上,就把这个工具一起弄上去了)。

上线的时候,我们顺便就把之前的驱动版本从8.4.1.jre8改成了7.4.1.jre8,也包括这个小工具。

上线后,以为这次肯定能行,结果,还是报一样的错误,此时正值周五快下班的时候,我无语了:就不能早点解决了这个bug,好好过个周末,不然还牵挂着它。

结果我回家路上,运维在群里说,bug可以了,他上网找了下文章:

https://blog.csdn.net/zhujun300/article/details/141434867

还是修改jdk的java.security文件,这次又把另一个被禁用的给去掉了:3DES_EDE_CBC. (我在开头说,同事A之前就改过一次,但是去的是tls1.1/tls1.0,没去这个3DES_EDE_CBC)。

然后,就好了。

行吧,还是能好好过周末的。

9、为什么去掉3DES_EDE_CBC能好

网上翻了很多资料,没找到讲这块原理的。我自己本地试验了下,在去掉这个3DES_EDE_CBC前,记录了打印的ssl日志;在去掉后,又记录了下。

对比如下,可以看到,去掉后,握手消息中多了很多加密套件(其中都包含了3DES_EDE_CBC这个加密算法):

image-20250105084822878

那这样的话,我们可以认为,线上那个库,应该是不支持客户端发送出去的所有加密套件,才把ssl握手终止了。

而加上3DES_EDE_CBC后,多了一些加密套件,而这些套件,正好服务端就支持,所以就可以了。

当然,具体选择了哪个加密套件,可能得下周上班了再找运维看看日志或者抓个包瞧瞧才知道。

这次呢,我也学会了一个新技能,由于ssl握手消息是封装在其他协议(TDS)里面的,在wireshark中都没法看。

image-20250105085534815

上面蓝色部分就是握手消息,但看不了,要知道握手的具体细节,非得看ssl日志才行,这个让人有点不爽。

我上网找了下,还真找到个网站:

https://williamlieurance.com/tls-handshake-parser/

只要把十六进制流复制进去,就能解析ssl。

image-20250105085747745

image-20250105085817985

对我来说,算是不小的一个收获。

10、怎么查看sql server 2008支持的加密套件

一开始,对这块不理解,以为ssl加密相关能力是sql server 2008这个软件提供的(对windows服务器太不了解了),但后来查了些资料发现,ssl加密相关能力是操作系统提供的;像是linux呢,一般就是安装了openssl,其他软件都是复用openssl的能力。

而sql server 2008,当时查了下版本:

Microsoft SQL Server 2008 (SP1) - 10.0.2531.0 (X64)   Mar 29 2009 10:11:52   Copyright (c) 1988-2008 Microsoft Corporation  Enterprise Edition (64-bit) on Windows NT 5.2 <X64> (Build 3790: Service Pack 2) (VM) 

没细问windows服务器的版本,但我们从上述也能看出来:

windows服务器版本其实就是:Windows NT 5.2 (Build 3790: Service Pack 2)

这个服务器,搜索了下,其实是:win server 2003版本。

image-20250105100603157

难怪了,这个操作系统版本太低了,估计就是很多ssl套件都不支持。

那么,我们如何查看一个windows电脑,支持哪些加密套件呢?

有以下几种方式:

10.1 通过组策略管理器查看

  1. 按下 “Win + R” 键,输入 “gpedit.msc” 并回车,打开组策略编辑器2。
  2. 依次展开 “计算机配置”→“策略”→“管理模板”→“网络”→“SSL 配置设置”2。
  3. 双击右侧的 “SSL 密码套件顺序”,选择 “已启用”,在左下侧可以看到支持的 SSL 加密套件

10.2 通过命令查看

使用 PowerShell:以管理员身份运行 PowerShell,输入Get-TlsCipherSuite命令,可列出当前系统上配置的 TLS/SSL 加密套件以及它们的启用状态等信息。

image-20250105095527943

10.3 IISCrypto

这边下载了一个软件:https://www.nartac.com/Products/IISCrypto/Download

可以大概看到ssl中涉及的几个部分:传输协议、加密算法、hash算法、秘钥交换算法。

image-20250105095223507

通过上述几个部分,组成了各种各样的ssl套件:

image-20250105095317399

官方参考链接

https://learn.microsoft.com/en-us/windows/win32/secauthn/schannel-cipher-suites-in-windows-vista

其他尝试过的定位手段

1、本地安装sql server 2008

我猜测这个问题应该是比较好复现的,只是苦于没有环境,然后就想着在本机装一个,没想到,就这也踩了好久的坑。

安装sql server 2008,依赖.net framework 3.5这个运行环境,我是win10系统。网上的方法分两类,在线安装和离线安装,整个.net framework 3.5包有100多m,在线安装,我这边反正不行,不只是网络的问题,好像在线安装是需要一个service正常运行才可以:windows update。

相信很多人,当初为了禁用windows的升级,可能把这个service都删掉了。

可能也正是因为这样,我甚至离线安装也是失败的。

我也附上几个链接吧,万一大家可以呢:

https://mp.weixin.qq.com/s/y0sZz8wtLtcPQM0sI8DHmQ

https://mp.weixin.qq.com/s/_unsXuBLH1JjtsprKYjSWw

https://mp.weixin.qq.com/s/4FtoTMF3L_hDAXGu_GgOdA

试这些的时候,也可以先看下官方帮助文档:

https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/enable-or-disable-windows-features-using-dism?view=windows-11

C:\WINDOWS\system32>dism /online /?
dism /online /Enable-Feature /?
dism /online /Add-Package /?

最终,我用上述这些方法也没成功,后来是按照如下文章来解决的:

https://blog.csdn.net/Roeluo/article/details/144692042

当然,这个.net framework 3.5装上了,并不影响我的sql server 2008安装失败,当然,现在bug都解决了,有空再弄吧。

参考链接

https://blog.csdn.net/wpf416533938/article/details/128573683

https://blog.csdn.net/tanhongwei1994/article/details/84957254

https://learn.microsoft.com/zh-cn/archive/blogs/jdbcteam/the-driver-could-not-establish-a-secure-connection-to-sql-server-by-using-secure-sockets-layer-ssl-encryption

https://stackoverflow.com/questions/32766114/sql-server-jdbc-error-on-java-8-the-driver-could-not-establish-a-secure-connect

https://stackoverflow.com/questions/79113822/java-1-8-sql-server-2008-r2-unable-to-run-query-when-encryption-is-activated

https://www.reddit.com/r/sysadmin/comments/u6grqv/very_legacy_ssl_problem_on_server_2003yep_it/