MXS-2891: Log password hash on mismatch

By logging the password hash when user authentication fails due to a
password mismatch, we can be certain what the client sent and what is the
currently stored value in MaxScale. This should not be on by default which
is why a new parameter is required.
This commit is contained in:
Markus Mäkelä 2020-03-04 11:46:54 +02:00
parent 0aae500577
commit 1bf46fadf6
No known key found for this signature in database
GPG Key ID: 5CE746D557ACC499
4 changed files with 53 additions and 12 deletions

View File

@ -84,3 +84,12 @@ case-insensitive by converting all names into their lowercase form.
```
authenticator_options=lower_case_table_names=false
```
### `log_password_mismatch`
This parameter takes a boolean value and is disabled by default. When enabled,
password hashes are logged in the error messages when authentication fails due
to a password mismatch between the one stored in MaxScale and the one given by
the user. This feature should only be used to diagnose authentication issues in
MaxScale and should be done on a secure system as the logging of the password
hashes can be considered a security risk.

View File

@ -374,11 +374,8 @@ static int auth_cb(void* data, int columns, char** rows, char** row_names)
return 0;
}
int validate_mysql_user(MYSQL_AUTH* instance,
DCB* dcb,
MYSQL_session* session,
uint8_t* scramble,
size_t scramble_len)
std::pair<bool, std::string> get_password(MYSQL_AUTH* instance, DCB* dcb, MYSQL_session* session,
uint8_t* scramble, size_t scramble_len)
{
sqlite3* handle = get_handle(instance);
const char* validate_query = instance->lower_case_table_names ?
@ -387,7 +384,6 @@ int validate_mysql_user(MYSQL_AUTH* instance,
size_t len = strlen(validate_query) + 1 + strlen(session->user) * 2
+ strlen(session->db) * 2 + MYSQL_HOST_MAXLEN + session->auth_token_len * 4 + 1;
char sql[len + 1];
int rval = MXS_AUTH_FAILED;
char* err;
if (instance->skip_auth)
@ -456,12 +452,25 @@ int validate_mysql_user(MYSQL_AUTH* instance,
}
}
if (res.ok)
return {res.ok, res.output};
}
int validate_mysql_user(MYSQL_AUTH* instance,
DCB* dcb,
MYSQL_session* session,
uint8_t* scramble,
size_t scramble_len)
{
int rval = MXS_AUTH_FAILED;
sqlite3* handle = get_handle(instance);
auto res = get_password(instance, dcb, session, scramble, scramble_len);
if (res.first)
{
/** Found a matching row */
if (no_password_required(res.output, session->auth_token_len)
|| check_password(res.output,
if (no_password_required(res.second.c_str(), session->auth_token_len)
|| check_password(res.second.c_str(),
session->auth_token,
session->auth_token_len,
scramble,

View File

@ -195,6 +195,7 @@ static void* mysql_auth_init(char** options)
instance->check_permissions = true;
instance->lower_case_table_names = false;
instance->checksum = 0;
instance->log_password_mismatch = false;
for (int i = 0; options[i]; i++)
{
@ -224,6 +225,10 @@ static void* mysql_auth_init(char** options)
{
instance->lower_case_table_names = config_truth_value(value);
}
else if (strcmp(options[i], "log_password_mismatch") == 0)
{
instance->log_password_mismatch = config_truth_value(value);
}
else
{
MXS_ERROR("Unknown authenticator option: %s", options[i]);
@ -308,8 +313,9 @@ static GWBUF* gen_auth_switch_request_packet(MySQLProtocol* proto, MYSQL_session
return buffer;
}
static void log_auth_failure(DCB* dcb, int auth_ret)
static void log_auth_failure(MYSQL_AUTH* instance, DCB* dcb, int auth_ret)
{
MySQLProtocol* protocol = DCB_PROTOCOL(dcb, MySQLProtocol);
MYSQL_session* client_data = (MYSQL_session*)dcb->data;
std::ostringstream extra;
@ -320,6 +326,18 @@ static void log_auth_failure(DCB* dcb, int auth_ret)
else if (auth_ret == MXS_AUTH_FAILED_WRONG_PASSWORD)
{
extra << "Wrong password.";
if (instance->log_password_mismatch)
{
uint8_t double_sha1[sizeof(client_data->client_sha1)];
gw_sha1_str(client_data->client_sha1, sizeof(client_data->client_sha1), double_sha1);
char buf[sizeof(double_sha1) * 2 + 1];
gw_bin2hex(buf, double_sha1, sizeof(double_sha1));
extra << " Received '" << buf << "', expected '"
<< get_password(instance, dcb, client_data, protocol->scramble,
sizeof(protocol->scramble)).second
<< "'.";
}
}
else
{
@ -410,9 +428,10 @@ static int mysql_auth_authenticate(DCB* dcb)
dcb->user = MXS_STRDUP_A(client_data->user);
/** Send an OK packet to the client */
}
else if (dcb->service->log_auth_warnings)
else if (dcb->service->log_auth_warnings
|| (instance->log_password_mismatch && auth_ret == MXS_AUTH_FAILED_WRONG_PASSWORD))
{
log_auth_failure(dcb, auth_ret);
log_auth_failure(instance, dcb, auth_ret);
}
/* let's free the auth_token now */

View File

@ -112,6 +112,7 @@ typedef struct mysql_auth
bool skip_auth; /**< Authentication will always be successful */
bool check_permissions;
bool lower_case_table_names; /**< Disable database case-sensitivity */
bool log_password_mismatch; /**< Log password mismatches*/
uint64_t checksum;
} MYSQL_AUTH;
@ -212,4 +213,7 @@ int validate_mysql_user(MYSQL_AUTH* instance,
uint8_t* scramble,
size_t scramble_len);
std::pair<bool, std::string> get_password(MYSQL_AUTH* instance, DCB* dcb, MYSQL_session* session,
uint8_t* scramble, size_t scramble_len);
MXS_END_DECLS