OpenSSL-TLS重协商

本文深入探讨了TLS连接中的重协商过程,包括重协商的定义、发起方式及安全问题。针对DoS攻击和中间人攻击,讨论了防御策略。重点介绍了OpenSSL 1.1.0f中的重协商实现,包括安全重协商功能和配置策略,强调了安全性和配置选项的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、什么是重协商

    大部分TLS连接都以handshake为开始,经过应用数据的交换,最后关闭会话。如果在第一次handshake之后(可能经历了应用数据的交换也可能没有)请求重新协商,就会发起一次新的handshake,对新的安全参数达成一致。重协商的handshake的消息都是全部加密的,这与第一次handshake明显不同。

    重协商功能应用场景举例:

*) Client证书:可以设置访问网站的根路径不要求client携带证书,而在client访问特定子区域时server发起重协商请求,要求client携带证书;

*) 隐藏消息:由于重协商的handshake消息是加密的,被动攻击者无法监视协商过程,这样就可以隐藏一些敏感信息(比如证书中包含的身份识别信息)。

二、怎样发起重协商

    有两种方式可以发起重协商:

*)Client发起:TLS协议允许client在任意时间简单地发送新的ClientHello消息请求重新协商,就如同建立一个新的连接一样;

*)Server发起:如果server希望重新协商,它会发送HelloRequest消息给client,这个消息通知client停止发送应用数据,并开始新的handshake。

三、重协商的安全性

    重协商机制并不安全,针对重协商的攻击类型如下:

3.1 DoS攻击

    TLS的handshake过程需要使用非对称算法进行身份认证和密钥协商,这个过程需要很多计算资源。Handshake本来只在TLS连接开始建立时执行一次,但由于重协商机制的引入,使得client被允许不断发起新的handshake。由于client可以使用较少的资源来执行handshake(比如:不检查server的证书,这样可以避免校验签名的开销),这样攻击者就可以更容易地耗尽server的资源导致其拒绝为其它用户的请求提供服务。

         这种攻击与分布式拒绝服务攻击(DDoS)的不同之处在于,它不需要大量的攻击来消耗网络带宽,而仅仅通过一台主机的一个TCP/IP socket来耗尽server的资源(这样就会导致当前的DoS和DDoS防御策略无效)。例如,一台server通常能执行150-300次/s握手,而一个client可以发起多达1000次/s握手请求。

         防御方法:

1)  禁用重协商功能:不推荐,因为这样会导致依赖重协商的特性无法使用;

2)  禁止client发起重协商:目前看来似乎是个不错的选择;

3)  速率限制:对新到来的TLS连接和重协商的速率进行限制;

4)  使用SSL加速卡:通过极大地提高server对handshake的处理能力来增加攻击的成本,但可能攻击者只增加一到两台主机进行攻击就可以使得此措施无效。

3.2 中间人攻击

    由于TLS的重协商前后的两条TLS连接之间没有关联(即使它们发生在同一条TCP连接上),而且应用层(如HTTP)与加密层很少交互(例如,如果重协商发生在HTTP请求的过程中,上层应用是得不到通知的),导致TLS层面发生的事情与上层应用了解到的信息不匹配。

    因此,一个中间人(man-in-the-middle,MITM)攻击者就可以通过如下步骤来利用这个漏洞:

1)  拦截一个client到server的TCP连接,截住其TLS handshake请求;

2)  新建一个到server的TLS连接,在handshake之后发送攻击负载;

3)  将1)中拦截的handshake请求通过与server的TLS连接发送过去,这样在server看来是重协商,而在client看来是一条全新的TLS连接。一旦重协商完成,client与server开始交换应用层数据,攻击者的攻击负载和client的正常数据就会被server合并处理,从而使得攻击成功。

    攻击过程(举例)的示意图如下:


    这种攻击会使得server执行攻击者制定的任意GET请求。

    对于这种MITM攻击,即使禁止了client发起重协商,依赖于client证书校验和支持SGC的网站仍然容易遭到攻击。因为攻击者只需要调查网站在哪些情况下是需要进行重协商的,如果条件得到满足则攻击者就可以开展攻击行为。

    防御方法:

