Skip to content

Commit

Permalink
By default the RestrictOrderByToPropertyOrField is now set to true in…
Browse files Browse the repository at this point in the history
… the ParsingConfig (#884)

* By default the RestrictOrderByToPropertyOrField is now set to true in the ParsingConfig

* .

* fix

* x

* if
  • Loading branch information
StefH authored Jan 26, 2025
1 parent bd4b8c7 commit 867d483
Show file tree
Hide file tree
Showing 9 changed files with 89 additions and 35 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,15 @@ If it's not possible to add that attribute, you need to implement a custom [Cust
Or provide a list of addtional types in the [DefaultDynamicLinqCustomTypeProvider.cs](https://github.com/zzzprojects/System.Linq.Dynamic.Core/blob/master/src/System.Linq.Dynamic.Core/CustomTypeProviders/DefaultDynamicLinqCustomTypeProvider.cs).

### v1.6.0-preview-01, 02, 03
A breaking change is introduced in this version to solve CVE-2024-51417.
#### Change 1
It's not allowed anymore to call any methods on the `object` type. By default also the `ToString` and `Equals` methods are not allowed.
To allow these methods set `AllowEqualsAndToStringMethodsOnObject` to `true` in the `ParsingConfig` and provide that config to all dynamic calls.
This is done to mitigate the risk of calling methods on the `object` type which could lead to security issues (CVE-2024-51417).

#### Change 2
By default the `RestrictOrderByToPropertyOrField` is now set to `true` in the `ParsingConfig`.
Which means that only properties and fields can be used in the `OrderBy` / `ThenBy`.
This is done to mitigate the risk of calling methods or other expressions in the `OrderBy` / `ThenBy` which could lead to security issues.

---

Expand Down
4 changes: 3 additions & 1 deletion src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -996,7 +996,9 @@ private Expression ParseIdentifier()

if (isValid && shouldPrioritizeType)
{
var keywordOrFunctionAllowed = !_usedForOrderBy || _usedForOrderBy && !_parsingConfig.RestrictOrderByToPropertyOrField;
var keywordOrFunctionAllowed =
!_usedForOrderBy ||
(_usedForOrderBy && (_keywordsHelper.IsItOrRootOrParent(keywordOrType) || !_parsingConfig.RestrictOrderByToPropertyOrField));
if (!keywordOrFunctionAllowed)
{
throw ParseError(Res.UnknownPropertyOrField, _textParser.CurrentToken.Text, TypeHelper.GetTypeName(_it?.Type));
Expand Down
2 changes: 2 additions & 0 deletions src/System.Linq.Dynamic.Core/Parser/IKeywordsHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ namespace System.Linq.Dynamic.Core.Parser;

internal interface IKeywordsHelper
{
bool IsItOrRootOrParent(AnyOf<string, Expression, Type> value);

bool TryGetValue(string text, out AnyOf<string, Expression, Type> value);
}
10 changes: 10 additions & 0 deletions src/System.Linq.Dynamic.Core/Parser/KeywordsHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,16 @@ public KeywordsHelper(ParsingConfig config)
}
}

public bool IsItOrRootOrParent(AnyOf<string, Expression, Type> value)
{
if (value.IsFirst)
{
return value.First is KEYWORD_IT or KEYWORD_ROOT or KEYWORD_PARENT or SYMBOL_IT or SYMBOL_PARENT or SYMBOL_ROOT;
}

return false;
}

public bool TryGetValue(string text, out AnyOf<string, Expression, Type> value)
{
// 1. Try to get as constant-expression, keyword, symbol or functions
Expand Down
6 changes: 3 additions & 3 deletions src/System.Linq.Dynamic.Core/ParsingConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -307,11 +307,11 @@ public IQueryableAnalyzer QueryableAnalyzer
public StringLiteralParsingType StringLiteralParsing { get; set; } = StringLiteralParsingType.Default;

/// <summary>
/// When set to <c>true</c>, the parser will restrict the OrderBy method to only allow properties or fields.
/// When set to <c>true</c>, the parser will restrict the OrderBy and ThenBy methods to only allow properties or fields. If set to <c>false</c>, any expression is allowed.
///
/// Default value is <c>false</c>.
/// Default value is <c>true</c>.
/// </summary>
public bool RestrictOrderByToPropertyOrField { get; set; }
public bool RestrictOrderByToPropertyOrField { get; set; } = true;

/// <summary>
/// When set to <c>true</c>, the parser will allow the use of the Equals(object obj), Equals(object objA, object objB), ReferenceEquals(object objA, object objB) and ToString() methods on the <see cref="object"/> type.
Expand Down
45 changes: 24 additions & 21 deletions test/System.Linq.Dynamic.Core.Tests/EntitiesTests.OrderBy.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Linq.Dynamic.Core.Exceptions;
using System.Linq.Dynamic.Core.Parser;
using System.Linq.Dynamic.Core.Tests.Helpers.Entities;
using FluentAssertions;
using Xunit;
Expand All @@ -7,57 +8,58 @@ namespace System.Linq.Dynamic.Core.Tests;

public partial class EntitiesTests
{
[Fact]
public void Entities_OrderBy_RestrictOrderByIsFalse()
[Theory]
[InlineData("IIF(1 == 1, 1, 0)")]
[InlineData("np(Name, \"x\")")]
public void Entities_OrderBy_RestrictOrderByIsFalse_ShouldAllowAnyExpression(string expression)
{
// Arrange
var config = new ParsingConfig { RestrictOrderByToPropertyOrField = false };

// Act
var resultBlogs = _context.Blogs.OrderBy(b => true).ToArray();
var dynamicResultBlogs = _context.Blogs.OrderBy("IIF(1 == 1, 1, 0)").ToDynamicArray<Blog>();
Action action = () => _ = _context.Blogs.OrderBy(config, expression).ToDynamicArray<Blog>();

// Assert
Assert.Equal(resultBlogs, dynamicResultBlogs);
action.Should().NotThrow();
}

[Fact]
public void Entities_OrderBy_RestrictOrderByIsTrue_ValidExpressionShouldNotThrow()
public void Entities_OrderBy_RestrictOrderByIsTrue_NonRestrictedExpressionShouldNotThrow()
{
// Arrange
var config = new ParsingConfig { RestrictOrderByToPropertyOrField = true };

// Act 1
var resultBlogs = _context.Blogs.OrderBy(b => b.Name).ToArray();
var dynamicResultBlogs = _context.Blogs.OrderBy(config, "Name").ToDynamicArray<Blog>();
var dynamicResultBlogs = _context.Blogs.OrderBy("Name").ToDynamicArray<Blog>();

// Assert 1
Assert.Equal(resultBlogs, dynamicResultBlogs);

// Act 2
var resultPosts = _context.Posts.OrderBy(p => p.Blog.Name).ToArray();
var dynamicResultPosts = _context.Posts.OrderBy(config, "Blog.Name").ToDynamicArray<Post>();
var dynamicResultPosts = _context.Posts.OrderBy("Blog.Name").ToDynamicArray<Post>();

// Assert 2
Assert.Equal(resultPosts, dynamicResultPosts);
}

[Fact]
public void Entities_OrderBy_RestrictOrderByIsTrue_InvalidExpressionShouldThrow()

[Theory]
[InlineData("IIF(1 == 1, 1, 0)")]
[InlineData("np(Name, \"x\")")]
public void Entities_OrderBy_RestrictOrderByIsTrue_RestrictedExpressionShouldThrow(string expression)
{
// Arrange
var config = new ParsingConfig { RestrictOrderByToPropertyOrField = true };

// Act
Action action = () => _context.Blogs.OrderBy(config, "IIF(1 == 1, 1, 0)");
Action action = () => _context.Blogs.OrderBy(expression);

// Assert
action.Should().Throw<ParseException>().WithMessage("No property or field 'IIF' exists in type 'Blog'");
action.Should().Throw<ParseException>().WithMessage("No property or field '*' exists in type 'Blog'");
}

[Fact]
public void Entities_OrderBy_NullPropagation_Int()
{
// Arrange
var config = new ParsingConfig { RestrictOrderByToPropertyOrField = false };
var resultBlogs = _context.Blogs.OrderBy(b => b.NullableInt ?? -1).ToArray();
var dynamicResultBlogs = _context.Blogs.OrderBy("np(NullableInt, -1)").ToArray();
var dynamicResultBlogs = _context.Blogs.OrderBy(config, "np(NullableInt, -1)").ToArray();

// Assert
Assert.Equal(resultBlogs, dynamicResultBlogs);
Expand All @@ -67,8 +69,9 @@ public void Entities_OrderBy_NullPropagation_Int()
public void Entities_OrderBy_NullPropagation_String()
{
// Arrange
var config = new ParsingConfig { RestrictOrderByToPropertyOrField = false };
var resultBlogs = _context.Blogs.OrderBy(b => b.X ?? "_").ToArray();
var dynamicResultBlogs = _context.Blogs.OrderBy("np(X, \"_\")").ToArray();
var dynamicResultBlogs = _context.Blogs.OrderBy(config, "np(X, \"_\")").ToArray();

// Assert
Assert.Equal(resultBlogs, dynamicResultBlogs);
Expand Down
12 changes: 9 additions & 3 deletions test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1384,6 +1384,8 @@ public void ExpressionTests_IsNull_ThrowsException()
[Fact]
public void ExpressionTests_Indexer_Issue57()
{
// Arrange
var config = new ParsingConfig { RestrictOrderByToPropertyOrField = true };
var rows = new List<JObject>
{
new JObject {["Column1"] = "B", ["Column2"] = 1},
Expand All @@ -1392,9 +1394,11 @@ public void ExpressionTests_Indexer_Issue57()
new JObject {["Column1"] = "A", ["Column2"] = 2}
};

// Act
var expected = rows.OrderBy(x => x["Column1"]).ToList();
var result = rows.AsQueryable().OrderBy(@"it[""Column1""]").ToList();
var result = rows.AsQueryable().OrderBy(config, @"it[""Column1""]").ToList();

// Assert
Assert.Equal(expected, result);
}

Expand Down Expand Up @@ -1727,14 +1731,15 @@ public void ExpressionTests_NullPropagation_Method_WithDefaultValue()
public void ExpressionTests_NullPropagating_DateTime()
{
// Arrange
var config = new ParsingConfig { RestrictOrderByToPropertyOrField = false };
var q = new[]
{
new { id = 1, date1 = (DateTime?) DateTime.Now, date2 = DateTime.Now.AddDays(-1) }
}.AsQueryable();

// Act
var result = q.OrderBy(x => x.date2).Select(x => x.id).ToArray();
var resultDynamic = q.OrderBy("np(date1)").Select("id").ToDynamicArray<int>();
var resultDynamic = q.OrderBy(config, "np(date1)").Select("id").ToDynamicArray<int>();

// Assert
Check.That(resultDynamic).ContainsExactly(result);
Expand All @@ -1744,14 +1749,15 @@ public void ExpressionTests_NullPropagating_DateTime()
public void ExpressionTests_NullPropagation_NullableDateTime()
{
// Arrange
var config = new ParsingConfig { RestrictOrderByToPropertyOrField = false };
var q = new[]
{
new { id = 1, date1 = (DateTime?) DateTime.Now, date2 = DateTime.Now.AddDays(-1)}
}.AsQueryable();

// Act
var result = q.OrderBy(x => x.date1.Value.Year).Select(x => x.id).ToArray();
var resultDynamic = q.OrderBy("np(date1.Value.Year)").Select("id").ToDynamicArray<int>();
var resultDynamic = q.OrderBy(config, "np(date1.Value.Year)").Select("id").ToDynamicArray<int>();

// Assert
Check.That(resultDynamic).ContainsExactly(result);
Expand Down
34 changes: 30 additions & 4 deletions test/System.Linq.Dynamic.Core.Tests/QueryableTests.OrderBy.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq.Dynamic.Core.Exceptions;
using System.Linq.Dynamic.Core.Parser;
using System.Linq.Dynamic.Core.Tests.Helpers.Models;
using FluentAssertions;
using Xunit;

namespace System.Linq.Dynamic.Core.Tests;
Expand Down Expand Up @@ -36,12 +38,13 @@ public int Compare(object? x, object? y)
public void OrderBy_Dynamic_NullPropagation_Int()
{
// Arrange
var config = new ParsingConfig { RestrictOrderByToPropertyOrField = false };
var testList = User.GenerateSampleModels(2);
var qry = testList.AsQueryable();

// Act
var orderBy = testList.OrderBy(x => x.NullableInt ?? -1).ToArray();
var orderByDynamic = qry.OrderBy("np(NullableInt, -1)").ToArray();
var orderByDynamic = qry.OrderBy(config, "np(NullableInt, -1)").ToArray();

// Assert
Assert.Equal(orderBy, orderByDynamic);
Expand All @@ -51,12 +54,13 @@ public void OrderBy_Dynamic_NullPropagation_Int()
public void OrderBy_Dynamic_NullPropagation_String()
{
// Arrange
var config = new ParsingConfig { RestrictOrderByToPropertyOrField = false };
var testList = User.GenerateSampleModels(2);
var qry = testList.AsQueryable();

// Act
var orderBy = testList.OrderBy(x => x.NullableString ?? "_").ToArray();
var orderByDynamic = qry.OrderBy("np(NullableString, \"_\")").ToArray();
var orderByDynamic = qry.OrderBy(config, "np(NullableString, \"_\")").ToArray();

// Assert
Assert.Equal(orderBy, orderByDynamic);
Expand All @@ -66,12 +70,13 @@ public void OrderBy_Dynamic_NullPropagation_String()
public void OrderBy_Dynamic_NullPropagation_NestedObject()
{
// Arrange
var config = new ParsingConfig { RestrictOrderByToPropertyOrField = false };
var testList = User.GenerateSampleModels(2);
var qry = testList.AsQueryable();

// Act
var orderBy = testList.OrderBy(x => x.Profile?.Age ?? -1).ToArray();
var orderByDynamic = qry.OrderBy("np(Profile.Age, -1)").ToArray();
var orderByDynamic = qry.OrderBy(config, "np(Profile.Age, -1)").ToArray();

// Assert
Assert.Equal(orderBy, orderByDynamic);
Expand All @@ -81,6 +86,7 @@ public void OrderBy_Dynamic_NullPropagation_NestedObject()
public void OrderBy_Dynamic_NullPropagation_NestedObject_Query()
{
// Arrange
var config = new ParsingConfig { RestrictOrderByToPropertyOrField = false };
var qry = User.GenerateSampleModels(2)
.Select(u => new
{
Expand All @@ -93,7 +99,7 @@ public void OrderBy_Dynamic_NullPropagation_NestedObject_Query()
.AsQueryable();

// Act
var orderByDynamic = qry.OrderBy("np(X.Age, -1)").ToArray();
var orderByDynamic = qry.OrderBy(config, "np(X.Age, -1)").ToArray();

// Assert
Assert.NotNull(orderByDynamic);
Expand Down Expand Up @@ -238,4 +244,24 @@ public void OrderBy_Dynamic_Exceptions()
Assert.Throws<ArgumentException>(() => qry.OrderBy(""));
Assert.Throws<ArgumentException>(() => qry.OrderBy(" "));
}

[Theory]
[InlineData(KeywordsHelper.KEYWORD_IT)]
[InlineData(KeywordsHelper.SYMBOL_IT)]
[InlineData(KeywordsHelper.KEYWORD_ROOT)]
[InlineData(KeywordsHelper.SYMBOL_ROOT)]
[InlineData("\"User\" + \"Name\"")]
[InlineData("\"User\" + \"Name\" asc")]
[InlineData("\"User\" + \"Name\" desc")]
public void OrderBy_RestrictOrderByIsTrue_NonRestrictedExpressionShouldNotThrow(string expression)
{
// Arrange
var queryable = User.GenerateSampleModels(3).AsQueryable();

// Act
Action action = () => _ = queryable.OrderBy(expression);

// Assert 2
action.Should().NotThrow();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Linq.Dynamic.Core.Exceptions;
using System.Linq.Dynamic.Core.Tests.Helpers.Models;
using System.Linq.Dynamic.Core.Tests.Helpers.Models;
using Xunit;

namespace System.Linq.Dynamic.Core.Tests
Expand Down

0 comments on commit 867d483

Please sign in to comment.