CVE | CVE-2023-32571 |
CWE | CWE-184: Incomplete List of Disallowed Inputs |
CVSSv3.1 vector | AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N |
CVSSv3.1 base score | 9.1 |
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.
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.
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:
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).
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:
$> mkdir dynamic-linq-poc
$> cd dynamic-linq-poc
$> dotnet new console
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); } } }
$> dotnet add package System.Linq.Dynamic.Core --version 1.2.25
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.
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); } } }
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.
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<Item> 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:
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.
Overview This article explains how to write a custom ROP (Return Oriented Programming) chain to bypass Data Execution Prevention (DEP) on a Windows 10 system. DEP makes certain parts of memory (e.g., the stack) used by an application non-executable. This means that overwriting EIP with a “JMP ESP” (or similar)…
This executable blog post is the fourth in a series related to machine learning and is a fascinating trifecta involving hardened cryptography software, embedded IoT-type hardware, and deep machine learning techniques. While the AES algorithm is designed such that a brute-force secret key guessing attack would likely finish ‘sometime near…
This article is an attempt at cataloging all the types of bitcoin transaction locking scripts, their prevalence and their security implications. The data presented in this article was lifted directly from the bitcoin blockchain, which required custom code to quickly iterate over the entire blockchain (over 450 GB at the…