Análisis de fallas | Desde un registro de errores hasta un análisis en profundidad del mecanismo de autenticación y errores de MySQL

Autor: Li Xichao

Un ingeniero de base de datos de Jiangsu Suning Bank al que le encanta reír, principalmente responsable de la operación y el mantenimiento diarios de la base de datos, la construcción de automatización y la operación y mantenimiento de la plataforma DMP. Bueno en MySQL, Python, Oracle, como el ciclismo y la tecnología de investigación.
Fuente de este artículo: contribución original

* Producido por la comunidad de código abierto de Aikesheng, el contenido original no puede usarse sin autorización, comuníquese con el editor e indique la fuente para la reimpresión.


Los estudiantes de I + D informaron que el sistema comercial relacionado con la base de datos MySQL en un entorno de prueba de rendimiento del sistema funciona normalmente, pero hay una gran cantidad de registros de advertencia y es necesario cooperar con el análisis de las razones.

1. Fenómenos anormales

Hay una gran cantidad de la siguiente información en el archivo de registro de errores de mysql:

2023-01-10T01:07:23.035479Z 13 [Warning] [MY-013360] [Server] Plugin sha256_password reported: ''sha256_password' is deprecated and will be removed in a future release. Please use caching_sha2_password instead'

información ambiental clave

2. Análisis preliminar

Al ver el registro de advertencia anterior, según la teoría empírica, la primera reacción debería ser que la versión del cliente es demasiado baja y su complemento de autenticación es una versión que el servidor descartará, por lo que se genera el mensaje de advertencia anterior. Especialmente, algunas herramientas de cliente comunes pueden desencadenar fácilmente este problema debido a la frecuencia de actualización.

tratar de reproducir

De acuerdo con las sugerencias del análisis preliminar, después de comunicar las sugerencias del análisis preliminar con los estudiantes de I+D, acceda a la base de datos a través de herramientas de base de datos comunes para ver si se puede reproducir el error. Sin embargo, a través de usuarios de base de datos comunes en la base de datos y accediendo a la base de datos a través de diferentes herramientas, la excepción no se activó en el momento del acceso.
Por lo tanto, el primer intento de reproducción fracasó. ¿Es por otras razones?

En el proceso de intentar acceder por primera vez, observe el registro de errores de la base de datos en tiempo real. En el proceso de intentar acceder con el cliente no se reprodujo el error. Pero aún verá que el registro de advertencia correspondiente se envía continuamente al archivo de registro de errores. Además, la frecuencia es alta y el intervalo de tiempo es fijo, lo que también prueba que las herramientas de la base de datos no acceden manualmente al error.

¡El sistema de la aplicación funciona normalmente y no es causado por el cliente! Como DBA, ¿cómo debería analizarlo más a fondo?

Primeros trucos

Debido al entorno de prueba, para este error, puede realizar las siguientes operaciones para habilitar los registros generales de MySQL:

-- 开通一般日志:
show variables like 'general_log';
set global general_log=on;
show variables like 'general_log';
-- 查看一般日志路径:
show variables like 'general_log_file';

Después de habilitar el registro, observe el registro de errores y busque los siguientes registros en el registro general:

Sugerencia: después de encontrar una excepción, cierre el registro general de inmediato para evitar generar demasiados registros y agotar el espacio en disco:

-- 开通一般日志:
show variables like 'general_log';
set global general_log=off;
show variables like 'general_log';

Es decir, el usuario dbuser2 inicia una solicitud para acceder a la base de datos desde el servidor 10.xy43 en el momento del problema. Después de confirmar el acceso anómalo del usuario y el servidor, verifique la tabla mysql.user de la base de datos, skip-grant-tables y otras configuraciones, y descubra que el usuario no existe en la base de datos, y que la tabla de autorización y otras configuraciones no se omiten. El uso de este usuario no podrá iniciar sesión en la base de datos.

Envíe la información a los estudiantes de investigación y desarrollo y pronto confirmó que algunas aplicaciones se configuraron de manera irrazonable, usando usuarios de bases de datos que no existían y conectándose regularmente a la base de datos para realizar tareas. Entonces, después de que los estudiantes de investigación y desarrollo modificaron la configuración, ya no se generó el registro de advertencia.

