防止LINQ注入

本文关键字:注入 LINQ 防止 | 更新日期: 2023-09-27 18:22:14

我有一组对象,我希望用户用LINQ对对象编写自定义查询。目前,我让用户在类似的文本框中输入文本

from t in tests where t.Name.EndsWith("st") select t

然后我将该文本传递给LINQ"编译器",后者将该字符串作为输入并动态生成一个类。代码:

using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CSharp;
namespace SecureLinqForUser
{
    internal static class LinqCompiler
    {
        public static Type Compile(string linq)
        {
            var csc = new CSharpCodeProvider(new Dictionary<string, string> {{"CompilerVersion", "v3.5"}});
            var parameters = new CompilerParameters(new[] {"mscorlib.dll", "System.Core.dll"}, "compiledlinq.dll", true)
            {
                GenerateExecutable = false,
                GenerateInMemory = true
            };
            parameters.ReferencedAssemblies.Add(typeof (LinqCompiler).Assembly.Location);
            parameters.CompilerOptions += " /platform:x64 ";
            var results = csc.CompileAssemblyFromSource(parameters,
                @"
            using System.Linq;
            using SecureLinqForUser;
            using System.Collections.Generic;
            class Linqed 
            {
              public IEnumerable<Test> Query(Test[] tests) 
              {
                IEnumerable<Test> list = " + linq + @";
                return list;
              }
            }");
            results.Errors.Cast<CompilerError>().ToList().ForEach(error => Console.WriteLine(error.ErrorText));
            return results.CompiledAssembly.GetType("Linqed");
        }
    }
}

使用给定的"编译器",不受信任的用户可以输入之类的内容

new List<Test>();
// some malicious code here, not LINQ at all

因为没有检查输入的文本实际上是LINQ。与SQL注入类似,我们称之为LINQ注入。

因此,我主要关心的是使代码更加安全。例如,是否有一种方法可以预先解析文本,以确保它只包含一个LINQ查询

出于SSCCE的目的,还可以找到代码的其余部分:

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
namespace SecureLinqForUser
{
    internal class Program
    {
        private static void Main()
        {
            Test[] tests =
            {
                new Test("Unit test"), new Test("System test"), new Test("Exploratory test"), new Test("Something"), new Test("Else")
            };
            var compile = LinqCompiler.Compile("from t in tests where t.Name.EndsWith('"st'") select t;");
            object obj = Activator.CreateInstance(compile);
            var list = (IEnumerable<Test>) compile.InvokeMember("Query",
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.InvokeMethod,
                null, obj, new[] {tests});
            var sb = new StringBuilder();
            foreach (var test in list)
            {
                sb.AppendLine(test.Name);
            }
            Console.WriteLine(sb.ToString());
            Console.ReadLine();
        }
    }
    public class Test
    {
        public string Name;
        public Test(string v)
        {
            Name = v;
        }
    }
}

防止LINQ注入

我的第一个倾向是利用linq表达式树,因为从定义上讲,它们是一个单独的表达式,并且有编译表达式以实现高效重用的功能。但这并不能保证这个表达不会伸出援手,做出任何有害或无意的事情。

我的猜测是,您必须编写一个解析器来形成DSL,DSL是您想要向最终用户公开的特性的子集。我的猜测是,由于Roslyn是开源的,与其他情况相比,利用它使Roslyn编译器在你不想支持的功能上失败可能需要更少的努力。

另一个考虑因素是添加一些运行时检查,方法是在单独的AppDomain中加载动态代码,并处理程序集解析和其他可能的挂钩,以防止执行代码查看其他不可用的框架功能或外部程序集。

进一步的步骤可以是将代码放置在一个对系统访问受限的单独进程中,也许可以将其放置在docker容器中,然后在自定义通信通道上与之通信。