【问题标题】:Preventing LINQ-injection防止 LINQ 注入
【发布时间】:2015-11-17 22:28:33
【问题描述】:

我有一组对象,我希望用户在 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;
        }
    }
}

【问题讨论】:

  • 你可以只使用 OData 吗?这样可以避免问题,但您可以限制他们查询的内容。
  • @johnny5:我处于早期开发阶段,所以是的,我基本上可以切换到完全不同的东西。到目前为止,我认为 OData 是基于网络的。我有一个独立的桌面应用程序。由于我的用户熟悉 SQL,因此查询语法不应离 SQL 太远。
  • 您是否考虑过编写自己的自定义 QueryProvider?这样您就可以拒绝任何您不希望他们使用的结构。
  • @BradfordDillon:不是这样。当我看到 this list 我必须自己实现的事情时,我停止了研究。如果我理解正确,这意味着分别实现每个关键字(这些文章并不短)。
  • OData 可以在桌面应用程序中轻松使用,它具有与 SQL 类似的语法,但除此之外,您可以在 sql 中为它们创建一个用户,该用户只有某些特权,这至少有助于阻止恶意软件攻击

标签: c# linq linq-to-objects


【解决方案1】:

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

我的猜测是,您必须编写一个解析器来形成一个 DSL,它是您想要向最终用户公开的功能的子集。我的猜测是,由于 Roslyn 是开源的,因此可能会比利用它来基本上让 Roslyn 编译器在您不想支持的功能上失败。

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-12
    • 2011-03-03
    • 1970-01-01
    • 2017-09-05
    • 2018-03-23
    • 1970-01-01
    相关资源
    最近更新 更多