协议版本降级攻击:防止 POODLE、BEAST 等攻击,强制使用高版本 TLS
说实话,协议版本降级攻击这玩意儿,是我在移动安全领域里见过最「阴」的攻击方式之一。攻击者不直接破解你的加密,而是想办法让你自己主动降低安全标准。你想想看,这就像骗你把防盗门换成木门,然后再来撬锁。
我当年在做一个金融类 App 的时候,就遇到过类似的问题。测试团队用了一个很老的抓包工具,结果发现我们的 App 居然还能跟服务器协商出 SSLv3 协议。我当时后背一凉——这要是被中间人攻击了,用户的交易密码不就等于裸奔了吗?
什么是协议版本降级攻击?
简单来说,就是攻击者利用客户端和服务器之间的「版本协商」机制,强行让双方使用一个更老、更脆弱的协议版本。
正常的 TLS 握手是这样的:客户端说「我支持 TLS 1.3、1.2、1.1」,服务器说「好,我们用 1.3」。但攻击者可以篡改握手消息,让服务器以为客户端只支持 SSLv3,于是双方就降级到了 SSLv3。
为什么要这么做?因为老版本协议有已知漏洞。比如:
- POODLE 攻击(Padding Oracle On Downgraded Legacy Encryption):针对 SSLv3 的填充预言攻击,能逐字节破解加密内容
- BEAST 攻击(Browser Exploit Against SSL/TLS):针对 TLS 1.0 的 CBC 模式攻击,能解密 Cookie 等敏感数据
- Lucky 13 攻击:同样是针对 CBC 模式的时序侧信道攻击
攻击流程拆解
我画了一张图,帮你理清攻击者的操作路径。你看完就明白为什么降级攻击这么难防了。
你看,攻击者其实只做了一件事——篡改握手消息。但后果是整个加密通道形同虚设。
Android 端如何防御?
在 Android 上,我们有几个层次可以加固。我个人习惯是从代码层面和配置层面双管齐下。
1. 强制最低 TLS 版本
从 Android 4.1(API 16)开始,我们可以直接设置 SSLSocket 的 enabled protocols。但要注意,不同 Android 版本默认支持的协议不一样。
// 强制使用 TLS 1.2 及以上
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, null, null)
val socket = sslContext.socketFactory.createSocket(host, port) as SSLSocket
// 只启用 TLS 1.2 和 1.3
socket.enabledProtocols = arrayOf("TLSv1.2", "TLSv1.3")
// 或者更激进一点,只保留 TLS 1.3
// socket.enabledProtocols = arrayOf("TLSv1.3")
2. 使用 Network Security Config
Android 7.0(API 24)之后,推荐用 XML 配置来管理网络安全策略。这种方式比代码更安全,因为不会被混淆或反编译篡改。
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">api.yourbank.com</domain>
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</domain-config>
<base-config>
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
</network-security-config>
但注意,这个配置只能控制证书信任,不能直接限制协议版本。所以还需要配合代码。
3. 禁用不安全的密码套件
除了协议版本,密码套件也是攻击目标。比如 POODLE 攻击就依赖于 SSLv3 的 CBC 模式密码套件。
// 禁用不安全的密码套件
val allowedCiphers = arrayOf(
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
)
socket.enabledCipherSuites = allowedCiphers
服务端也要配合
光客户端加固是不够的。如果服务器还支持 SSLv3 或 TLS 1.0,攻击者可以直接跟服务器握手,绕过客户端限制。
我建议服务端做以下配置(以 Nginx 为例):
# 只启用 TLS 1.2 和 1.3
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';
ssl_prefer_server_ciphers on;
检测与验证
配置完了,怎么知道有没有生效?我一般用这几个方法:
| 检测方法 | 工具 | 说明 |
|---|---|---|
| 抓包验证 | Wireshark / tcpdump | 查看 ClientHello 和 ServerHello 中的协议版本字段 |
| 在线扫描 | SSL Labs / testssl.sh | 检测服务器支持的协议和密码套件 |
| 客户端日志 | Android Logcat | 打印 SSLSocket 的 enabled protocols |
| 自动化测试 | 自定义脚本 | 尝试用低版本协议连接,验证是否被拒绝 |
核心要点总结:
- 协议降级攻击的本质是攻击者篡改握手消息,诱导双方使用低版本协议
- POODLE、BEAST、Lucky 13 都是针对低版本协议或特定密码套件的攻击
- Android 端要同时限制协议版本和密码套件,不能只做一半
- 服务端必须同步禁用低版本协议,否则客户端加固等于白做
- 每次修改后都要用工具验证,不要相信「我觉得没问题」
嗯,关于协议降级攻击就讲到这里。说实话,这类攻击在移动端越来越少见,因为 Android 系统本身已经逐步移除了对 SSLv3 和 TLS 1.0 的支持。但如果你要兼容老旧系统,或者对接第三方服务,就一定要把这些检查做到位。我见过太多「理论上没问题,实际上被攻破」的案例了。