Entonces el análisis de este problema está aquí, ¿se puede terminar?

¡Después de modificar la configuración, el registro de advertencia ya no aparece! Pero dado que es un usuario inexistente, ¿por qué se solicita que se descarte el complemento de autenticación al acceder a él?

3. Análisis del código fuente

Con las preguntas, lo primero que viene a la mente es: dado que el usuario de la base de datos no existe en la tabla mysql.user, el inicio de sesión también generará una advertencia. ¿Este usuario es un usuario interno de mysql y está codificado? Así que obtenga el código fuente de la versión correspondiente y confírmelo con el siguiente comando:

cd mysql-8.0.27/
grep -rwi "dbuser2" *

El resultado de acceso está vacío, es decir, no hay un "usuario interno" adivinado.

Lógica de autenticación de inicio de sesión normal

Dado que no hay codificación rígida, solo puede ser causado por la lógica interna. En primer lugar, para el proceso de inicio de sesión del usuario de mysql en circunstancias normales, los resultados del análisis del código fuente son los siguientes:

|—> handle_connection
  |—> thd_prepare_connection
    |—> login_connection
      |—> check_connection
        // 判断客户端的主机名是否可以登录(mysql.user.host),如果 mysql.user.host 有 '%' 那么将 allow_all_hosts,允许所有主机。
        |—> acl_check_host   
        |—> acl_authenticate
          |—> server_mpvio_initialize // 初始化mpvio对象,包括赋值 mpvio->ip / mpvio->host
          |—> auth_plugin_name="caching_sha2_password"
          |—> do_auth_once
            |—> caching_sha2_password_authenticate // auth->authenticate_user
              |—> server_mpvio_read_packet // vio->read_packet(vio, &pkt) // pkt=passwd
                |—> parse_client_handshake_packet
                  |—> char *user = get_string(&end, &bytes_remaining_in_packet, &user_len);
                  |—> passwd = get_length_encoded_string(&end, &bytes_remaining_in_packet, &passwd_len);
                  |—> mpvio->auth_info.user_name = my_strndup(key_memory_MPVIO_EXT_auth_info, user, user_len, MYF(MY_WME))
                  // 根据 user 搜索 mysql.user.host ,并与客户端的 hostname/ip 进行比较:匹配记录后,赋值 mpvio->acl_user
                  |—> find_mpvio_user(thd, mpvio) 
                    |—> list = cached_acl_users_for_name(mpvio->auth_info.user_name); // 根据 user 搜索 mysql.user.host 
                    |—> acl_user_tmp->host.compare_hostname(mpvio->host, mpvio->ip) //  与客户端的 hostname/ip 进行比较
                    |—> mpvio->acl_user_plugin = mpvio->acl_user->plugin; // 赋值 acl_user_plugin 属性为用户的plugin名
                    |—> mpvio->auth_info.multi_factor_auth_info[0].auth_string = mpvio->acl_user->credentials[PRIMARY_CRED].m_auth_string.str; 
                    |—> mpvio->auth_info.auth_string = mpvio->auth_info.multi_factor_auth_info[0].auth_string; 
                  |—> if (my_strcasecmp(system_charset_info, mpvio->acl_user_plugin.str,plugin_name(mpvio->plugin)->str) != 0)
                  |—> my_strcasecmp(system_charset_info, client_plugin,user_client_plugin_name) //检查客户端的认证插件与用户插件是否相同
              |—> make_hash_key(info->authenticated_as, hostname ? hostname : nullptr, authorization_id);  // 生成 authorization_id = user1\000% 
              |—> g_caching_sha2_password->fast_authenticate(authorization_id,*scramble,20,pkt,false) // 进行快速授权操作
                |—> m_cache.search(authorization_id, digest) // 根据 user、host 搜索密码,赋值到digest
                |—> Validate_scramble validate_scramble_first(scramble, digest.digest_buffer[0], random, random_length);
                |—> validate_scramble_first.validate(); // 校验 scramble
              // 如验证成功
              |—> vio->write_packet(vio, (uchar *)&fast_auth_success, 1)
              |—> return CR_OK;
              // 否则进行进行慢授权操作
              |—> g_caching_sha2_password->authenticate( authorization_id, serialized_string, plaintext_password);
          |—> server_mpvio_update_thd(thd, &mpvio);
          |—> check_and_update_password_lock_state(mpvio, thd, res);
          // 继续其它授权操作

