From f51c6989b008a4293e2fa0a18c10290499bf020a Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Fri, 25 Apr 2014 20:02:39 +0200 Subject: [PATCH] MEDIUM: ssl: implement a workaround for the OpenSSL heartbleed attack Using the previous callback, it's trivial to block the heartbeat attack, first we control the message length, then we emit an SSL error if it is out of bounds. A special log is emitted, indicating that a heartbleed attack was stopped so that they are not confused with other failures. That way, haproxy can protect itself even when running on an unpatched SSL stack. Tests performed with openssl-1.0.1c indicate a total success. --- include/proto/connection.h | 1 + include/types/connection.h | 3 ++- src/ssl_sock.c | 33 ++++++++++++++++++++++++++++++--- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/include/proto/connection.h b/include/proto/connection.h index dde9b8c7e5..10bc4d8ac3 100644 --- a/include/proto/connection.h +++ b/include/proto/connection.h @@ -582,6 +582,7 @@ static inline const char *conn_err_code_str(struct connection *c) case CO_ER_SSL_CRT_FAIL: return "SSL client certificate not trusted"; case CO_ER_SSL_HANDSHAKE: return "SSL handshake failure"; case CO_ER_SSL_HANDSHAKE_HB: return "SSL handshake failure after heartbeat"; + case CO_ER_SSL_KILLED_HB: return "Stopped a TLSv1 heartbeat attack (CVE-2014-0160)"; case CO_ER_SSL_NO_TARGET: return "Attempt to use SSL on an unknown target (internal error)"; } return NULL; diff --git a/include/types/connection.h b/include/types/connection.h index 84248c98ec..83ac432b34 100644 --- a/include/types/connection.h +++ b/include/types/connection.h @@ -163,7 +163,8 @@ enum { CO_ER_SSL_CRT_FAIL, /* client cert verification failed on the certificate */ CO_ER_SSL_HANDSHAKE, /* SSL error during handshake */ CO_ER_SSL_HANDSHAKE_HB, /* SSL error during handshake with heartbeat present */ - CO_ER_SSL_NO_TARGET, /* unkonwn target (not client nor server) */ + CO_ER_SSL_KILLED_HB, /* Stopped a TLSv1 heartbeat attack (CVE-2014-0160) */ + CO_ER_SSL_NO_TARGET, /* unknown target (not client nor server) */ }; /* source address settings for outgoing connections */ diff --git a/src/ssl_sock.c b/src/ssl_sock.c index 83c75f1d25..b202ea57d7 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -190,8 +190,33 @@ void ssl_sock_msgcbk(int write_p, int version, int content_type, const void *buf #ifdef TLS1_RT_HEARTBEAT /* test heartbeat received (write_p is set to 0 for a received record) */ - if ((content_type == TLS1_RT_HEARTBEAT) && (write_p == 0)) + if ((content_type == TLS1_RT_HEARTBEAT) && (write_p == 0)) { + const unsigned char *p = buf; + unsigned int payload; + conn->xprt_st |= SSL_SOCK_RECV_HEARTBEAT; + + /* Check if this is a CVE-2014-0160 exploitation attempt. */ + if (*p != TLS1_HB_REQUEST) + return; + + if (len < 1 + 2 + 16) + goto kill_it; + + payload = (p[1] * 256) + p[2]; + if (1 + 2 + payload + 16 <= len) + return; /* OK no problem */ + kill_it: + /* we have a clear heartbleed attack (CVE-2014-0160), + * we can't know if the SSL stack is patched, so better + * kill the connection before OpenSSL tries to send the + * bytes back to the attacker. It will be reported above + * as SSL_ERROR_SSL while an other handshake failure with + * a heartbeat message will be reported as SSL_ERROR_SYSCALL. + */ + SSLerr(SSL_F_TLS1_HEARTBEAT, SSL_R_SSL_HANDSHAKE_FAILURE); + return; + } #endif } @@ -1443,7 +1468,8 @@ int ssl_sock_handshake(struct connection *conn, unsigned int flag) */ conn_drain(conn); if (!conn->err_code) - conn->err_code = CO_ER_SSL_HANDSHAKE; + conn->err_code = (conn->xprt_st & SSL_SOCK_RECV_HEARTBEAT) ? + CO_ER_SSL_KILLED_HB : CO_ER_SSL_HANDSHAKE; goto out_error; } } @@ -1508,7 +1534,8 @@ int ssl_sock_handshake(struct connection *conn, unsigned int flag) */ conn_drain(conn); if (!conn->err_code) - conn->err_code = CO_ER_SSL_HANDSHAKE; + conn->err_code = (conn->xprt_st & SSL_SOCK_RECV_HEARTBEAT) ? + CO_ER_SSL_KILLED_HB : CO_ER_SSL_HANDSHAKE; goto out_error; } } -- 2.39.5