9 votes

problèmes de sécurité de la compilation et de l'exécution en ligne de c#

Je pensais donc écrire un compilateur et un environnement d'exécution C# en ligne. Et bien sûr, le problème n°1 est la sécurité. J'ai fini par créer un domaine d'application peu privilégié pour le code utilisateur et le lancer dans un nouveau processus dont la consommation de processeur et de mémoire est étroitement surveillée. Les espaces de noms standard des applications de la console sont disponibles. Ma question est donc la suivante : pouvez-vous imaginer des moyens de casser quelque chose d'une manière ou d'une autre ? Vous pouvez tester vos idées sur place rundotnet .

Edit2 Si quelqu'un s'intéresse au code, il y a maintenant un fork open source de ce projet : rextester sur github

Edit1 En réponse à l'un des commentaires, voici quelques exemples de code.

Donc, en gros, vous créez une application console. Je vais juste poster un gros morceau de celui-ci :

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);
        }
    }
}

Vous compilez ce qui précède et vous obtenez un exécutable que vous démarrez et surveillez dans un nouveau processus :

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)
        {
           ...
        }
    }
}

Les assemblages que le code utilisateur peut utiliser sont ajoutés au moment de la compilation :

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 });
}

3voto

Auðunn Points 31

Avez-vous regardé Le compilateur de Mono en tant que service ? Je pense que c'est plutôt cool ce qu'ils font, peut-être que quelque chose là-bas pourrait vous être utile pour ce projet.

2voto

Nuzzolilo Points 411

Pour un bon exemple de quelque chose de similaire qui existe déjà, il y a un endroit à l'adresse suivante http://www.topcoder.com qui dispose d'une "Algorithm Arena", où le code est soumis et automatiquement noté. Il existe des restrictions contre l'utilisation de certains types de classes, comme Exception, mais il peut être judicieux d'examiner leur application pour une preuve de concept.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X