目前,我正在使用ASP.NET Core 2.2进行一个Web项目,并且正在做一些研究,以使应用程序可扩展以适应将来的更改,而无需始终构建和部署新版本。 因此,我的想法是在Web应用程序中实现某种插件系统,并在应用程序仍在运行的情况下以“热插拔”模式动态加载新功能。
我确实成功找到了ApplicationPart和ApplicationPartManager,从而可以从外部来源注入零件。
实际上,我没有发现这种方法的安全性。假设我的网络应用带有安装程序,可以共享。插件编程将是使用该应用程序的社区的一个开放选项。
动态加载应用程序部件有多安全? 是否存在注入作为插件提供的恶意代码的风险? 可以以某种方式弥补这些风险吗?
编辑: 看来我没有清楚地表达我的问题。
是否可以通过这种方法使应用程序崩溃?
是否有可能以不再可用的方式破坏或更改数据库?
是否可以注入将可能的敏感数据发送到第三方端点的插件代码,例如身份信息?
最后,是否可以限制应用程序中的“访问”插件代码?比如让它仅查询与插件相关的数据库表?
答案 0 :(得分:2)
我同意Fildor,“安全”的定义尚待讨论。尽管您已经很好地编辑了一些问题的帖子,但这使我们有话要说。
首先,让我们先说一下,只要CLR加载了包含ApplicationPart
的程序集,它们就会被加载到应用程序的默认AppDomain
中。现在我们可以讨论.NET Core是否包含AppDomain
。这里的重点是要了解已加载的程序集都能够相互通信。因此,现在我们知道了这一点,并且主应用程序(webapp)会在您的自定义(插件)程序集中调用 some 代码,让我们看一下您的问题:
是否可以通过这种方法使应用程序崩溃?
是的。可以说,每当插件加载时,就会执行以下代码:
public void Run()
{
this.Run();
}
这将导致stackoverflow异常,该异常无法由用户代码处理,并且默认情况下CLR将终止该进程,这意味着您的应用程序将崩溃。
是否有可能以不再可用的方式破坏或更改数据库?并且有可能注入将可能的敏感数据发送到第三方端点的插件代码,例如身份信息?
我将这些问题归为本质,您想知道的是:动态加载和调用的代码的局限性和功能是什么?
答案是一切。他们可以访问您的所有程序集,也可以访问您的DatabaseManager
类型(例如)。可以从插件中实例化此类型,然后使用它来操作数据库。除此之外,他们可以执行.NET框架提供的任何功能,如果他们想向网站发送内容,则可以轻松地实例化WebClient
并发出请求。
让我们忘记ApplicationPart
功能,并想象我们创建了自己的插件系统,这是我的主要应用程序:
class Program
{
private static string secretUser = "SecretUser";
static void Main(string[] args)
{
Console.WriteLine($" > Hello {secretUser}");
LoadPlugins();
Console.WriteLine($" > System fully loaded! Hello again {secretUser}");
}
}
LoadPlugins
将搜索当前正在执行的程序集的目录以发现动态链接的库(DLL),并将尝试使用某些数据协定来加载这些库。假设合同强制一个插件具有一个void Run
。我可以在此方法中包含以下代码来修改原始应用程序,例如,像这样修改secretUser
变量:
public void Run()
{
var mainClass = System.AppDomain.CurrentDomain.GetAssemblies()
.Select(asm => asm.EntryPoint?.DeclaringType)
.Single(x => x?.Name == "Program");
var fieldDef = mainClass
.GetField("secretUser",
BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Static);
fieldDef.SetValue(mainClass, "HackerPlugin");
}
现在,每当代码运行时,首先显示SecuretUser
,但是每当加载恶意插件时,应用程序都会显示HackerPlugin
,因为该插件使用反射来访问main的实例。应用程序并修改其内容。这听起来像是一个安全问题,但实际上,这是设计使然,它使.NET生态系统像今天一样流行,因为它解决了许多其他我们需要处理的问题。
基本上,如果您担心安全性,则不应该使用ApplicationPart
方法,因为您的代码将与插件共享(并因此可以访问)。
您想要的是将插件代码与应用程序的代码隔离开。这可以通过以下任一方法来实现:使用多个AppDomain
(这仍可能导致进程终止)或使用多个进程并使这些进程相互通信。进程是独立的,不能(默认)互相读取其他内存区域。
请注意,ApplicationPart
为您完成了很多工作,例如发现并挑战(外部)依赖项的加载,如果您决定构建自定义解决方案,则还需要自己构建它-这并不意味着您可以查看.NET存储库并使用其解决方案作为参考。