1)禁用重协商功能:不推荐,除了会使得依赖重协商的特性无法使用外,还会导致增加了网络上重协商功能的不确定性,使得client无法有效保护自己【注1】

2)使用“安全重协商”功能:通过关联重协商前后的TLS连接来阻止非法数据注入;详见第四节。

【注1】:重协商的安全缺陷对client的威胁在于:攻击者可以通过控制服务器来攻击与之通信的client。由于在攻击发生时client并未参与到重协商的过程中,故对于client唯一可行的保护自己的方法就是只于支持安全重协商的server建立连接。对于禁用了重协商功能的server,client不希望自己无法连接它们,但client无法区分server是禁用了重协商还是不支持安全重协商。所以server禁用重协商的行为会导致client很难使用有效的方法来保护自己。

四、安全重协商

    为了解决中间人攻击的问题,【RFC5764】提出了“安全重协商”机制。本质很简单,就是关联两次握手,方式是提供了一个新的扩展(renegotiation_info)。SSLv3/TLS 1.0不支持扩展,为了使其支持安全重协商,client需要发送TLS_EMPTY_RENEGOTIATION_INFO_SCSV(0xFF)密码套件(缩写为SCSV)

安全重协商的流程如下:

1) 在某个连接的第一次握手期间,双方通过renegotiation_info扩展或SCSV套件通知对方自己支持安全重协商;

2) 在handshake过程中,client和server都分别记录Finish消息之中的client_verify_data和server_verify_data;

3)重协商时client在ClientHello中包含client_verify_data,server在ServerHello中包含client_verify_data和server_verify_data。对于受害者,如果协商中不会携带这些数据则连接无法建立。由于Finished消息总是加密的,攻击者无法得到client_verify_data和server_verify_data的值。

五、OpenSSL中的重协商(基于OpenSSL-1.1.0f)

5.1 发起重协商

5.1.1 SSL_renegotiate

         Client和server只需调用SSL_renegotiate(ssl)函数即可完成发起重协商的设置。SSL_renegotiate()函数定义如下:

1641 int SSL_renegotiate(SSL *s)
1642 {
1643     if (s->renegotiate == 0)
1644         s->renegotiate = 1;
1645
1646     s->new_session = 1; 
1647
1648     return (s->method->ssl_renegotiate(s));
1649 }
         对于TLS_client_method()和TLS_server_method(),s->method->ssl_renegotiate指向ssl3_renegotiate():
3865 int ssl3_renegotiate(SSL *s)
3866 {
3867     if (s->handshake_func == NULL)
3868         return (1);
3869
3870     if (s->s3->flags &SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS)
3871         return (0);
3872
3873     s->s3->renegotiate = 1;
3874     return (1);
3875 }
     可见,SSL_renegotiate()函数只是将s->s3->renegotiate设置为1而已,并不是发送重协商报文(Handshake,HelloRequest)。发送重协商报文是在SSL_write()或SSL_read()函数被调用的时候进行的:

5.1.2 发送第一个消息

         先来看SSL_write()函数。对于TLS_client_method()和TLS_server_method(),SSL_write()最终都会调用ssl3_write()函数:

3816 int ssl3_write(SSL *s, const void *buf, int len)
3817 {
3818     clear_sys_error();
3819     if (s->s3->renegotiate)
3820         ssl3_renegotiate_check(s);     
3821
3822     return s->method->ssl_write_bytes(s,SSL3_RT_APPLICATION_DATA, buf, len);
3823 }
         由于之前调用的SSL_renegotiate()函数将s->s3->renegotiate设置为1,故会在3820行调用到ssl3_renegotiate_check()函数:
