-
Notifications
You must be signed in to change notification settings - Fork 255
Description
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));
}