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:
@ -84,3 +84,12 @@ case-insensitive by converting all names into their lowercase form.
|
|||||||
```
|
```
|
||||||
authenticator_options=lower_case_table_names=false
|
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.
|
||||||
|
@ -374,11 +374,8 @@ static int auth_cb(void* data, int columns, char** rows, char** row_names)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int validate_mysql_user(MYSQL_AUTH* instance,
|
std::pair<bool, std::string> get_password(MYSQL_AUTH* instance, DCB* dcb, MYSQL_session* session,
|
||||||
DCB* dcb,
|
uint8_t* scramble, size_t scramble_len)
|
||||||
MYSQL_session* session,
|
|
||||||
uint8_t* scramble,
|
|
||||||
size_t scramble_len)
|
|
||||||
{
|
{
|
||||||
sqlite3* handle = get_handle(instance);
|
sqlite3* handle = get_handle(instance);
|
||||||
const char* validate_query = instance->lower_case_table_names ?
|
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
|
size_t len = strlen(validate_query) + 1 + strlen(session->user) * 2
|
||||||
+ strlen(session->db) * 2 + MYSQL_HOST_MAXLEN + session->auth_token_len * 4 + 1;
|
+ strlen(session->db) * 2 + MYSQL_HOST_MAXLEN + session->auth_token_len * 4 + 1;
|
||||||
char sql[len + 1];
|
char sql[len + 1];
|
||||||
int rval = MXS_AUTH_FAILED;
|
|
||||||
char* err;
|
char* err;
|
||||||
|
|
||||||
if (instance->skip_auth)
|
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 */
|
/** Found a matching row */
|
||||||
|
|
||||||
if (no_password_required(res.output, session->auth_token_len)
|
if (no_password_required(res.second.c_str(), session->auth_token_len)
|
||||||
|| check_password(res.output,
|
|| check_password(res.second.c_str(),
|
||||||
session->auth_token,
|
session->auth_token,
|
||||||
session->auth_token_len,
|
session->auth_token_len,
|
||||||
scramble,
|
scramble,
|
||||||
|
@ -195,6 +195,7 @@ static void* mysql_auth_init(char** options)
|
|||||||
instance->check_permissions = true;
|
instance->check_permissions = true;
|
||||||
instance->lower_case_table_names = false;
|
instance->lower_case_table_names = false;
|
||||||
instance->checksum = 0;
|
instance->checksum = 0;
|
||||||
|
instance->log_password_mismatch = false;
|
||||||
|
|
||||||
for (int i = 0; options[i]; i++)
|
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);
|
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
|
else
|
||||||
{
|
{
|
||||||
MXS_ERROR("Unknown authenticator option: %s", options[i]);
|
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;
|
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;
|
MYSQL_session* client_data = (MYSQL_session*)dcb->data;
|
||||||
std::ostringstream extra;
|
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)
|
else if (auth_ret == MXS_AUTH_FAILED_WRONG_PASSWORD)
|
||||||
{
|
{
|
||||||
extra << "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
|
else
|
||||||
{
|
{
|
||||||
@ -410,9 +428,10 @@ static int mysql_auth_authenticate(DCB* dcb)
|
|||||||
dcb->user = MXS_STRDUP_A(client_data->user);
|
dcb->user = MXS_STRDUP_A(client_data->user);
|
||||||
/** Send an OK packet to the client */
|
/** 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 */
|
/* let's free the auth_token now */
|
||||||
|
@ -112,6 +112,7 @@ typedef struct mysql_auth
|
|||||||
bool skip_auth; /**< Authentication will always be successful */
|
bool skip_auth; /**< Authentication will always be successful */
|
||||||
bool check_permissions;
|
bool check_permissions;
|
||||||
bool lower_case_table_names; /**< Disable database case-sensitivity */
|
bool lower_case_table_names; /**< Disable database case-sensitivity */
|
||||||
|
bool log_password_mismatch; /**< Log password mismatches*/
|
||||||
uint64_t checksum;
|
uint64_t checksum;
|
||||||
} MYSQL_AUTH;
|
} MYSQL_AUTH;
|
||||||
|
|
||||||
@ -212,4 +213,7 @@ int validate_mysql_user(MYSQL_AUTH* instance,
|
|||||||
uint8_t* scramble,
|
uint8_t* scramble,
|
||||||
size_t scramble_len);
|
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
|
MXS_END_DECLS
|
||||||
|
Reference in New Issue
Block a user