ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

TLS_RSA密钥交换漏洞原理与修复实操:从前向保密到Nginx/Apache/Tomcat配置

TLS_RSA密钥交换漏洞原理与修复实操:从前向保密到Nginx/Apache/Tomcat配置 我把这次漏洞处理从原理到实操完整梳理一遍。这不算一个多严重的问题但它是每个做安全运维的人一定会遇到的典型“告警式漏洞”只扫了一遍心里只知道“有个漏洞”和真正搞明白“它为什么报、要不要改、怎么改”之间差的恰好是今天这篇博文的内容。1. 先说清楚RSA密钥交换和这个漏洞到底是怎么一回事1.1 RSA密钥交换在老TLS握手里的位置我们要聊的这套漏洞目标主机支持RSA密钥交换全称其实是“TLS_RSARSA密钥交换”是TLS握手协议里一种历史非常悠久的密钥协商方式。要理解它为什么被当作漏洞得先知道TLS握手里“密钥协商”这个环节是干什么的。我们平时访问HTTPS网站浏览器和服务器的数据是加密的这个加密用的对称密钥比如AES密钥不是提前约定好的而是在每次连接建立时由客户端和服务器通过握手协议临时协商出来的。而这个“协商”的过程就必须依赖非对称加密算法也就是公钥和私钥那套机制因为我们不能在公网上直接把密钥明文发出去。RSA密钥交换做的事情就是客户端生成一个随机数也就是预主密钥pre-master secret然后用服务器的RSA公钥把它加密发给服务器服务器用自己的RSA私钥解密双方就拿到了同一个对称密钥。这个机制本身设计得很精巧在TLS 1.0、TLS 1.1那个年代它是绝对的主流方案。它的问题不在“RSA加密算法本身不安全”而在于密钥交换的方式有一个致命缺点没有前向保密性Forward Secrecy也叫完美前向保密PFS。1.2 没有前向保密性意味着什么前向保密这个概念很多人觉得抽象我用一个生活中的例子来解释。你有一把保险柜钥匙相当于服务器的RSA私钥为了保护你的日记相当于通信内容你每次把日记放进一个加密盒子里盒子密码是你临时想的相当于每次会话的对称密钥但你没法直接告诉对方密码于是你用保险柜钥匙把盒子密码加密后递给对方。这种方式下所有方盒子密码的加密都依赖于同一把保险柜钥匙。问题来了一旦哪天这把保险柜钥匙丢了——对应服务器私钥被黑客窃取或者因密码学进步被破解——黑客就可以把你过去记录的所有盒子密码全部解开进而打开你过去所有的日记。也就是说历史流量全部被解密了。如果换成支持前向保密的算法比如ECDHE临时椭圆曲线Diffie-Hellman密钥交换双方每次握手都会临时生成一对密钥且这个临时密钥用过即弃。就算服务器的主私钥泄露也只是让攻击者能冒充服务器但他无法解密过去截获的历史流量。用一句话总结RSA密钥交换把“当前安全”建立在“私钥永久安全”这个假设上而实际上私钥有泄露的一天历史数据的安全就全部归零。现在很多安全合规要求、等保测评、行业安全检查都把“支持TLS_RSA密钥交换”列为隐患或高风险项通行的依据正是这一点。1.3 为什么明明是“原理扫描”还要当回事很多团队看到扫描报告里“【原理扫描】”这四个字第一反应往往是“是不是误报”。这个想法其实不准确我先把这个概念讲清楚。“原理扫描”是常见漏洞扫描器的分类方式之一它区别于“验证扫描”或“利用扫描”。原理扫描的意思是扫描器并没有真的去尝试利用这个漏洞做一次完整的攻击比如真的去窃取私钥或解密流量而是通过检测目标主机是否具备某种特征从原理上判断漏洞是否存在。具体到RSA密钥交换扫描器就是尝试和目标服务器做一次TLS握手查看服务器在握手过程中是否声明支持TLS_RSA套件。只要支持它就直接判定“存在漏洞”不做进一步验证。所以“原理扫描”不是误报它只是把规则定得比较宽。它不误报“支持RSA密钥交换”这个行为本身但如果你的业务环境中根本没有客户端在用老TLS、没有历史流量被解密的实际风险那它可能出现“实际风险不高但报告上仍然报漏洞”的情况。但这不意味着可以放任不管因为扫描报告是给管理层、合规审计看的一个挂在报告上迟迟不消的漏洞项在安全考核和红蓝对抗中都是很扎眼的存在。再说RSA密钥交换本身也确实过时了把它禁掉没有任何技术负担。1.4 哪些系统最容易中招我在实际处理中遇到的最典型的几类情况大家可以对照自查一下。老旧的Nginx/Apache服务器很多公司线上服务器从2015年之前跑到现在配置文件一直是“能不动就不动”默认配置里包含RSA密钥交换套件一扫描一个准。Tomcat等Java中间件Tomcat 8.5之前的版本默认的HTTPS配置通常使用TLS_RSA_WITH_AES_128_CBC_SHA这类套件非常容易命中。IIS服务器Windows Server 2008/2012上未做加固的IIS系统自带的SSL配置里RSA密钥交换默认是开启状态。网络设备/物联网设备很多路由器、防火墙的管理后台自带的HTTPS服务为了兼容老设备把TLS 1.0和RSA套件都开着而且这种设备往往很难改配置只能打补丁或者关管理网口。我处理过的很多案例里攻防演练时扫描报告上一排“高危漏洞”点进去一看全是这种“原理扫描”的问题。这类问题虽然不算什么真正致命的0day但它会影响整个安全评分而且修复过程不复杂完全可以规范化处理。2. 确认自己是不是真的踩雷从报告到手工复现2.1 扫描报告上的“原理扫描”到底是怎么得到结论的在动手修复之前我建议先搞明白扫描器为什么判定你“支持RSA密钥交换”。大部分商业扫描器和开源工具比如Nmap的ssl-enum-ciphers脚本做法是一样的和目标服务器的443端口建立TCP连接发起一个ClientHello请求服务器支持的全部密码套件服务器回一个ServerHello或Certificate消息里面会携带它支持的套件列表脚本再从中筛选是否存在以TLS_RSA_开头的套件。这里有一个容易忽略的细节扫描器上报“支持RSA密钥交换”指的是服务器在握手的密码套件列表里包含了TLS_RSA套件而不代表客户端当前连接已经在使用RSA密钥交换。因为现在主流的浏览器客户端Chrome、Firefox在和服务器的多次握手中已经会优先协商ECDHE套件了只有老版本客户端比如打着IE6、IE8旗号的收费系统客户端才会落到TLS_RSA上。扫描器要摸清的是服务器“支持”的能力面而不是当前某个连接的“实际使用情况”。所以验证思路就定了不是去连一次HTTPS页面看能不能打开而是要用openssl命令直接把服务器支持的套件全部列出来看里面有没有我们不要的TLS_RSA套件。2.2 用OpenSSL手工自查的完整流程我平时最常用的验证命令是这一条openssl s_client -connect your.server.com:443 -tls1_2 -cipher RSA 21 | grep Cipher is这条命令的意思是用OpenSSL客户端和your.server.com的443端口做TLS 1.2握手并且限定密码套件只选RSA开头也就是TLS_RSA套件的然后看握手最后选择了哪个Cipher。如果返回的结果是Cipher is NONE或者直接报错那说明服务器已经不支持RSA密钥交换了修复成功如果返回的是Cipher is TLS_RSA_WITH_AES_128_CBC_SHA之类的名字那说明服务器还开着这个套件扫描器的结论没问题。用这条命令跑出来的输出我再贴一个实际的工作片段给大家参考CONNECTED(00000003) depth2 C US, O SomeCA, CN Some Root CA verify error:num19:self signed certificate in certificate chain ... Cipher is TLS_RSA_WITH_AES_128_CBC_SHA看到Cipher is TLS_RSA_WITH_AES_128_CBC_SHA就知道中招了。如果想要更系统的套件清单可以用以下命令把全部套件列出来openssl s_client -connect your.server.com:443 -tls1_2 /dev/null 2/dev/null | openssl ciphers -V ALL 2/dev/null不过这条命令的效果因平台而异更推荐用Nmap的脚本在2.3小节统一讲。2.3 顺手把Nmap验证也做了如果要一次性摸清主机全部端口的TLS配置情况我推荐直接用Nmap的ssl-enum-ciphers脚本命令是nmap --script ssl-enum-ciphers -p 443,8443,9443 your.server.com这条命令跑完以后脚本会把该端口支持的TLS版本、每个版本的密码套件、以及每个套件的安全评级A/B/C/F都列出来非常适合做修复前后的对比。我在排查的时候习惯把修复前的Nmap输出保存一份修复后再跑一遍两相对比套件数量减少了哪些、等级提升到多少一目了然报告也好写。这里提一个Nmap脚本使用上的小细节ssl-enum-ciphers脚本默认只做TLS握手不会真的发送应用层数据所以对目标主机的负载影响很小可以放心在业务低峰期用。但如果端口范围太大比如全端口扫建议加上--min-rate控制发包速率避免触发目标机器的安全防护报警。2.4 验证时最容易踩的两个坑第一个坑只测443端口。现在很多应用会拆分端口比如API网关放在8443、管理后台放在9443、消息中间件放在61617上SA往往只修了443结果扫描器换了个端口一扫又报漏洞。我的建议是不要只盯着443把业务用到的所有TLS端口一次性排查清楚再动手修配置。第二个坑用老旧的openssl命令格式。有些系统自带的是OpenSSL 1.0.x语法和1.1.1/3.0有些差异比如openssl ciphers -V的参数支持情况不完全一样。如果出现命令参数不识别的情况不要浪费时间直接用Nmap脚本验证兼容性最好。3. 修复实操从配置到验证的完整过程这个漏洞的修复思路很统一在服务器端的TLS配置里禁用所有以TLS_RSA_开头的密码套件保留ECDHE、DHE等提供前向保密性的套件。但不同中间件的配置写法不一样我挨个给可抄的作业。3.1 Nginx服务器怎么改最省心如果你的Web服务器是Nginx修复的关键在于改ssl_ciphers这一行。我推荐直接用它官方推荐的配置ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers on;这段配置的核心逻辑是只开启ECDHE和DHE两类临时密钥交换套件这两类都具备前向保密性RSA密钥交换套件一个都不留。改完之后测试配置语法nginx -t输出ok和successful后执行重载nginx -s reload很多老配置里还有ssl_protocols TLSv1 TLSv1.1 TLSv1.2;这样的写法把TLS 1.0和TLS 1.1也开着。TLS 1.0/1.1本身已经属于过时的加密协议和RSA密钥交换是“难兄难弟”一般建议一并禁用只留TLSv1.2和TLSv1.3。3.2 Apache服务器一个配置文件管全局Apache的修改思路基本一样在SSL配置段通常是/etc/httpd/conf.d/ssl.conf或/etc/apache2/mods-available/ssl.conf里找到SSLCipherSuite和SSLProtocol两行改成下面这样SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1 SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 SSLHonorCipherOrder on改完后测试配置并重载apachectl configtest systemctl reload httpd # 或 apache2ctl -k gracefulApache的套件写法不带TLS_前缀用的是OpenSSL的短名称写错一个字符整个服务起不来所以每次改完一定要先跑configtest不要直接reload。3.3 Tomcat和Spring Boot靠JVM参数兜底Java技术栈的HTTPS配置有点特别Tomcat在server.xml的Connector节点里可以配置ciphers属性。但更常见的场景是Spring Boot内嵌Tomcat配置在application.yml或application.properties里。Tomcat 8.5/9.0的server.xml中HTTPS连接器修改示例Connector port8443 protocolorg.apache.coyote.http11.Http11NioProtocol maxThreads150 SSLEnabledtrue schemehttps securetrue SSLHostConfig Certificate certificateKeystoreFileconf/keystore.jks certificateKeystorePasswordchangeit / SSLProtocolsTLSv1.2,TLSv1.3/SSLProtocols CiphersECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384/Ciphers /SSLHostConfig /ConnectorSpring Boot项目里如果用的是内嵌Tomcat我一般直接在application.yml里加server: ssl: enabled: true protocol: TLS enabled-protocols: - TLSv1.2 - TLSv1.3 ciphers: - ECDHE-ECDSA-AES128-GCM-SHA256 - ECDHE-RSA-AES128-GCM-SHA256 - ECDHE-ECDSA-AES256-GCM-SHA384 - ECDHE-RSA-AES256-GCM-SHA384Java生态有个历史包袱要专门说一句JDK 8之前的版本默认启用的密码套件列表里既有TLS_RSA套件又没有TLS_AES_128_GCM_SHA256这类TLS 1.3套件。如果项目还在用JDK 8u251之前的版本默认禁用TLS 1.3光改Tomcat配置还不够保险建议直接把JDK升级到8u261以上、11.x、17.x版本同时改配置双管齐下。旧JDK里还有jdk.tls.disabledAlgorithms这个安全属性文件位于$JAVA_HOME/conf/security/java.security可以在jdk.tls.disabledAlgorithms里追加RSA keySize 2048, TLS_RSA等参数进一步强制禁用RSA密钥交换。不过这个方案容易误伤其他依赖TLS的组件我的经验是如果不是特别老的系统优先升级JDK版本比手动改安全属性更稳妥。3.4 中间件和云负载均衡器怎么处理这里再把其他几类常见中间件和负载均衡器的处理方式也一并列出来方便对照处理。设备/中间件类型配置位置关键配置项示例备注Nginxhttp块/server块ssl_ciphers、ssl_protocolsreload即可生效nginx -t必须是第一步Apachessl.confSSLCipherSuite、SSLProtocolapachectl configtest通过后再graceful重启Tomcatserver.xml ConnectorSSLHostConfig Ciphers注意JDK版本对TLS 1.3套件的支持情况Spring Boot内嵌Tomcatapplication.ymlserver.ssl.ciphers修改后需要重启应用进程IIS 8/10注册表或IIS Crypto工具Cipher Suites列表用IIS Crypto工具勾选后点击“Apply”会重启相关服务阿里云SLB/腾讯云CLB控制台-证书管理/监听配置选择“安全策略”-禁用TLS_RSA套件在监听器或安全策略组中调整套件组合F5 BIG-IPClient SSL ProfileCiphers设置单独在profile里改名、重新关联云负载均衡器的处理最省心通常只需要在控制台选择一个“增强型安全策略”或“高等级”模式系统就会自动帮你去掉过时套件不需要动后端ECS。但注意负载均衡器放开和后端真实服务器的连接走的还是内部网络通常不受这个漏洞影响重点改对外暴露的那一层即可。3.5 修复后如何确认配置没有改坏改配置最怕的不是漏改而是把访问改坏了。我在每次修复后都会把验证做全套不只跑扫描器还会做实际的业务连通性验证。先做本地握手验证openssl s_client -connect your.server.com:443 -tls1_2 -cipher ECDHE-RSA-AES128-GCM-SHA256 21 | grep Cipher is拿一个正常的现代套件过去握手如果返回的是Cipher is ECDHE-RSA-AES128-GCM-SHA256说明握手正常。再拿RSA套件去握一次确认被拒openssl s_client -connect your.server.com:443 -tls1_2 -cipher RSA 21 | grep Cipher is如果输出Cipher is NONE或报错no cipher match就说明修复生效。然后跑一次真实业务请求我用curlcurl -I https://your.server.com/返回200状态码说明业务没被影响。如果你的业务有移动端APP有些APP内置的HTTP库对ECDHE支持不好修复后建议让测试同事在低版本Android/iOS手机上各点一遍确认TOP页面正常打开再安排上线。这一步看起来麻烦但比修完后第2天收到业务方“APP打不开”的投诉要省心十倍。4. 常见问题与排查技巧实录这个漏洞的修复流程本身不长但我在不同项目里处理过非常多次真正常见的问题其实集中在下面几个地方。4.1 为什么配置改了好几个小时重新扫描还是报漏洞这是我最常被问到的问题。碰到这类情况第一步不是怀疑修复没生效而是从缓存和分层两个角度排查。很多漏洞扫描器会在扫描结果里对同一台主机做缓存重复扫描时如果目标开放的端口没有变化扫描器可能直接复用上次的结果并不真正重新探测。这种情况最省事的验证办法就是我2.2节里讲的自己用openssl命令先确认一下如果openssl命令已经显示RSA套件被拒绝而扫描器还在报那基本就是扫描器缓存或扫描策略配置的问题可以直接把手工验证记录作为补充材料提交到漏洞管理平台消单。另外一种更隐蔽的情况是改的是Nginx配置但前面还挡着一层负载均衡器或防火墙扫描器扫描的IP是负载均衡器的IP负载均衡器上仍然保留着旧的TLS套件。如果整个链路是“SLB → Nginx → 后端应用”那就要SLB、Nginx两层都要处理只处理一层不解决问题。所以排查时一定要先确认扫描器扫的是哪个IP、哪个端口再顺着这个入口一路排查到最末端。4.2 修复后某些老客户端连不上了要不要回退这个问题在政企类项目里特别常见。有些单位内部有一个特别老的收费系统或报表系统客户端是N年前开发的只支持TLS 1.0和RSA密钥交换服务器升级以后这些老客户端无法连通了。我的建议分三种情况处理。第一种老客户端可以升级或替换那不用犹豫直接升级客户端禁用RSA套件。第二种老客户端暂时无法替换但访问量不大、只在内网特定网段使用可以在服务器的防火墙上把对应网段的访问单独拉出来给这些老客户端用的IP单独监听一个端口保留老套件同时限制只有这些IP能访问该端口而对外服务的IP则保持新配置。值得再次强调的是网络侧操作请务必结合自身合规要求来判断这里我只是描述一种通用技术方案。第三种系统已经老旧到无法打补丁、无法调整客户端这种属于高风险遗留系统不是改几行配置能解决的需要作为专项向上汇报推动系统升级改造。这种系统放在公网上就是在赌运气迟早要出事。4.3 扫描器还报了“SSL/TLS协议信息泄露漏洞(CVE-2016-2183)”要不要一起处理CVE-2016-2183SWEET32在扫描报告里出现的频率非常高而且经常和RSA密钥交换漏洞同时出现。这个漏洞针对的是3DES加密算法CBC模式它的问题在于3DES的64位分组长度在传输大量数据时会产生碰撞理论上有被破解的可能。修复方式和RSA密钥交换类似同样是修改ssl_ciphers把包含DES、3DES、RC4的套件全部去掉。我给出的Nginx和Apache配置示例里已经是“把RSA密钥交换、3DES、RC4全部关掉”的组合配置所以如果你按我上面给的配置改通常两个漏洞能一起消掉。这也是为什么我处理这类问题时喜欢用一份“干净”的套件清单而不是只去掉某一个套件——反正现代客户端支持的套件就那几个与其留着一堆名声不好的旧套件不如直接上最小化配置。4.4 为什么我改完配置后Nmap显示还有TLS_RSA_WITH_3DES_EDE_CBC_SHA这个套件这种情况大概率是Nginx或Apache配置里ssl_ciphers确实改了但连接TLS 1.0/1.1的协议没禁用。TLS 1.0/1.1的套件集合和TLS 1.2不完全一样有些老套件只在TLS 1.0/1.1里生效。比如TLS_RSA_WITH_3DES_EDE_CBC_SHA在TLS 1.0和TLS 1.1里都还有如果只改了ssl_ciphers而没把ssl_protocols改成只留TLSv1.2/TLSv1.3扫描器用TLS 1.0探测时照样能扫到。所以我处理时总是强调一句协议和套件要一起改光改套件不限制协议等于只堵了正门没堵侧门。4.5 弱DH参数、证书太小这类“邻居问题”别忽略扫描报告里如果同时出现“TLS协议定义弱DH参数”或“证书密钥过小”之类的问题不要当作不相干的两件事分开处理。RSA密钥交换漏洞的相反面除了前向保密还有密钥强度。如果服务器的RSA私钥长度低于2048位比如1024位即使把RSA密钥交换禁了攻击者依然可能通过破解私钥来冒充服务器这就是另一条攻击路径。这个我建议做到“一把梭”修改套件的同时重新生成2048位以上的RSA私钥和证书如果证书是在阿里云或腾讯云申请的直接升级到2048/3072位免费证书默认就是2048位。同时检查DH参数如果配置里用了ssl_dhparam用如下命令重新生成一个2048位DH参数文件openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048生成文件后在Nginx配置里引用它ssl_dhparam /etc/nginx/ssl/dhparam.pem;再配合高强度的DH参数最低位数设置ssl_ecdh_curve secp384r1;这几行配置加上后常见扫描报告里的加密相关问题基本能消掉一大半。5. 最后把这个漏洞从“扫出来”到“改完”的完整流程串一遍我这里给一个可以直接拿去当操作SOP用的全流程清单团队里新人照着做也基本不会跑偏。拿到扫描报告确认漏洞名称是“目标主机支持RSA密钥交换【原理扫描】”记录IP和端口核对资产归属。用Nmap的ssl-enum-ciphers脚本跑一遍确认影响面哪些端口、哪些TLS版本。用openssl s_client -cipher RSA验证当前握手是否真的支持TLS_RSA套件。按业务实际情况选择Nginx/Apache/Tomcat/负载均衡器的修改方式把ssl_ciphers和ssl_protocols一起改掉。修改后测试配置文件nginx -t、apachectl configtest通过后执行reload/restart。用openssl和curl做相互验证现代套件能握上、RSA套件被拒绝、首页能正常打开。重新跑扫描器或Nmap对比修复前后输出确认该漏洞项已被消除。如果同时存在CVE-2016-2183等加密相关告警确认新配置里同时去掉了3DES、RC4等过时套件。修改漏洞管理平台状态附上验证记录同步到相关人员做复核。6. 个人实操中的两点小体会最后聊点实操层面的感受。我在处理这类漏洞时最开始也走过弯路拿到报告就闷头改配置改完让扫描器一跑发现另外一个端口又报了然后又去改来回折腾了三四轮后来才发现其实是自己没做全端口排查只盯着443。所以我现在处理任何加密套件类的告警第一件事永远是Nmap全端口扫一遍把目标机器上所有监听TLS服务的端口全部摸清楚再决定从哪里开始改。这个习惯帮我少走了很多弯路。另一个体会是修复加密套件这类告警不要只抱着“消漏洞”的心态而是顺手把服务器的TLS配置整体梳理一遍。很多服务器上的TLS配置是多年“打补丁”打出来的这里加一行那里注释一行早就不成样子了。每次处理这类告警我都会顺手清理一下配置文件把协议、套件、DH参数、证书强度全部统一规范起来。后续再做扫描时加密相关的问题通常就不会反复出现了。加密套件这种东西一次性把规矩立好后面真的能省很多事。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进