Es decir, la operación de autenticación central se realiza en la función caching_sha2_password_authenticate(), primero llame a la función find_mpvio_user(), encuentre el usuario configurado a través del usuario y el nombre de host, y luego llame a la función fast_authenticate() para verificar rápidamente la contraseña.

Usar lógica de autenticación de usuario inexistente

Cuando el usuario no existe, el proceso de inicio de sesión del usuario de mysql, los resultados del análisis del código fuente son los siguientes:

|—> handle_connection
  |—> thd_prepare_connection
    |—> login_connection
      |—> check_connection
        // 判断客户端的主机名是否可以登录(mysql.user.host),如果 mysql.user.host 有 '%' 那么将 allow_all_hosts,允许所有主机。
        |—> acl_check_host   
        |—> acl_authenticate
          |—> server_mpvio_initialize // 初始化mpvio对象,包括赋值 mpvio->ip / mpvio->host
          |—> auth_plugin_name="caching_sha2_password"
          |—> do_auth_once
            |—> caching_sha2_password_authenticate // auth->authenticate_user
              |—> server_mpvio_read_packet // vio->read_packet(vio, &pkt) // pkt=passwd
                |—> parse_client_handshake_packet
                  |—> char *user = get_string(&end, &bytes_remaining_in_packet, &user_len);
                  |—> passwd = get_length_encoded_string(&end, &bytes_remaining_in_packet, &passwd_len);
                  |—> mpvio->auth_info.user_name = my_strndup(key_memory_MPVIO_EXT_auth_info, user, user_len, MYF(MY_WME))
                  |—> find_mpvio_user(thd, mpvio) 
                    |—> list = cached_acl_users_for_name(mpvio->auth_info.user_name); // 根据 user 搜索 mysql.user.host, 由于用户不存在,搜索不到记录
                    |—> mpvio->acl_user = decoy_user(usr, hst, mpvio->mem_root, mpvio->rand, initialized); // 
                      |—> Auth_id key(user);
                      // 判断是否用户存在于 unknown_accounts
                      |—> unknown_accounts->find(key, value)
                      // 如存在:
                      |—> user->plugin = Cached_authentication_plugins::cached_plugins_names[value];
                      // 如不存在:
                      |—> const int DECIMAL_SHIFT = 1000;
                      |—> const int random_number = static_cast<int>(my_rnd(rand) * DECIMAL_SHIFT);
                      |—> uint plugin_num = (uint)(random_number % ((uint)PLUGIN_LAST));
                      |—> user->plugin = Cached_authentication_plugins::cached_plugins_names[plugin_num];
                      |—> unknown_accounts->insert(key, plugin_num)
                    |—> mpvio->acl_user_plugin = mpvio->acl_user->plugin; // 赋值 acl_user_plugin 属性为用户的plugin名
                    |—> mpvio->auth_info.multi_factor_auth_info[0].auth_string = mpvio->acl_user->credentials[PRIMARY_CRED].m_auth_string.str; // ""
                    |—> mpvio->auth_info.auth_string = mpvio->auth_info.multi_factor_auth_info[0].auth_string; // ""
                    |—> mpvio->auth_info.additional_auth_string_length = 0; // 0
                    |—> mpvio->auth_info.auth_string_length = mpvio->auth_info.multi_factor_auth_info[0].auth_string_length; // 0
                  |—> if (my_strcasecmp(system_charset_info, mpvio->acl_user_plugin.str,plugin_name(mpvio->plugin)->str) != 0)
                  |—> return packet_error;
                |—> if (pkt_len == packet_error) goto err;
                |—> return -1;
          |—> auth_plugin_name = mpvio.acl_user->plugin;
          |—> res = do_auth_once(thd, auth_plugin_name, &mpvio);
            |—> sha256_password_authenticate() //auth->authenticate_user(mpvio, &mpvio->auth_info);
              |—> LogPluginErr // Deprecate message for SHA-256 authentication plugin.
              // 打印: 2023-01-10T01:07:23.035479Z 13 [Warning] [MY-013360] [Server] Plugin sha256_password reported: ''sha256_password' is deprecated and will be removed in a future release. Please use caching_sha2_password instead'
              |—> server_mpvio_read_packet() // vio->read_packet(vio, &pkt)
              |—> if (info->auth_string_length == 0 && info->additional_auth_string_length == 0) // info -> auth_info
              |—>   return CR_ERROR;
            |—> return res; // 0
          |—> server_mpvio_update_thd(thd, &mpvio);
          |—> check_and_update_password_lock_state(mpvio, thd, res); // 直接返回
          ...
          |—> login_failed_error // 打印登录报错信息
          // 2023-01-10T02:02:44.659796Z 19 [Note] [MY-010926] [Server] Access denied for user 'user2'@'localhost' (using password: YES)
      |—> thd->send_statement_status();  // 客户端终止

