MXS-2457 Treat string args as fields

The masking filter will now consider all string arguments to
functions to be fields. This in order to prevent bypassing of
the masking with

    > set @@sql_mode='ANSI_QUOTES';
    > select concat("ssn") from masking;

This may lead to false positives, but no can do.
This commit is contained in:
Johan Wikman
2019-05-02 14:16:30 +03:00
parent f09d46c8e6
commit 3a5a8b13b9
5 changed files with 119 additions and 9 deletions

View File

@ -96,6 +96,26 @@ Please see the configuration parameter
[require_fully_parsed](#require_fully_parsed)
for how to change the default behaviour.
From MaxScale 2.3.7 onwards, the masking filter will treat any strings
passed to functions as if they were fields. The reason is that as the
MaxScale query classifier is not aware of whether `ANSI_QUOTES` is
enabled or not, it is possible to bypass the masking by turning that
option on.
```
mysql> set @@sql_mode = 'ANSI_QUOTES';
mysql> select concat("ssn") from managers;
```
Before this change, the content of the field `ssn` would have been
returned in clear text even if the column should have been masked.
Note that this change will mean that there may be false positives
if `ANSI_QUOTES` is not enabled and a string argument happens to
be the same as the name of a field to be masked.
Please see the configuration parameter
[treat_string_arg_as_field(#treat_string_arg_as_field)
for how to change the default behaviour.
## Limitations
The masking filter can _only_ be used for masking columns of the following
@ -215,6 +235,17 @@ Note that if this parameter is set to false, then `prevent_function_usage`,
less effective, as it with a statement that can not be fully parsed may be
possible to bypass the protection that they are intended to provide.
#### `treat_string_arg_as_field`
This optional parameter specifies how the masking filter should treat
strings used as arguments to functions. If true, they will be handled
as fields, which will cause fields to be masked even if `ANSI_QUOTES` has
been enabled and `"` is used instead of backtick.
```
treat_string_arg_as_field=false
```
The default value is `true`.
#### `check_user_variables`
This optional parameter specifies how the masking filter should

View File

@ -145,6 +145,12 @@ extern "C" MXS_MODULE* MXS_CREATE_MODULE()
Config::require_fully_parsed_default,
MXS_MODULE_OPT_NONE,
},
{
Config::treat_string_arg_as_field_name,
MXS_MODULE_PARAM_BOOL,
Config::treat_string_arg_as_field_default,
MXS_MODULE_OPT_NONE
},
{MXS_END_MODULE_PARAMS}
}
};

View File