3877 int ssl3_renegotiate_check(SSL *s)
3878 {
3879     int ret = 0;
3880
3881     if (s->s3->renegotiate) {
3882         if(!RECORD_LAYER_read_pending(&s->rlayer)
3883             &&!RECORD_LAYER_write_pending(&s->rlayer)
3884             && !SSL_in_init(s)) {         
3885             /*
3886              * if we are the server, and wehave sent a 'RENEGOTIATE'
3887              * message, we need to set thestate machine into the renegotiate
3888              * state.
3889              */
3890             ossl_statem_set_renegotiate(s);
3891             s->s3->renegotiate = 0;       
3892             s->s3->num_renegotiations++;  
3893            s->s3->total_renegotiations++;
3894             ret = 1;
3895         }
3896     }
3897     return (ret);
3898 }
         其中的关键代码是3890行ossl_statem_set_renegotiate()函数:
103 /*      
104  * Set the state machine up ready for arenegotiation handshake
105  */ 
106 void ossl_statem_set_renegotiate(SSL *s)
107 {
108     s->statem.state = MSG_FLOW_RENEGOTIATE;
109     s->statem.in_init = 1;
110 }
    调用完ssl3_renegotiate_check()函数之后,ssl3_write()会调用s->method->ssl_write_bytes指向的ssl3_write_bytes()函数:
