You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在隔离环境执行动态表达式?防范未授权代码访问

动态表达式执行的安全隔离方案

我正在实现动态表达式调用功能,允许用户编写基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 19:25:49