Review assemblies and types loaded from runtime-provided names
Matches methods that load assemblies from a path or from bytes (Assembly.LoadFrom(), LoadFile(), Load(byte[]), AssemblyLoadContext.LoadFromAssemblyPath(), LoadFromStream()), instantiate a type from assembly and type names (Activator.CreateInstance(assemblyName, typeName)), or, in a method receiving a string, combine Type.GetType(string) with Activator.CreateInstance(Type). Plugin systems legitimately do this, but a path or name that comes from a request, a configuration file, a database or a user-writable directory turns it into remote code execution.
This is CWE-470 Use of Externally-Controlled Input to Select Classes or Code and CWE-829 Inclusion of Functionality from Untrusted Control Sphere (review).
Flagged Code
This is CWE-470 Use of Externally-Controlled Input to Select Classes or Code and CWE-829 Inclusion of Functionality from Untrusted Control Sphere (review).
Flagged Code
public object LoadPlugin(string pluginPath, string typeName)
{
var asm = Assembly.LoadFrom(pluginPath); // flagged: path from caller
return Activator.CreateInstance(asm.GetType(typeName));
}
Fixed Codestatic readonly Dictionary<string, Func<IPlugin>> s_Plugins = new()
{
["csv"] = () => new CsvPlugin(),
["json"] = () => new JsonPlugin()
};
public IPlugin LoadPlugin(string name)
{
// The name maps to a closed set of known types: nothing is resolved by reflection.
if (!s_Plugins.TryGetValue(name, out var create)) throw new ArgumentException(name);
return create();
}How to fix
Load plugins only from a directory the application owns (not user-writable), verify a signature or hash before loading, and map user-provided names to a whitelist of known types instead of resolving them by reflection. Suppress the issue once the source of the path or name is confirmed trusted.
CQLinq source
This rule is written in CQLinq, C# LINQ over the NDepend code model. It ships with every new NDepend project: you can read it, adapt it, or use it as a starting point for your own custom rules.
// <Name>Review assemblies and types loaded from runtime-provided names</Name>
// <Id>ND3125:ReviewAssembliesAndTypesLoadedFromRuntimeProvidedNames</Id>
warnif count > 0
// Third-party members are part of the code model only when the application uses them.
let dynamicLoadMembers =
ThirdParty.Types.WithFullName("System.Reflection.Assembly").ChildMethods()
.Where(m => m.SimpleNameEqualsAny("LoadFrom", "LoadFile", "UnsafeLoadFrom", "LoadWithPartialName") ||
(m.SimpleName == "Load" && m.Name.Contains("Byte[]")))
.Concat(ThirdParty.Types.WithFullName("System.Runtime.Loader.AssemblyLoadContext").ChildMethods()
.Where(m => m.SimpleNameEqualsAny("LoadFromAssemblyPath", "LoadFromStream", "LoadFromNativeImagePath")))
.Concat(ThirdParty.Types.WithFullNameIn("System.Activator", "System.AppDomain").ChildMethods()
.Where(m => m.SimpleNameEqualsAny("CreateInstance", "CreateInstanceFrom", "CreateInstanceAndUnwrap", "CreateInstanceFromAndUnwrap") &&
m.Name.Contains("String,String"))) // (assemblyName, typeName) overloads
.ToHashSetEx()
// Type.GetType(string) alone is common; it becomes a dynamic instantiation when combined with
// Activator.CreateInstance(Type...) in a method that receives a string (the type name may flow in).
let typeFromName = ThirdParty.Types.WithFullNameIn("System.Type", "System.Reflection.Assembly").ChildMethods()
.Where(m => m.SimpleName == "GetType" && m.Name.StartsWith("GetType(String")).ToHashSetEx()
let createInstanceFromType = ThirdParty.Types.WithFullName("System.Activator").ChildMethods()
.Where(m => m.SimpleName == "CreateInstance" && m.Name.StartsWith("CreateInstance(Type")).ToHashSetEx()
where dynamicLoadMembers.Any() || (typeFromName.Any() && createInstanceFromType.Any())
from m in Application.Methods.UsingAny(dynamicLoadMembers.Concat(typeFromName))
let dynamicLoadMembersUsed = m.MethodsCalled.Where(x => dynamicLoadMembers.Contains(x)).ToArray()
let instantiatesTypeByName = m.Name.Contains("String") &&
m.MethodsCalled.Any(x => typeFromName.Contains(x)) &&
m.MethodsCalled.Any(x => createInstanceFromType.Contains(x))
where dynamicLoadMembersUsed.Length > 0 || instantiatesTypeByName
select new {
m,
dynamicLoadMembersUsed,
instantiatesTypeByName,
Debt = 20.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Expl>
// The *{0}* {2?resolves a type by name and instantiates it:loads code dynamically through {1}}.
// If the path, bytes or type name can be influenced by a user or by a
// writable location, arbitrary code runs inside the process.
//</Expl>
//<Desc>
// Matches methods that load assemblies from a path or from bytes
// (*Assembly.LoadFrom()*, *LoadFile()*, *Load(byte[])*,
// *AssemblyLoadContext.LoadFromAssemblyPath()*, *LoadFromStream()*),
// instantiate a type from assembly and type names (*Activator.CreateInstance(assemblyName, typeName)*),
// or, in a method receiving a string, combine *Type.GetType(string)* with
// *Activator.CreateInstance(Type)*. Plugin systems legitimately do this,
// but a path or name that comes from a request, a configuration file, a
// database or a user-writable directory turns it into remote code
// execution.
//
// This is CWE-470 *Use of Externally-Controlled Input to Select Classes
// or Code* and CWE-829 *Inclusion of Functionality from Untrusted Control
// Sphere* (review).
//
// **Flagged Code**
// ```csharp
// public object LoadPlugin(string pluginPath, string typeName)
// {
// var asm = Assembly.LoadFrom(pluginPath); // flagged: path from caller
// return Activator.CreateInstance(asm.GetType(typeName));
// }
// ```
//
// **Fixed Code**
// ```csharp
// static readonly Dictionary<string, Func<IPlugin>> s_Plugins = new()
// {
// ["csv"] = () => new CsvPlugin(),
// ["json"] = () => new JsonPlugin()
// };
// public IPlugin LoadPlugin(string name)
// {
// // The name maps to a closed set of known types: nothing is resolved by reflection.
// if (!s_Plugins.TryGetValue(name, out var create)) throw new ArgumentException(name);
// return create();
// }
// ```
//</Desc>
//<HowToFix>
// Load plugins only from a directory the application owns (not
// user-writable), verify a signature or hash before loading, and map
// user-provided names to a whitelist of known types instead of resolving
// them by reflection. Suppress the issue once the source of the path or
// name is confirmed trusted.
//</HowToFix>
Open in the interactive Rules Explorer · All default rules
Security: all 39 items
- Software Composition Analysis (SCA) ND3120
- Transitive Software Composition Analysis (SCA) ND3121
- Don't hard-code secrets in constants ND3114
- Review credentials built from hard-coded values ND3115
- Don't use insecure deserializers ND3110
- Don't use weak hash algorithms (MD5, SHA1) ND3109
- Don't use DES/3DES/RC2 weak cipher algorithms ND3102
- Don't use weak password-based key derivation ND3123
- Don't use the ECB encryption mode ND3132
- Don't disable certificate validation ND3103
- Don't hard-code TLS/SSL protocol versions ND3122
- Review commands vulnerable to SQL injection ND3107
- Review data adapters vulnerable to SQL injection ND3108
- Review raw SQL built from composed strings ND3128
- Review LDAP filters composed from strings ND3137
- Don't enable external XML entity resolution (XXE) ND3111
- Review processes started with a composed arguments string ND3124
- Review web endpoints building file paths from their parameters ND3129
- Review custom archive extraction (Zip Slip) ND3135
- Review assemblies and types loaded from runtime-provided names ND3125
- Review web endpoints without [Authorize] nor [AllowAnonymous] ND3118
- Review state-changing endpoints opened to anonymous callers ND3130
- Review state-changing MVC actions without anti-forgery validation ND3119
- Review HTML-encoding bypasses ND3133
- Review explicitly disabled request validation ND3134
- Review redirects to a URL that is not validated as local ND3126
- Review request-influenced outbound HTTP calls (SSRF) ND3136
- Review insecure ASP.NET Core CORS policies and unguarded developer exception page ND3116
- Review cookies created without HttpOnly and Secure flags ND3117
- Review regular expressions without a match timeout (ReDoS) ND3112
- Review secrets compared with non-constant-time equality ND3113
- Review secrets passed to logging methods ND3127
- Review security operations that log nothing ND3131
- Don't use System.Random for security purposes ND3101
- Don't use CoSetProxyBlanket and CoInitializeSecurity ND3100
- Review publicly visible event handlers ND3104
- Pointers should not be publicly visible ND3105
- Seal methods that satisfy non-public interfaces ND3106
- Restrict sensitive APIs to reviewed components ND3138