Skip to content

The parameter of the Regex.IsMatch method is not escaped #3292

@hostage2222

Description

@hostage2222

Usually, constants are escaped when converting to SQL (the example uses the table injection_regex_table with a single field "field", and as a constant, a simple SQL injection is used). The expression

query.Where(entity => entity.Field == "';delete from injection_regex_table;select '")

is converted into:

SELECT i.field
FROM injection_regex_table AS i
WHERE i.field = ''';delete from injection_regex_table;select '''

But when using Regex.IsMatch (and possibly some other functions), this is not the case. The expression

query.Where(entity => Regex.IsMatch(entity.Field,
    "';delete from injection_regex_table;select '",
    RegexOptions.IgnoreCase))

is converted into:

SELECT i.field
FROM injection_regex_table AS i
WHERE i.field ~* '(?p)';delete from injection_regex_table;select ''

The behavior changed when upgrading from version 7.0.18 to version 8.0.0-preview.1; in version 7.0.18, this same expression was still being escaped:

SELECT i.field
FROM injection_regex_table AS i
WHERE i.field ~ ('(?ip)' || ''';delete from injection_regex_table;select ''')

Potentially, this can create issues when generating Expression in the code if it's done incorrectly (using Expression.Constant). An example can be found here: https://github.com/hostage2222/testdbregex/tree/8.0.0-preview.1. For version 7.0.18, check the branch https://github.com/hostage2222/testdbregex/tree/7.0.18 or just change version in the project. You need a running PostgreSQL instance with default port and credentials or you need to specify them directly in the code. Upon execution, the public.injection_regex_table table is created.
This doesn't seem like a serious security issue, but getting an SQL injection without using raw SQL is rather unpleasant. This actually happened in our project when migrating from .NET 6 to .NET 8; we are now using the following code (which also helped us avoid memory leaks due to cache misses when converting Expression -> SQL inside EF Core):

private class FakeFieldClass<TValue>
{
    public TValue Value = default!;
}

public static MemberExpression CreateArgument<TValue>(TValue value)
{
    var fakeField = new FakeFieldClass<TValue> { Value = value };
    return Expression.Field(Expression.Constant(fakeField), nameof(fakeField.Value));
}

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions