c#在线编译和执行安全问题

本文关键字:安全 问题 执行 在线 编译 | 更新日期: 2023-09-27 17:58:49

所以我想写一个在线的c#编译器和执行环境。当然,第一个问题是安全性。我最终为用户代码创建了一个有特权的应用程序域,并在一个严格监控cpu和内存消耗的新进程中启动它。标准控制台应用程序命名空间可用。所以我的问题是:你能想出以某种方式打破某些东西的方法吗?你可以在网上当场试试你的想法。

Edit2如果有人关心代码,现在有了这个项目的开源分支:github的rextester

Edit1作为对其中一条注释的回应,下面是一些代码示例。

因此,基本上您创建了一个控制台应用程序。我只发布一大块:

class Sandboxer : MarshalByRefObject
{
    private static object[] parameters = { new string[] { "parameter for the curious" } };
    static void Main(string[] args)
    {
        Console.OutputEncoding = Encoding.UTF8;
        string pathToUntrusted = args[0].Replace("|_|", " ");
        string untrustedAssembly = args[1];
        string entryPointString = args[2];
        string[] parts = entryPointString.Split(new string[] { "|" }, StringSplitOptions.RemoveEmptyEntries);
        string name_space = parts[0];
        string class_name =  parts[1];
        string method_name = parts[2];
        //Setting the AppDomainSetup. It is very important to set the ApplicationBase to a folder 
        //other than the one in which the sandboxer resides.
        AppDomainSetup adSetup = new AppDomainSetup();
        adSetup.ApplicationBase = Path.GetFullPath(pathToUntrusted);
        //Setting the permissions for the AppDomain. We give the permission to execute and to 
        //read/discover the location where the untrusted code is loaded.
        PermissionSet permSet = new PermissionSet(PermissionState.None);
        permSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));

        //Now we have everything we need to create the AppDomain, so let's create it.
        AppDomain newDomain = AppDomain.CreateDomain("Sandbox", null, adSetup, permSet, null);

        //Use CreateInstanceFrom to load an instance of the Sandboxer class into the
        //new AppDomain. 
        ObjectHandle handle = Activator.CreateInstanceFrom(
            newDomain, typeof(Sandboxer).Assembly.ManifestModule.FullyQualifiedName,
            typeof(Sandboxer).FullName
            );
        //Unwrap the new domain instance into a reference in this domain and use it to execute the 
        //untrusted code.
        Sandboxer newDomainInstance = (Sandboxer)handle.Unwrap();
        Job job = new Job(newDomainInstance, untrustedAssembly, name_space, class_name, method_name, parameters);
        Thread thread = new Thread(new ThreadStart(job.DoJob));
        thread.Start();
        thread.Join(10000);
        if (thread.ThreadState != ThreadState.Stopped)
        {
            thread.Abort();
            Console.Error.WriteLine("Job taking too long. Aborted.");
        }
        AppDomain.Unload(newDomain);
    }
    public void ExecuteUntrustedCode(string assemblyName, string name_space, string class_name, string method_name, object[] parameters)
    {
        MethodInfo target = null;
        try
        {
            target = Assembly.Load(assemblyName).GetType(name_space+"."+class_name).GetMethod(method_name);
            if (target == null)
                throw new Exception();
        }
        catch (Exception)
        {
            Console.Error.WriteLine("Entry method '{0}' in class '{1}' in namespace '{2}' not found.", method_name, class_name, name_space);
            return;
        }
        ...            
        //Now invoke the method.
        try
        {
            target.Invoke(null, parameters);
        }
        catch (Exception e)
        {
            ...
        }
    }
}
class Job
{
    Sandboxer sandboxer = null;
    string assemblyName;
    string name_space;
    string class_name;
    string method_name;
    object[] parameters;
    public Job(Sandboxer sandboxer, string assemblyName, string name_space, string class_name, string method_name, object[] parameters)
    {
        this.sandboxer = sandboxer;
        this.assemblyName = assemblyName;
        this.name_space = name_space;
        this.class_name = class_name;
        this.method_name = method_name;
        this.parameters = parameters;
    }
    public void DoJob()
    {
        try
        {
            sandboxer.ExecuteUntrustedCode(assemblyName, name_space, class_name, method_name, parameters);
        }
        catch (Exception e)
        {
            Console.Error.WriteLine(e.Message);
        }
    }
}

你编译了上面的内容,并有了可执行文件,你可以在一个新的过程中启动和监控:

using (Process process = new Process())
{
    try
    {
        double TotalMemoryInBytes = 0;
        double TotalThreadCount = 0;
        int samplesCount = 0;
        process.StartInfo.FileName = /*path to sandboxer*/;
        process.StartInfo.Arguments = folder.Replace(" ", "|_|") + " " + assemblyName + " Rextester|Program|Main"; //assemblyName - assembly that contains compiled user code
        process.StartInfo.UseShellExecute = false;
        process.StartInfo.CreateNoWindow = true;
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.RedirectStandardError = true;
        DateTime start = DateTime.Now;
        process.Start();
        OutputReader output = new OutputReader(process.StandardOutput);
        Thread outputReader = new Thread(new ThreadStart(output.ReadOutput));
        outputReader.Start();
        OutputReader error = new OutputReader(process.StandardError);
        Thread errorReader = new Thread(new ThreadStart(error.ReadOutput));
        errorReader.Start();

        do
        {
            // Refresh the current process property values.
            process.Refresh();
            if (!process.HasExited)
            {
                try
                {
                    var proc = process.TotalProcessorTime;
                    // Update the values for the overall peak memory statistics.
                    var mem1 = process.PagedMemorySize64;
                    var mem2 = process.PrivateMemorySize64;
                    //update stats
                    TotalMemoryInBytes += (mem1 + mem2);
                    TotalThreadCount += (process.Threads.Count);
                    samplesCount++;
                    if (proc.TotalSeconds > 5 || mem1 + mem2 > 100000000 || process.Threads.Count > 100 || start + TimeSpan.FromSeconds(10) < DateTime.Now)
                    {
                        var time = proc.TotalSeconds;
                        var mem = mem1 + mem2;
                        process.Kill();
                        ...
                    }
                }
                catch (InvalidOperationException)
                {
                    break;
                }
            }
        }
        while (!process.WaitForExit(10)); //check process every 10 milliseconds
        process.WaitForExit();
        ...
}
...
class OutputReader
{
    StreamReader reader;
    public string Output
    {
        get;
        set;
    }
    StringBuilder sb = new StringBuilder();
    public StringBuilder Builder
    {
        get
        {
            return sb;
        }
    }
    public OutputReader(StreamReader reader)
    {
        this.reader = reader;
    }
    public void ReadOutput()
    {
        try
        {                
            int bufferSize = 40000;
            byte[] buffer = new byte[bufferSize];
            int outputLimit = 200000;
            int count;
            bool addMore = true;
            while (true)
            {
                Thread.Sleep(10);
                count = reader.BaseStream.Read(buffer, 0, bufferSize);
                if (count != 0)
                {
                    if (addMore)
                    {
                        sb.Append(Encoding.UTF8.GetString(buffer, 0, count));
                        if (sb.Length > outputLimit)
                        {
                            sb.Append("'n'n...");
                            addMore = false;
                        }
                    }
                }
                else
                    break;
            }
            Output = sb.ToString();
        }
        catch (Exception e)
        {
           ...
        }
    }
}

用户代码可以使用的程序集在编译时添加:

CompilerParameters cp = new CompilerParameters();
cp.GenerateExecutable = false;
cp.OutputAssembly = ...
cp.GenerateInMemory = false;
cp.TreatWarningsAsErrors = false;
cp.WarningLevel = 4;
cp.IncludeDebugInformation = false;
cp.ReferencedAssemblies.Add("System.dll");
cp.ReferencedAssemblies.Add("System.Core.dll");
cp.ReferencedAssemblies.Add("System.Data.dll");
cp.ReferencedAssemblies.Add("System.Data.DataSetExtensions.dll");
cp.ReferencedAssemblies.Add("System.Xml.dll");
cp.ReferencedAssemblies.Add("System.Xml.Linq.dll");
using (CodeDomProvider provider = CodeDomProvider.CreateProvider(/*language*/))
{
    cr = provider.CompileAssemblyFromSource(cp, new string[] { data.Program });
}

c#在线编译和执行安全问题

您是否将Mono的编译器视为一项服务?我认为他们正在做的事情很酷,也许对这个项目有用。

对于已经存在的类似内容的一个很好的例子http://www.topcoder.com,它有一个"算法竞技场",在这里提交代码并自动评分。使用某些类型的类(如Exception)是有限制的,但检查它们的应用程序以进行概念验证可能是个好主意。