【问题标题】:Safe code execution on server side API with Roslyn使用 Roslyn 在服务器端 API 上安全执行代码
【发布时间】:2021-03-12 04:33:19
【问题描述】:

为了给我的用户更大的灵活性并让他们编写自己的表达式,我想让他们在文本字段中编写非常简单的 C# 语句,这些语句在服务器端执行以进行一些自定义计算。 我正在与 Roslyn 一起存档。
可以在here 找到一个很好的例子。

我让用户在评估函数中注入代码,如下所示:

        string codeToCompile = @"
        using System;
        using System.Collections.Generic;
        namespace Evaluator
        {
            public class Evaluator
            {
                public string Eval()
                {
                    " + {POTENTIALLY_DANGEROUS_CODE_GOES_HERE} + @"                      
                }
            }
        }";

您可以看到注入的代码总是在一个 Eval-Function 中,最后应该返回一个字符串。
用户可以决定如何计算这个字符串。
我现在正在考虑安全性,因为我无法控制注入的代码。

实际上我的用户应该只能:

  • 使用数学表达式
  • 原始变量
  • if 语句

因此,注入代码的示例可能如下所示:

int a = 5;
int b = 10;

if(a < b) 
{
   return "a is smaller";
}
else
{
   return "a is bigger or equal";
}

您可以在上面的示例代码中看到,命名空间仅限于“System”和“System.Collections.Generic”,所以很多东西都不再可能(比如从服务器的文件系统中读取一些东西)并将此信息作为字符串输出)

我还替换了所有出现的循环,因此 while、for、foreach 等表达式从字符串中删除。

但我仍然不确定这个解决方案是否安全。

  1. 潜在的攻击者现在还能做什么? (尤其是提供的两个命名空间的选项)
  2. 在这种情况下我可以采取什么最佳做法来防止攻击?

【问题讨论】:

标签: c# security roslyn code-injection roslyn-code-analysis


【解决方案1】:

根据您的需要,这非常很难做到。很难。 “如果你不得不问怎么做,你可能会不知所措”很难。一些有趣的事情要考虑:

  1. 仅仅因为您将命名空间限制在文件顶部并不意味着某人不能明确地将其代码 sn-p 中的某些内容限定为不同的命名空间。所以重要的是你必须遍历整个代码以查看是否有任何其他类型的用途。我不知道您允许的明确列表是否隐式禁止所有方法调用或对象创建。
  2. 小心假设系统中的任何内容都是安全的。考虑 System.Activator,它允许您调用 CreateInstance 并传入另一个类型的字符串名称来创建它。仅这种类型就可以让您绕过您可能进行的任何其他检查。当我按字母顺序在 System 命名空间中提取文档时,这只是跳出来的第一个!
  3. ...当然不只是专门阻止 System.Activator。每当您更新人们编写代码的框架时,都可能会出现有问题的新类型。

还要考虑您的潜在安全攻击类型:即使您无法写入文件,您是否仍会从您的服务器泄漏信息(如用户名或机器名称),这可能允许用户以其他方式侵入您的系统.或者他们只是编写一个消耗服务器资源的无限循环。您提到您将删除循环,但不要忘记诸如 goto 之类的事情,或者只是编写某种递归函数来执行堆栈溢出。

我不会说“只要做 X 就安全了”,因为我什至不相信自己会写那个。但是:

  1. 使用你的操作系统来帮助你隔离:在一个单独的进程中运行它,权限较少或没有,等等。如果你可以做一个单独的虚拟机/容器,那就太好了。在这里隔离得越多越好。
  2. 如果您要进行代码检查,请不要拒绝您知道不好的模式;而是编写只接受您知道是“安全”的模式的代码。这可能会导致大量工作来选择愚蠢的事情,但替代方案需要您列举所有不好的事情。

【讨论】:

  • 谢谢!我已经预料到很难归档一个安全的方法。我现在所做的是创建一个“允许条款”的白名单。代码只能包含此列表中的术语。其他一切都将停止验证。您对此有何看法: var whiteList = new List {"if","{","}",";","\\","+","-","*"," /","\"","","=","re​​turn","0","1","2","3","4 ","5","6","7","8","9","0","null","ToString","int","long","double","float", "char","string","else",".","var","new","DateTime","Parse","AddDays","ToUniversalTime","Date",":"}; 现在用户的选择非常有限......
  • 啊伙计...内联函数仍然可以是递归的。但这是我在这里发现的唯一问题...也许我从列表中删除 {}...
  • 即使这足以使进程因内存不足而崩溃,如果您执行string s1 = "hello"; string s2 = s1 + s1; string s3 = s2 + s2; string s4 = s3 + s3; string s5 = s4 + s4; 之类的操作足够长,直到最终分配的字符串大小达到千兆字节,这很容易使您的进程崩溃(或其他原因)在机器上。)
  • 我将代码的最大长度限制为 300 个字符 - 我也在单独的任务中运行它。如果任务花费的时间超过 5 秒,则将其取消。所以我现在也避免了各种循环/递归。任何其他想法,可以做什么? :-D
【解决方案2】:

也许您不需要 C# 代码?集成脚本语言有多种选择。它们将被解释并且速度较慢,但​​这通常不是问题。

例如,我可以推荐 https://github.com/sebastienros/jint 用于 JavaScript。 (我不隶属于该项目)它与托管 C# 代码具有良好的互操作性。它还具有针对“资源”攻击(如无限循环或过度内存消耗)的内置安全防护。

小小提醒:小心你允许什么样的 .net 互操作性,否则即使使用解释代码,你也可能会带来安全风险。

【讨论】:

    猜你喜欢
    • 2012-03-06
    • 1970-01-01
    • 2016-06-25
    • 1970-01-01
    • 1970-01-01
    • 2019-08-28
    • 2012-05-12
    • 2016-04-21
    • 1970-01-01
    相关资源
    最近更新 更多