@ -17,14 +17,15 @@
namespace
{
const char config_name_check_subqueries[] = "check_subqueries";
const char config_name_check_unions[] = "check_unions";
const char config_name_check_user_variables[] = "check_user_variables";
const char config_name_large_payload[] = "large_payload";
const char config_name_prevent_function_usage[] = "prevent_function_usage";
const char config_name_require_fully_parsed[] = "require_fully_parsed";
const char config_name_rules[] = "rules";
const char config_name_warn_type_mismatch[] = "warn_type_mismatch";
const char config_name_check_subqueries[] = "check_subqueries";
const char config_name_check_unions[] = "check_unions";
const char config_name_check_user_variables[] = "check_user_variables";
const char config_name_large_payload[] = "large_payload";
const char config_name_prevent_function_usage[] = "prevent_function_usage";
const char config_name_require_fully_parsed[] = "require_fully_parsed";
const char config_name_rules[] = "rules";
const char config_name_warn_type_mismatch[] = "warn_type_mismatch";
const char config_name_treat_string_arg_as_field[] = "treat_string_arg_as_field";
const char config_value_abort[] = "abort";
@ -105,7 +106,11 @@ const char* MaskingFilterConfig::check_subqueries_default = config_value_true;
const char* MaskingFilterConfig::require_fully_parsed_name = config_name_require_fully_parsed;
const char* MaskingFilterConfig::require_fully_parsed_default = config_name_require_fully_parsed;
/*
* PARAM treat_string_arg_as_field
*/
const char* MaskingFilterConfig::treat_string_arg_as_field_name = config_name_treat_string_arg_as_field;
const char* MaskingFilterConfig::treat_string_arg_as_field_default = config_value_true;
/*
* MaskingFilterConfig
*/
@ -161,3 +166,9 @@ bool MaskingFilterConfig::get_require_fully_parsed(const MXS_CONFIG_PARAMETER* p
{
return config_get_bool(pParams, require_fully_parsed_name);
}
// static
bool MaskingFilterConfig::get_treat_string_arg_as_field(const MXS_CONFIG_PARAMETER* pParams)
{
return config_get_bool(pParams, treat_string_arg_as_field_name);
}

View File

@ -57,6 +57,9 @@ public:
static const char* require_fully_parsed_name;
static const char* require_fully_parsed_default;
static const char* treat_string_arg_as_field_name;
static const char* treat_string_arg_as_field_default;
MaskingFilterConfig(const char* zName, const MXS_CONFIG_PARAMETER* pParams)
: m_name(zName)
, m_large_payload(get_large_payload(pParams))
@ -67,6 +70,7 @@ public:
, m_check_unions(get_check_unions(pParams))
, m_check_subqueries(get_check_subqueries(pParams))
, m_require_fully_parsed(get_require_fully_parsed(pParams))
, m_treat_string_arg_as_field(get_treat_string_arg_as_field(pParams))
{
}
@ -119,6 +123,11 @@ public:
return m_require_fully_parsed;
}
bool treat_string_arg_as_field() const
{
return m_treat_string_arg_as_field;
}
void set_large_payload(large_payload_t l)
{
m_large_payload = l;
@ -158,6 +167,11 @@ public:
m_require_fully_parsed = b;
}
void set_treat_string_arg_as_field(bool b)
{
m_treat_string_arg_as_field = b;
}
bool is_parsing_needed() const
{
return prevent_function_usage() || check_user_variables() || check_unions() || check_subqueries();
@ -171,6 +185,7 @@ public:
static bool get_check_unions(const MXS_CONFIG_PARAMETER* pParams);
static bool get_check_subqueries(const MXS_CONFIG_PARAMETER* pParams);
static bool get_require_fully_parsed(const MXS_CONFIG_PARAMETER* pParams);
static bool get_treat_string_arg_as_field(const MXS_CONFIG_PARAMETER* pParams);
private:
std::string m_name;
@ -182,4 +197,5 @@ private:
bool m_check_unions;
bool m_check_subqueries;
bool m_require_fully_parsed;
bool m_treat_string_arg_as_field;
};

View File

@ -48,6 +48,46 @@ GWBUF* create_parse_error_response()
return create_error_response(zMessage);
}
class EnableOption
{
public:
EnableOption(const EnableOption&) = delete;
EnableOption& operator=(const EnableOption&) = delete;
EnableOption(uint32_t option)
: m_option(option)
, m_options(0)
, m_disable(false)
{
if (m_option)
{
m_options = qc_get_options();
if (!(m_options & m_option))
{
uint32_t options = (m_options | m_option);
MXB_AT_DEBUG(bool rv = )qc_set_options(options);
mxb_assert(rv);
m_disable = true;
}
}
}
~EnableOption()
{
if (m_disable)
{
MXB_AT_DEBUG(bool rv = )qc_set_options(m_options);
mxb_assert(rv);
}
}
private:
uint32_t m_option;
uint32_t m_options;
bool m_disable;
};
}
MaskingFilterSession::MaskingFilterSession(MXS_SESSION* pSession, const MaskingFilter* pFilter)
@ -127,6 +167,9 @@ bool MaskingFilterSession::check_textual_query(GWBUF* pPacket)
{
bool rv = false;
uint32_t option = m_filter.config().treat_string_arg_as_field() ? QC_OPTION_STRING_ARG_AS_FIELD : 0;
EnableOption enable(option);
if (qc_parse(pPacket, QC_COLLECT_FIELDS | QC_COLLECT_FUNCTIONS) == QC_QUERY_PARSED
|| !m_filter.config().require_fully_parsed())
{
@ -166,6 +209,9 @@ bool MaskingFilterSession::check_binary_query(GWBUF* pPacket)
{
bool rv = false;
uint32_t option = m_filter.config().treat_string_arg_as_field() ? QC_OPTION_STRING_ARG_AS_FIELD : 0;
EnableOption enable(option);
if (qc_parse(pPacket, QC_COLLECT_FIELDS | QC_COLLECT_FUNCTIONS) == QC_QUERY_PARSED
|| !m_filter.config().require_fully_parsed())
{