343 int ssl3_write_bytes(SSL *s, int type, const void *buf_, int len)
344 {
…
379     if (SSL_in_init(s) &&!ossl_statem_get_in_handshake(s)) {
380         i = s->handshake_func(s);
381         if (i < 0)
382             return (i);
383         if (i == 0) {
384             SSLerr(SSL_F_SSL3_WRITE_BYTES,SSL_R_SSL_HANDSHAKE_FAILURE);
385             return -1;
386         }
387     }
…
    其中SSL_in_init(s)的返回值会是1:
69 int SSL_in_init(SSL *s)
70 {   
71     return s->statem.in_init;
72 }
         由于是在handshake结束之后调用,故ossl_statem_get_in_handshake(s)的返回值会是0:

141 int ossl_statem_get_in_handshake(SSL *s)
142 {   
143     return s->statem.in_handshake;
144 }
         故ssl3_write_bytes()会执行380行s->handshake_func(s)。

         再来看SSL_read()。对于TLS_client_method()和TLS_server_method(),这个函数最终会调用ssl3_read():

3825 static int ssl3_read_internal(SSL *s, void *buf, int len, int peek)
3826 {  
3827     int ret;            
3828    
3829     clear_sys_error();
3830     if (s->s3->renegotiate)
3831         ssl3_renegotiate_check(s);
3832     s->s3->in_read_app_data = 1;
3833     ret =
3834         s->method->ssl_read_bytes(s,SSL3_RT_APPLICATION_DATA, NULL, buf, len,
3835                                   peek);
3836     if ((ret == -1) &&(s->s3->in_read_app_data == 2)) {
3837         /*
3838          * ssl3_read_bytes decided to calls->handshake_func, which called
3839          * ssl3_read_bytes to read handshakedata. However, ssl3_read_bytes
3840          * actually found application data andthinks that application data
3841          * makes sense here; so disablehandshake processing and try to read
3842          * application data again.
3843          */
3844         ossl_statem_set_in_handshake(s, 1);
3845         ret =
3846             s->method->ssl_read_bytes(s,SSL3_RT_APPLICATION_DATA, NULL, buf,
3847                                       len,peek);
3848         ossl_statem_set_in_handshake(s, 0);
3849     } else
3850         s->s3->in_read_app_data = 0;
3851    
3852     return (ret);
3853 }
3854
3855 int ssl3_read(SSL *s, void *buf, int len)
3856 {
3857     return ssl3_read_internal(s, buf, len, 0);
3858 }
         调用SSL_renegotiate()后3831行会被执行,其影响见上文对SSL_write()函数的分析。s->method->ssl_read_bytes()指向ssl3_read_bytes():

975 int ssl3_read_bytes(SSL *s, int type, int *recvd_type, unsigned char *buf,
976                     int len, int peek)            
977 {
…
1029     if (!ossl_statem_get_in_handshake(s)&& SSL_in_init(s)) {
1030         /* type == SSL3_RT_APPLICATION_DATA */
1031         i = s->handshake_func(s);
1032         if (i < 0)
1033             return (i);
1034         if (i == 0) {
1035             SSLerr(SSL_F_SSL3_READ_BYTES,SSL_R_SSL_HANDSHAKE_FAILURE);
1036             return (-1);
1037         }
1038     }
…
    最后SSL_read()会执行1031行代码。可见在调用SSL_renegotiate()开启协商功能后,SSL_write()和SSL_read()都会调用s->handshake_func(s),对于client会调用到ossl_statem_connect:

168 int ossl_statem_connect(SSL *s)
169 {
170     return state_machine(s, 0);   
171 }
         对于server则会调用ossl_statem_accept():
173 int ossl_statem_accept(SSL *s)
174 {                        
175     return state_machine(s, 1);   
176 }
         它们都会调用state_machine():
218 static int state_machine(SSL *s, int server)
219 {
…
276     if (st->state == MSG_FLOW_UNINITED ||st->state == MSG_FLOW_RENEGOTIATE) {
277         if (st->state == MSG_FLOW_UNINITED){
278             st->hand_state =TLS_ST_BEFORE;                                                                                                                                                            
279         }
280
281         s->server = server;
…
380         st->state = MSG_FLOW_WRITING;
381         init_write_state_machine(s);
382         st->read_state_first_init = 1;
383     }
384
385     while (st->state != MSG_FLOW_FINISHED){
386         if (st->state == MSG_FLOW_READING){
387             ssret = read_state_machine(s);
388             if (ssret == SUB_STATE_FINISHED) {
389                 st->state =MSG_FLOW_WRITING;
390                 init_write_state_machine(s);
391             } else {
392                 /* NBIO or error */
393                 goto end;
394             }
395         } else if (st->state ==MSG_FLOW_WRITING) {
396             ssret = write_state_machine(s);
397             if (ssret == SUB_STATE_FINISHED) {
398                 st->state =MSG_FLOW_READING;
399                 init_read_state_machine(s);
400             } else if (ssret ==SUB_STATE_END_HANDSHAKE) {
401                 st->state =MSG_FLOW_FINISHED;
402             } else {
403                 /* NBIO or error */
404                 goto end;
405             }
406         } else {
407             /* Error */
408             ossl_statem_set_error(s);
409             goto end;
410         }
411     }
412
413     st->state = MSG_FLOW_UNINITED;
414     ret = 1;
         由于执行了380行,故396行write_state_machine()会执行:
704 static SUB_STATE_RETURN write_state_machine(SSL *s)
705 {
706     OSSL_STATEM *st = &s->statem;
707     int ret;
708     WRITE_TRAN(*transition) (SSL *s);
709     WORK_STATE(*pre_work) (SSL *s, WORK_STATEwst);
710     WORK_STATE(*post_work) (SSL *s, WORK_STATEwst);
711     
<think>我们正在解决一个具体问题:用户使用OpenSSL s_client指定TLS 1.3连接服务器,但实际抓包显示使用的是TLS 1.0,而浏览器连接同一服务器却使用了TLS 1.3。用户想知道原因以及如何解决。 可能的原因分析: 1. OpenSSL版本过低:较旧的OpenSSL版本(如1.0.x)不支持TLS 1.3,即使指定了`-tls1_3`参数也会回退到旧协议。 2. 参数使用错误:用户可能使用了错误的参数指定TLS版本。例如,使用`-tls1`表示TLS 1.0,而TLS 1.3应该使用`-tls1_3`。 3. 服务器配置:服务器可能不支持TLS 1.3,但根据浏览器连接成功使用TLS 1.3,说明服务器是支持的。因此问题更可能出在客户端。 4. 网络中间件干扰:可能存在中间代理(如公司防火墙)强制降级了TLS版本。 解决步骤: 1. **确认OpenSSL版本**: 运行命令: ```bash openssl version ``` 如果版本低于1.1.1(TLS 1.3在OpenSSL 1.1.1中引入),则需要升级OpenSSL。 2. **正确使用s_client命令**: 确保使用正确的参数: - 对于TLS 1.3:`-tls1_3` - 对于TLS 1.2:`-tls1_2` 示例命令: ```bash openssl s_client -connect aivisionxskino.com:443 -tls1_3 ``` 3. **检查服务器支持的协议**: 使用以下命令测试服务器支持的协议版本: ```bash # 测试TLS 1.0 openssl s_client -connect aivisionxskino.com:443 -tls1 # 测试TLS 1.1 openssl s_client -connect aivisionxskino.com:443 -tls1_1 # 测试TLS 1.2 openssl s_client -connect aivisionxskino.com:443 -tls1_2 # 测试TLS 1.3(需要OpenSSL 1.1.1+) openssl s_client -connect aivisionxskino.com:443 -tls1_3 ``` 4. **排除中间件干扰**: 使用抓包工具(如Wireshark)分析连接过程,观察ClientHello消息中客户端提供的协议版本以及服务器返回的协议版本。如果发现ClientHello中客户端声明的是TLS 1.0,那么可能是命令参数使用错误或OpenSSL版本问题。如果客户端声明的是TLS 1.3,但服务器回复了TLS 1.0,则可能是服务器不支持TLS 1.3(但浏览器连接成功说明服务器支持,所以这种情况不太可能)或者中间有设备进行了拦截。 5. **升级OpenSSL**: 如果OpenSSL版本过低,需要升级。升级方法取决于操作系统。 例如,在Ubuntu上: ```bash sudo apt update sudo apt install openssl ``` 6. **使用其他工具验证**: 使用curl命令测试TLS 1.3连接: ```bash curl --tlsv1.3 -Iv https://aivisionxskino.com ``` 如果curl可以成功使用TLS 1.3,则进一步确认是OpenSSL的问题。 7. **检查OpenSSLTLS 1.3支持**: 即使OpenSSL版本是1.1.1以上,也可能在编译时禁用了TLS 1.3。可以通过以下命令检查支持的协议: ```bash openssl ciphers -v | awk '{print $2}' | sort | uniq ``` 查看输出中是否有TLSv1.3。 8. **环境变量影响**: 检查是否有环境变量(如`OPENSSL_CONF`)指向了一个旧的配置文件,该配置文件可能禁用了TLS 1.3。 根据用户描述,抓包显示为TLS 1.0,而指定的是TLS 1.3,最可能的原因是: - 命令参数错误:用户可能使用了`-tls1`(代表TLS 1.0)而不是`-tls1_3`。 - OpenSSL版本过低,不支持TLS 1.3,因此即使指定了`-tls1_3`也会被忽略。 因此,重点检查命令参数和OpenSSL版本。 示例正确命令: ```bash openssl s_client -connect aivisionxskino.com:443 -tls1_3 ``` 如果问题仍然存在,请提供以下信息: - OpenSSL版本 - 完整的命令和输出 - 抓包文件分析(可选) 但根据用户问题,我们推测是参数使用错误或版本问题。 注意:在原始问题中,用户使用的命令是: openssl s_client -connect aivisionxskino.com:443 -tls1 这里`-tls1`指定的是TLS 1.0,所以实际连接使用TLS 1.0是符合的。而用户想要使用TLS 1.3,应该改为`-tls1_3`。 因此,解决方案是:使用正确的参数`-tls1_3`。 如果用户已经使用了`-tls1_3`,但连接仍为TLS 1.0,则说明OpenSSL版本不支持TLS 1.3,需要升级。</think>### 解决 OpenSSL s_client 指定 TLS 1.3 却使用 TLS 1.0 的问题 #### 问题原因分析 1. **OpenSSL 版本兼容性问题** TLS 1.3 需要 OpenSSL 1.1.1 或更高版本支持。若版本低于此: - 指定 `-tls1_3` 会被忽略 - 自动回退到 TLS 1.0(默认行为) - 验证版本:`openssl version` 2. **参数使用错误** - `-tls1` 表示 TLS 1.0(旧语法) - TLS 1.3 需使用 `-tls1_3`(新语法) 3. **服务器优先策略** 即使客户端请求 TLS 1.3,若服务器配置强制使用 TLS 1.0: - 服务器可能忽略 ClientHello 的协议版本 - 常见于老旧服务器配置 4. **网络中间件干扰** 代理或防火墙可能强制协议降级: ```bash # 检查是否有代理 echo $http_proxy echo $https_proxy ``` --- ### 解决方案 #### 1. 升级 OpenSSL 版本 ```bash # Ubuntu/Debian sudo apt update && sudo apt install openssl libssl-dev # CentOS/RHEL sudo yum update openssl openssl-devel ``` 验证版本: $ \text{需满足:} \quad \text{OpenSSL 版本} \geq 1.1.1 $ #### 2. 使用正确的 TLS 1.3 参数 ```bash # 正确语法(注意下划线) openssl s_client -connect aivisionxskino.com:443 -tls1_3 ``` #### 3. 强制仅使用 TLS 1.3 禁用旧协议防止回退: ```bash openssl s_client -connect aivisionxskino.com:443 \ -tls1_3 -no_tls1_2 -no_tls1_1 -no_tls1 ``` #### 4. 验证服务器支持情况 使用在线工具检测服务器协议支持: ```bash # 使用 testssl.sh 工具(推荐) git clone https://github.com/drwetter/testssl.sh cd testssl.sh ./testssl.sh aivisionxskino.com:443 ``` 输出示例: ``` Testing protocols via sockets except NPN+ALPN TLSv1 offered (deprecated) TLSv1.1 offered (deprecated) TLSv1.2 offered TLSv1.3 offered ✔ ``` #### 5. 抓包分析握手过程 使用 Wireshark 过滤: ``` tcp.port == 443 && ssl.handshake.type == 1 ``` 检查关键字段: - **ClientHello**:`Version: 0x0304 (TLS 1.3)` - **ServerHello**:`Version: 0x0301 (TLS 1.0)` ← 服务器降级标志 #### 6. 服务器配置检查 若服务器强制使用 TLS 1.0,需修改配置: ```nginx # Nginx 配置示例 ssl_protocols TLSv1.2 TLSv1.3; # 启用 TLS 1.3 ssl_prefer_server_ciphers off; # 允许客户端协商 ``` --- ### 典型成功输出 ```bash $ openssl s_client -connect example.com:443 -tls1_3 CONNECTED(00000003) SSL-Session: Protocol : TLSv1.3 Cipher : TLS_AES_256_GCM_SHA384 Session-ID: A3B5... Verify return code: 0 (ok) ``` --- ### 根本原因对比 | 现象 | 可能原因 | 解决方案 | |---------------------|--------------------------|-----------------------------| | 抓包显示 TLS 1.0 | OpenSSL 版本过低 | 升级到 OpenSSL ≥1.1.1 | | 浏览器可用 TLS 1.3 | 参数错误 (`-tls1` vs `-tls1_3`) | 修正命令行参数 | | 服务器拒绝连接 | 服务器强制旧协议 | 修改服务器 SSL 配置 | | 间歇性失败 | 中间件干扰 | 检查代理/防火墙规则 | > 注:根据 SSL Labs 数据,全球约 92.5% 的服务器已支持 TLS 1.3,但错误配置仍会导致回退[^1]。 ---
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值