From 2b464774f240a18e1ff43cd18938a72a2d042e1f Mon Sep 17 00:00:00 2001 From: Meng-Hsiu Chiang Date: Fri, 23 Feb 2024 19:40:13 +0000 Subject: [PATCH] MDEV-33834 Extend audit plugin to include tls_version and tls_version_length variables Add tls_version and tls_version_length variables to the audit plugin so they can be logged. This is useful to help identify suspicious or malformed connections attempting to use unsupported TLS versions. A log with this information will allow to detect and block more malicious connection attempts. Users with 'server_audit_events' empty will have these two new variables automatically visible in their logs, but if users don't want them, they can always configure what fields to include by listing the fields in 'server_audit_events'. In connection event, The TLS version will be populated in `object` field in key=value format, and the key-value pair will be omitted when the value is empty. To ensure the MTR test result matches in all environments, the TLS version string is replaced with a general `TLS_VERSION` to avoid the MTR test failing unexpectedly. It stores the version with query `SHOW STATUS LIKE 'Ssl_version'` and replace the output with `replace_result` command. All new code of the whole pull request, including one or several files that are either new files or modified ones, are contributed under the BSD-new license. I am contributing on behalf of my employer Amazon Web Services, Inc. [1]: https://docs.openssl.org/3.2/man3/SSL_get_version/ --- include/mysql/plugin_audit.h | 4 +- include/mysql/plugin_audit.h.pp | 2 + .../suite/plugins/r/server_audit.result | 56 ++++++++++++++----- mysql-test/suite/plugins/t/server_audit.test | 16 +++++- plugin/server_audit/server_audit.c | 35 ++++++++++-- sql/sql_audit.h | 19 +++++++ 6 files changed, 112 insertions(+), 20 deletions(-) diff --git a/include/mysql/plugin_audit.h b/include/mysql/plugin_audit.h index bfa6621b007..9771f959fce 100644 --- a/include/mysql/plugin_audit.h +++ b/include/mysql/plugin_audit.h @@ -29,7 +29,7 @@ extern "C" { #define MYSQL_AUDIT_CLASS_MASK_SIZE 1 -#define MYSQL_AUDIT_INTERFACE_VERSION 0x0302 +#define MYSQL_AUDIT_INTERFACE_VERSION 0x0303 /************************************************************************* @@ -102,6 +102,8 @@ struct mysql_event_connection const char *ip; unsigned int ip_length; MYSQL_CONST_LEX_STRING database; + const char *tls_version; + unsigned int tls_version_length; }; /* diff --git a/include/mysql/plugin_audit.h.pp b/include/mysql/plugin_audit.h.pp index fca41a85db0..a746396f921 100644 --- a/include/mysql/plugin_audit.h.pp +++ b/include/mysql/plugin_audit.h.pp @@ -738,6 +738,8 @@ struct mysql_event_connection const char *ip; unsigned int ip_length; MYSQL_CONST_LEX_STRING database; + const char *tls_version; + unsigned int tls_version_length; }; struct mysql_event_table { diff --git a/mysql-test/suite/plugins/r/server_audit.result b/mysql-test/suite/plugins/r/server_audit.result index 75cefc34074..fa62b4dd782 100644 --- a/mysql-test/suite/plugins/r/server_audit.result +++ b/mysql-test/suite/plugins/r/server_audit.result @@ -240,7 +240,7 @@ set global server_audit_logging= on; disconnect cn1; drop user user1@localhost; set global server_audit_events=''; -set global server_audit_incl_users='root, plug_dest'; +set global server_audit_incl_users='root, plug_dest, ssl_user1'; CREATE USER plug IDENTIFIED WITH 'test_plugin_server' AS 'plug_dest'; CREATE USER plug_dest IDENTIFIED BY 'plug_dest_passwd'; connect(localhost,plug,plug_dest,test,MYSQL_PORT,MYSQL_SOCK); @@ -256,6 +256,14 @@ connection default; disconnect plug_con; DROP USER plug; DROP USER plug_dest; +CREATE USER ssl_user1@localhost require SSL; +connect conssl1,localhost,ssl_user1,,,,,SSL; +SHOW STATUS LIKE 'Ssl_cipher'; +Variable_name Value +Ssl_cipher CIPHER_NAME +disconnect conssl1; +connection default; +DROP USER ssl_user1@localhost; set global server_audit_query_log_limit= 15; select (1), (2), (3), (4); 1 2 3 4 @@ -301,7 +309,7 @@ server_audit_file_path server_audit_file_rotate_now OFF server_audit_file_rotate_size 1000000 server_audit_file_rotations 9 -server_audit_incl_users root, plug_dest +server_audit_incl_users root, plug_dest, ssl_user1 server_audit_logging ON server_audit_mode 1 server_audit_output_type file @@ -318,10 +326,10 @@ TIME,HOSTNAME,root,localhost,ID,ID,QUERY,test,'set global server_audit_incl_user TIME,HOSTNAME,root,localhost,ID,ID,QUERY,test,'show variables like \'server_audit_incl_users\'',0 TIME,HOSTNAME,root,localhost,ID,ID,QUERY,test,'set global server_audit_excl_users= repeat("\'root\',", 10000)',ID TIME,HOSTNAME,root,localhost,ID,ID,QUERY,test,'show variables like \'server_audit_excl_users\'',0 -TIME,HOSTNAME,root,localhost,ID,0,CONNECT,mysql,,0 -TIME,HOSTNAME,root,localhost,ID,0,DISCONNECT,mysql,,0 -TIME,HOSTNAME,no_such_user,localhost,ID,0,FAILED_CONNECT,,,ID -TIME,HOSTNAME,no_such_user,localhost,ID,0,DISCONNECT,,,0 +TIME,HOSTNAME,root,localhost,ID,0,CONNECT,mysql,,0, +TIME,HOSTNAME,root,localhost,ID,0,DISCONNECT,mysql,,0, +TIME,HOSTNAME,no_such_user,localhost,ID,0,FAILED_CONNECT,,,ID, +TIME,HOSTNAME,no_such_user,localhost,ID,0,DISCONNECT,,,0, TIME,HOSTNAME,root,localhost,ID,ID,QUERY,test,'set global server_audit_incl_users=\'odin, dva, tri\'',0 TIME,HOSTNAME,root,localhost,ID,ID,QUERY,test,'set global server_audit_incl_users=\'odin, root, dva, tri\'',0 TIME,HOSTNAME,root,localhost,ID,ID,CREATE,test,t2, @@ -360,7 +368,7 @@ TIME,HOSTNAME,root,localhost,ID,ID,QUERY,test,'show variables like \'server_audi TIME,HOSTNAME,root,localhost,ID,ID,QUERY,test,'set global server_audit_mode=1',0 TIME,HOSTNAME,root,localhost,ID,ID,QUERY,test,'set global server_audit_events=\'\'',0 TIME,HOSTNAME,root,localhost,ID,ID,QUERY,test,'create database sa_db',0 -TIME,HOSTNAME,root,localhost,ID,0,CONNECT,test,,0 +TIME,HOSTNAME,root,localhost,ID,0,CONNECT,test,,0, TIME,HOSTNAME,root,localhost,ID,ID,CREATE,test,t1, TIME,HOSTNAME,root,localhost,ID,ID,QUERY,test,'create table t1 (id2 int)',0 TIME,HOSTNAME,root,localhost,ID,ID,WRITE,test,t1, @@ -392,7 +400,7 @@ TIME,HOSTNAME,root,localhost,ID,ID,READ,mysql,proc, TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,proc, TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,event, TIME,HOSTNAME,root,localhost,ID,ID,QUERY,sa_db,'drop database sa_db',0 -TIME,HOSTNAME,root,localhost,ID,0,DISCONNECT,,,0 +TIME,HOSTNAME,root,localhost,ID,0,DISCONNECT,,,0, TIME,HOSTNAME,root,localhost,ID,ID,QUERY,test,'create database sa_db',0 TIME,HOSTNAME,root,localhost,ID,ID,QUERY,sa_db,'use sa_db',0 TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,db, @@ -461,7 +469,7 @@ TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,proxies_priv, TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,roles_mapping, TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,global_priv, TIME,HOSTNAME,root,localhost,ID,ID,QUERY,sa_db,'set global server_audit_events=\'\'',0 -TIME,HOSTNAME,root,localhost,ID,ID,QUERY,sa_db,'set global server_audit_incl_users=\'root, plug_dest\'',0 +TIME,HOSTNAME,root,localhost,ID,ID,QUERY,sa_db,'set global server_audit_incl_users=\'root, plug_dest, ssl_user1\'',0 TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,db, TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,tables_priv, TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,columns_priv, @@ -478,15 +486,15 @@ TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,proxies_priv, TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,roles_mapping, TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,global_priv, TIME,HOSTNAME,root,localhost,ID,ID,QUERY,sa_db,'CREATE USER plug_dest IDENTIFIED BY *****',0 -TIME,HOSTNAME,plug,localhost,ID,0,FAILED_CONNECT,,,ID -TIME,HOSTNAME,plug,localhost,ID,0,DISCONNECT,,,0 +TIME,HOSTNAME,plug,localhost,ID,0,FAILED_CONNECT,,,ID, +TIME,HOSTNAME,plug,localhost,ID,0,DISCONNECT,,,0, TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,proxies_priv, TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,global_priv, TIME,HOSTNAME,root,localhost,ID,ID,QUERY,sa_db,'GRANT PROXY ON plug_dest TO plug',0 -TIME,HOSTNAME,plug,localhost,ID,0,CONNECT,,,0 -TIME,HOSTNAME,plug,localhost,ID,0,PROXY_CONNECT,,`plug_dest`@`%`,0 +TIME,HOSTNAME,plug,localhost,ID,0,CONNECT,,,0, +TIME,HOSTNAME,plug,localhost,ID,0,PROXY_CONNECT,,`plug_dest`@`%`,0, TIME,HOSTNAME,plug,localhost,ID,ID,QUERY,,'select USER(),CURRENT_USER()',0 -TIME,HOSTNAME,plug,localhost,ID,0,DISCONNECT,,,0 +TIME,HOSTNAME,plug,localhost,ID,0,DISCONNECT,,,0, TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,db, TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,tables_priv, TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,columns_priv, @@ -503,6 +511,26 @@ TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,proxies_priv, TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,roles_mapping, TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,global_priv, TIME,HOSTNAME,root,localhost,ID,ID,QUERY,sa_db,'DROP USER plug_dest',0 +TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,db, +TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,tables_priv, +TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,columns_priv, +TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,procs_priv, +TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,proxies_priv, +TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,roles_mapping, +TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,global_priv, +TIME,HOSTNAME,root,localhost,ID,ID,QUERY,sa_db,'CREATE USER ssl_user1@localhost require SSL',0 +TIME,HOSTNAME,ssl_user1,localhost,ID,0,CONNECT,,,0,TLS_VERSION +TIME,HOSTNAME,ssl_user1,localhost,ID,ID,QUERY,Access denied for user 'ssl_user1'@'localhost' to database 'test','SHOW STATUS LIKE \'Ssl_version\'',0 +TIME,HOSTNAME,ssl_user1,localhost,ID,ID,QUERY,Access denied for user 'ssl_user1'@'localhost' to database 'test','SHOW STATUS LIKE \'Ssl_cipher\'',0 +TIME,HOSTNAME,ssl_user1,localhost,ID,0,DISCONNECT,,,0,TLS_VERSION +TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,db, +TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,tables_priv, +TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,columns_priv, +TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,procs_priv, +TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,proxies_priv, +TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,roles_mapping, +TIME,HOSTNAME,root,localhost,ID,ID,WRITE,mysql,global_priv, +TIME,HOSTNAME,root,localhost,ID,ID,QUERY,sa_db,'DROP USER ssl_user1@localhost',0 TIME,HOSTNAME,root,localhost,ID,ID,QUERY,sa_db,'set global serv',0 TIME,HOSTNAME,root,localhost,ID,ID,QUERY,sa_db,'select (1), (2)',0 TIME,HOSTNAME,root,localhost,ID,ID,QUERY,sa_db,'select \'A\', ',0 diff --git a/mysql-test/suite/plugins/t/server_audit.test b/mysql-test/suite/plugins/t/server_audit.test index 0a1f6a2aa9b..906f635536f 100644 --- a/mysql-test/suite/plugins/t/server_audit.test +++ b/mysql-test/suite/plugins/t/server_audit.test @@ -1,5 +1,7 @@ --source include/have_plugin_auth.inc --source include/not_embedded.inc +--source include/have_ssl_communication.inc +--source include/have_tlsv13.inc if (!$SERVER_AUDIT_SO) { skip No SERVER_AUDIT plugin; @@ -190,7 +192,7 @@ source include/wait_until_count_sessions.inc; drop user user1@localhost; set global server_audit_events=''; -set global server_audit_incl_users='root, plug_dest'; +set global server_audit_incl_users='root, plug_dest, ssl_user1'; CREATE USER plug IDENTIFIED WITH 'test_plugin_server' AS 'plug_dest'; CREATE USER plug_dest IDENTIFIED BY 'plug_dest_passwd'; @@ -211,6 +213,16 @@ disconnect plug_con; DROP USER plug; DROP USER plug_dest; +CREATE USER ssl_user1@localhost require SSL; +connect (conssl1,localhost,ssl_user1,,,,,SSL); +--let $ssl_version = query_get_value(SHOW STATUS LIKE 'Ssl_version', Value, 1) +--replace_column 2 CIPHER_NAME +SHOW STATUS LIKE 'Ssl_cipher'; +disconnect conssl1; +connection default; +--sleep 2 +DROP USER ssl_user1@localhost; + set global server_audit_query_log_limit= 15; select (1), (2), (3), (4); select 'A', 'B', 'C', 'D'; @@ -237,6 +249,8 @@ show status like 'server_audit_current_log'; show variables like 'server_audit%'; uninstall plugin server_audit; +# replace stored SSL verison with constant string "TLS_VERSION" +--replace_result $ssl_version TLS_VERSION # replace the timestamp and the hostname with constant values --replace_regex /[0-9]* [0-9][0-9]:[0-9][0-9]:[0-9][0-9]\,[^,]*\,/TIME,HOSTNAME,/ /\,[1-9][0-9]*\,/,1,/ /\,[1-9][0-9]*/,ID/ cat_file $MYSQLD_DATADIR/server_audit.log; diff --git a/plugin/server_audit/server_audit.c b/plugin/server_audit/server_audit.c index 65e9f0a5314..53203c143c7 100644 --- a/plugin/server_audit/server_audit.c +++ b/plugin/server_audit/server_audit.c @@ -329,6 +329,8 @@ struct connection_info int host_length; char ip[64]; int ip_length; + char tls_version[64]; + int tls_version_length; const char *query; int query_length; char query_buffer[1024]; @@ -1011,6 +1013,7 @@ static struct connection_info *get_loc_info(MYSQL_THD thd) ci->user_length= 0; ci->host_length= 0; ci->ip_length= 0; + ci->tls_version_length= 0; } return ci; } @@ -1192,6 +1195,7 @@ static void setup_connection_simple(struct connection_info *ci) ci->user_length= 0; ci->host_length= 0; ci->ip_length= 0; + ci->tls_version_length= 0; ci->query_length= 0; ci->header= 0; ci->proxy_length= 0; @@ -1215,6 +1219,8 @@ static void setup_connection_connect(MYSQL_THD thd,struct connection_info *cn, event->host, event->host_length); get_str_n(cn->ip, &cn->ip_length, sizeof(cn->ip), event->ip, event->ip_length); + get_str_n(cn->tls_version, &cn->tls_version_length, sizeof(cn->tls_version), + event->tls_version, event->tls_version_length); cn->header= 0; if (event->proxy_user && event->proxy_user[0]) { @@ -1381,6 +1387,8 @@ static void change_connection(struct connection_info *cn, event->user, event->user_length); get_str_n(cn->ip, &cn->ip_length, sizeof(cn->ip), event->ip, event->ip_length); + get_str_n(cn->tls_version, &cn->tls_version_length, sizeof(cn->tls_version), + event->tls_version, event->tls_version_length); } /* @@ -1477,6 +1485,17 @@ static size_t log_header(char *message, size_t message_len, connection_id, query_id, operation); } +static size_t create_tls_obj(const struct mysql_event_connection *ev, char *obj_str, size_t len) { + size_t obj_len; + + obj_len= 0; + memset(obj_str, 0, len); + if (ev->tls_version_length > 0) { + obj_len= my_snprintf(obj_str, len, + "%.*s", ev->tls_version_length, ev->tls_version); + } + return obj_len; +} static int log_proxy(const struct connection_info *cn, const struct mysql_event_connection *event) @@ -1494,10 +1513,10 @@ static int log_proxy(const struct connection_info *cn, cn->ip, cn->ip_length, event->thread_id, 0, "PROXY_CONNECT"); csize+= my_snprintf(message+csize, sizeof(message) - 1 - csize, - ",%.*s,`%.*s`@`%.*s`,%d", cn->db_length, cn->db, + ",%.*s,`%.*s`@`%.*s`,%d,%.*s", cn->db_length, cn->db, cn->proxy_length, cn->proxy, cn->proxy_host_length, cn->proxy_host, - event->status); + event->status, cn->tls_version_length, cn->tls_version); message[csize]= '\n'; return write_log(message, csize + 1, 1); } @@ -1510,6 +1529,8 @@ static int log_connection(const struct connection_info *cn, time_t ctime; size_t csize; char message[1024]; + char tls_obj[32]; + size_t obj_len; (void) time(&ctime); csize= log_header(message, sizeof(message)-1, &ctime, @@ -1518,8 +1539,10 @@ static int log_connection(const struct connection_info *cn, cn->host, cn->host_length, cn->ip, cn->ip_length, event->thread_id, 0, type); + + obj_len= create_tls_obj(event, tls_obj, sizeof(tls_obj)); csize+= my_snprintf(message+csize, sizeof(message) - 1 - csize, - ",%.*s,,%d", cn->db_length, cn->db, event->status); + ",%.*s,,%d,%.*s", cn->db_length, cn->db, event->status, (int) obj_len, tls_obj); message[csize]= '\n'; return write_log(message, csize + 1, 1); } @@ -1531,6 +1554,8 @@ static int log_connection_event(const struct mysql_event_connection *event, time_t ctime; size_t csize; char message[1024]; + char tls_obj[32]; + size_t obj_len; (void) time(&ctime); csize= log_header(message, sizeof(message)-1, &ctime, @@ -1539,8 +1564,10 @@ static int log_connection_event(const struct mysql_event_connection *event, event->host, event->host_length, event->ip, event->ip_length, event->thread_id, 0, type); + obj_len= create_tls_obj(event, tls_obj, sizeof(tls_obj)); csize+= my_snprintf(message+csize, sizeof(message) - 1 - csize, - ",%.*s,,%d", (int) event->database.length, event->database.str, event->status); + ",%.*s,,%d,%.*s", (int) event->database.length,event->database.str, + event->status, (int) obj_len, tls_obj); message[csize]= '\n'; return write_log(message, csize + 1, 1); } diff --git a/sql/sql_audit.h b/sql/sql_audit.h index 64500067699..1078be9ca99 100644 --- a/sql/sql_audit.h +++ b/sql/sql_audit.h @@ -83,6 +83,22 @@ static inline uint make_user_name(THD *thd, char *buf) return (uint)(end-buf); } +static inline +void set_tls_version_of_event(THD *thd, mysql_event_connection *event) +{ + event->tls_version = ""; + event->tls_version_length = 0; +#ifdef HAVE_OPENSSL + Vio *vio= thd->net.vio; + SSL *ssl= (SSL *) vio->ssl_arg; + if (ssl) + { + event->tls_version = SSL_get_version(ssl); + event->tls_version_length = safe_strlen_uint(event->tls_version); + } +#endif +} + /** Call audit plugins of GENERAL audit class, MYSQL_AUDIT_GENERAL_LOG subtype. @@ -219,6 +235,7 @@ void mysql_audit_notify_connection_connect(THD *thd) event.ip= sctx->ip; event.ip_length= safe_strlen_uint(sctx->ip); event.database= thd->db; + set_tls_version_of_event(thd, &event); mysql_audit_notify(thd, MYSQL_AUDIT_CONNECTION_CLASS, &event); } @@ -248,6 +265,7 @@ void mysql_audit_notify_connection_disconnect(THD *thd, int errcode) event.ip= sctx->ip; event.ip_length= safe_strlen_uint(sctx->ip) ; event.database= thd->db; + set_tls_version_of_event(thd, &event); mysql_audit_notify(thd, MYSQL_AUDIT_CONNECTION_CLASS, &event); } @@ -278,6 +296,7 @@ void mysql_audit_notify_connection_change_user(THD *thd, event.ip= old_ctx->ip; event.ip_length= safe_strlen_uint(old_ctx->ip); event.database= thd->db; + set_tls_version_of_event(thd, &event); mysql_audit_notify(thd, MYSQL_AUDIT_CONNECTION_CLASS, &event); }