Es decir, el objeto acl_user creado por la función señuelo_usuario() cuando se utiliza un usuario inexistente para iniciar sesión en la base de datos. Cuando se crea este objeto, su atributo de complemento se selecciona aleatoriamente de cached_plugins_enum. Por lo tanto, es posible seleccionar el complemento PLUGIN_SHA256_PASSWORD. Sin embargo, al ingresar la función sha256_password_authenticate(), se generará un indicador de nivel de Advertencia para indicar que se descartará la autenticación PLUGIN_SHA256_PASSWORD. Posteriormente, dado que la longitud del objeto acl_user auth_string_length creado en decoy_user() no es 0, CR_ERROR se devolverá directamente en la lógica de autenticación posterior, es decir, la autenticación falla.

Resumen de causa raíz

De acuerdo con el análisis certificado anterior, la causa principal de que PLUGIN_SHA256_PASSWORD se descarte en el registro de errores es: en la versión actual, cuando se utiliza un usuario inexistente para iniciar sesión en la base de datos, mysql seleccionará aleatoriamente el complemento de autenticación de contraseña del usuario. , en la versión actual, hay una probabilidad de 1/3 de que se seleccione el complemento PLUGIN_SHA256_PASSWORD. Después de seleccionar este complemento, la lógica de autenticación posterior activará la generación de registros de advertencia.

4. Resolución de problemas

Según el proceso de análisis anterior, la causa directa de este problema es que la aplicación configura un usuario de base de datos inexistente y la causa principal es que existen ciertos defectos en la lógica de autenticación de inicio de sesión de la base de datos. Entonces, para resolver este problema, puede consultar las siguientes soluciones:

1. Refiriéndose a la solución en el análisis preliminar, modifique la configuración de conexión de la aplicación a la información correcta del usuario;

2. Puede filtrar la alarma a través de parámetros en la base de datos mysql para evitar ingresar la información de la alarma en el archivo de registro de errores. La configuración correspondiente es la siguiente:

show variables like 'log_error_suppression_list';
set global log_error_suppression_list='MY-013360;
show variables like 'log_error_suppression_list';

Tenga en cuenta que el uso de este esquema también dará como resultado una alerta que existe y utiliza el complemento de autenticación SHA256_PASSWORD. Se puede utilizar como solución temporal;

3. Modifique el código mysql para evitar seleccionar el complemento de autenticación SHA256_PASSWORD al iniciar sesión en la base de datos con un usuario inexistente. El error #109635 está archivado actualmente para esta solución.

Adjunto: ubicación de la función clave

find_mpvio_user() (./sql/auth/sql_authentication.cc:2084)
parse_client_handshake_packet() (./sql/auth/sql_authentication.cc:2990)
server_mpvio_read_packet() (./sql/auth/sql_authentication.cc:3282)
caching_sha2_password_authenticate() (./sql/auth/sha2_password.cc:955)
do_auth_once() (./sql/auth/sql_authentication.cc:3327)
acl_authenticate() (./sql/auth/sql_authentication.cc:3799)
check_connection() (./sql/sql_connect.cc:651)
login_connection() (./sql/sql_connect.cc:716)
thd_prepare_connection() (./sql/sql_connect.cc:889)
handle_connection() (./sql/conn_handler/connection_handler_per_thread.cc:298)

Supongo que te gusta

Origin blog.csdn.net/ActionTech/article/details/130088608
Recomendado
Clasificación