如何在隔离环境执行动态表达式?防范未授权代码访问
动态表达式执行的安全隔离方案
我正在实现动态表达式调用功能,允许用户编写基于context参数的脚本片段,但需要构建安全机制防止未授权访问。合法的表达式应仅操作context的公开属性,比如:
context => context.State.Value1 + "c";
但用户可能编写恶意脚本,通过反射访问核心程序集或执行未授权逻辑:
// 通过context的类型反射遍历程序集 context => string.Join(", ", context.GetType().Assembly.GetTypes().Where(...)) // 直接获取当前执行程序集 context => Assembly.GetExecutingAssembly().GetTypes().Where(...)
关键词/方法黑名单检查完全不可行,因为未授权访问的实现方式太多,同时我不确定独立AppDomain是否能解决问题。
可行的安全隔离方案
1. 表达式树白名单验证,严格限制访问范围
在解析表达式树阶段,遍历所有节点,只允许访问预先定义的context成员和基础类型的安全方法:
- 仅放行
context及其嵌套对象的指定公开属性/字段(比如context.State.Value1),直接拒绝GetType()、GetHashCode()这类继承自object的危险方法。 - 限制可用类型:只允许
string、int、decimal等基础值类型,以及自定义的安全数据类型,Assembly、Type这类反射相关类型直接禁用。 - 静态方法仅放行明确允许的(比如
string.Concat),其余一概拒绝。
验证逻辑伪代码参考:
public bool ValidateExpressionSafety(Expression<Func<Context, object>> expr) { var checker = new SafeExpressionVisitor(); checker.Visit(expr.Body); return checker.IsSafe; } class SafeExpressionVisitor : ExpressionVisitor { public bool IsSafe { get; private set; } = true; protected override Expression VisitMember(MemberExpression node) { // 检查成员是否在预先定义的白名单内 if (!IsAllowedMember(node.Member)) { IsSafe = false; return base.VisitMember(node); } return base.VisitMember(node); } protected override Expression VisitMethodCall(MethodCallExpression node) { // 直接禁用反射相关方法 if (node.Method.DeclaringType == typeof(Type) || node.Method.DeclaringType == typeof(Assembly)) { IsSafe = false; return base.VisitMethodCall(node); } // 静态方法必须在白名单内才放行 if (node.Method.IsStatic && !IsAllowedStaticMethod(node.Method)) { IsSafe = false; return base.VisitMethodCall(node); } return base.VisitMethodCall(node); } }
2. 替换为带沙箱的脚本引擎,放弃原生表达式树
如果原生表达式树的安全限制成本太高,直接改用自带安全隔离的脚本引擎:
- Roslyn Scripting API:可配置脚本的引用集,仅添加必要的程序集(比如只引用包含
Context类型的程序集和基础类库),同时禁用反射权限。
简单配置示例:var scriptOptions = ScriptOptions.Default .AddReferences(typeof(Context).Assembly) // 仅添加必要程序集 .AddImports("YourApp.ContextNamespace") .WithMetadataResolver(new RestrictedMetadataResolver()); // 自定义解析器限制类型访问 - IronPython/IronRuby:这类动态语言引擎自带沙箱机制,可严格限制脚本能访问的类型和方法,完全隔离主程序集的内部逻辑。
3. AppDomain隔离(仅限.NET Framework,新框架不适用)
如果基于.NET Framework开发,AppDomain可实现隔离:
- 创建权限受限的
AppDomain,仅授予SecurityPermissionFlag.Execution权限,禁用反射、文件访问等危险权限。 - 将
Context标记为[Serializable]或实现MarshalByRefObject,跨AppDomain传递时仅传递必要数据,而非对象本身的引用。 - 注意:.NET Core及以后版本已移除
AppDomain,此方案仅适用于旧框架。
4. DTO隔离,避免直接暴露业务对象
不要将业务逻辑中的Context直接暴露给用户脚本,创建只读的DTO(数据传输对象):
- DTO仅包含用户需要操作的属性,除了getter外无任何方法。
- 可将DTO封装为结构体(不继承
object的危险方法),或在表达式验证阶段直接拒绝调用object的方法。 - 这样用户脚本仅能访问DTO的指定属性,无法通过反射获取主程序集的任何信息。
总结
优先推荐表达式树白名单验证+DTO隔离的组合方案,既能保证原生表达式树的执行效率,又能严格限制访问范围;如果用户脚本逻辑复杂或需要更强隔离,可改用沙箱脚本引擎。AppDomain仅适用于.NET Framework,新项目无需考虑。
内容的提问来源于stack exchange,提问作者Xeo
相关产品推荐
相关产品推荐

