我正在编写一个可扩展的服务器应用程序。我想公开一个库,第三方可以使用它来编写服务器应用程序的扩展。当我加载第三方程序集时,如果程序集使用反射,我希望阻止加载它。
目标是允许第三方库开发人员实现服务器接口,服务器将执行这些实现,但如果使用反射,则会阻止加载程序集。
这样做的目的是在将第三方程序集加载到服务器时保持合理的安全级别。即,第三方程序集应受到限制,并且不能使用反射来访问应用程序的其他部分。
我读过关于集会证据的文章,但我不知道这是否能让我收集到我需要知道的信息。
当我加载第三方程序集时,如果程序集使用反射,我想阻止加载它
你遇到了一个没有好解决方案的难题。没有一个好的方法来验证某个图书馆将要做什么。你可以实现一些启发法(如下(,但可能有足够积极的人可以找到绕过这些检查的方法。以下内容适用于dotnet核心,但可以在dotnet框架上进行一些小的更改。
此代码执行两个分析:
- 检查引用了哪些程序集
- 查看被禁止程序集中调用的方法的IL操作码
;明显的";方法是调用assembly.GetReferencedAssemblies()
并检查System.Reflection
是否存在,但实际上这是失败的,因为反射是由System.Runtime
提供的,并且并没有单独的组件用于反射。但您至少可以使用该过程来检查其他程序集。
因此,似乎需要对源代码进行更仔细的检查。使用Mono项目调试器库Mono.Cecil
。请参阅此问题以获取相关示例。
ILSpy可能有助于确定检查方式/检查内容。
我创建了两个虚拟项目进行测试。这些是.net标准库,编译成dll后放在主运行时文件夹中。这些将在运行时进行检查,而不提供对程序集的硬引用。
项目1——可能会做一些你不想做的事情,但至少它没有使用反射!
using System.Net.Sockets;
namespace LibrarySafe
{
public class ModuleLibrarySafe
{
public bool DoSomething(string args)
{
new TcpClient(args.Split(',')[0], int.Parse(args.Split(',')[1])).GetStream().Write(System.Text.Encoding.ASCII.GetBytes("yeah"), 0, 5);
return true;
}
}
}
项目2-使用反射:
using System.Reflection;
namespace LibraryUnsafe
{
public class ModuleLibraryUnsafe
{
public bool DoSomething(string args)
{
return Assembly.GetExecutingAssembly().DefinedTypes.Any(x => x.FullName == args);
}
}
}
以下是确定是否使用反思的一种缓解策略(而非解决方案(。Console.WriteLine
部分表示与被禁止的命名空间匹配。我在需要根据您的用例进行扩展/调整的领域留下了一些评论。以下内容不完整,不包括您需要考虑的一些IL(我为我所知道的缺失留下了评论;也许还有更多我不知道的内容(。
最后,这可能需要一些异常处理,并且可能需要某种类型的运行时缓存或哈希集来跟踪以前看到的方法调用。
祝你好运。
使用nuget包System.Reflection.MetadataLoadContext
using Mono.Cecil;
using System.Net.NetworkInformation;
using System.Reflection;
using System.Reflection.Metadata;
using System.Runtime.InteropServices;
namespace FindReferencedAssemblies
{
internal class Program
{
// List of namespaces that will be checked against.
private static List<string> _unsafeAssemblyNames = new List<string>()
{
"System.Reflection",
};
static void Main(string[] args)
{
string prefix = Directory.GetCurrentDirectory();
// need to pick up required/running dotnet libraries or PathAssemblyResolver will fail.
var requiredAssemblyFilesnames = new List<string>(Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.dll"));
// now add assemblies to check. This would come from your runtime module to load but is hard coded here.
var toCheckAssemblyFilesnames = new List<string>()
{
Path.Combine(prefix, "LibrarySafe.dll"),
Path.Combine(prefix, "LibraryUnsafe.dll"),
};
var resolver = new PathAssemblyResolver(requiredAssemblyFilesnames.Concat(toCheckAssemblyFilesnames));
// dotnetcore: assembly metadata is only available withing this "using" context
using (var metadataContext = new MetadataLoadContext(resolver))
{
foreach (var filename in toCheckAssemblyFilesnames)
{
// dotnet framework: use Assembly.ReflectionOnlyLoad
var assembly = metadataContext.LoadFromAssemblyPath(filename);
var assemblyShortName = assembly.GetName().Name;
var assemblyFullName = assembly.FullName;
// Check all referenced assemblies against the list of banned namespaces.
var referencedAssemblies = assembly.GetReferencedAssemblies().ToList();
foreach (var assemblyReference in referencedAssemblies)
{
var unsafeAsm = _unsafeAssemblyNames.FirstOrDefault(x => string.Compare(x, assemblyReference.Name) == 0);
if (!string.IsNullOrEmpty(unsafeAsm))
{
Console.WriteLine($"Found unsafe reference to [{unsafeAsm}] in assembly [{assemblyFullName}]");
}
}
// Now switch over to looking at method calls within the assembly.
var cecilAssemblyDefinition = Mono.Cecil.AssemblyDefinition.ReadAssembly(filename);
// Get a list of all types defined within the assembly.
var assemblyTypes = cecilAssemblyDefinition.MainModule.GetTypes()
// here, `StartsWith` may or may not be sufficient
.Where(x => x.FullName.StartsWith(assemblyShortName));
foreach (var type in assemblyTypes)
{
// Get a list of all methods defined on the type.
foreach (var method in type.Methods)
{
// Find references to methods called, from within the method we are considering.
var calledMethods = method.Body.Instructions
.Where(x =>
x.OpCode == Mono.Cecil.Cil.OpCodes.Call
&& x.Operand is MethodReference)
.Select(x => x.Operand)
.Cast<MethodReference>()
.ToList();
// TODO: perform the same check against `Operand is MethodDefinition`
// TODO: perform the same two checks against `x.OpCode == Mono.Cecil.Cil.OpCodes.Callvirt`
// Iterate the list of methods called, and compare against the list of banned namespaces.
foreach (var methodRef in calledMethods)
{
// here, `StartsWith` may or may not be sufficient
var unsafeAsmMatch = _unsafeAssemblyNames.Where(x => methodRef.FullName.StartsWith(x));
foreach (var match in unsafeAsmMatch)
{
Console.WriteLine($"Found unsafe reference to [{match}] in assembly [{assemblyFullName}], method [{method.FullName}]");
}
}
}
}
}
}
}
}
}