Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET Assembly Plugin Security

I have used the following code in a number of applications to load .DLL assemblies that expose plugins.

However, I previously was always concerned with functionality, rather than security.

I am now planning to use this method on a web application that could be used by groups other than me, and I would like to make sure that the security of the function is up-to-snuff.

private void LoadPlugins(string pluginsDirectory)
{
    List<IPluginFactory> factories = new List<IPluginFactory>();

    foreach (string path in Directory.GetFiles(pluginsDirectory, "*.dll"))
    {
        Assembly assembly = Assembly.LoadFile(path);
        foreach (Type type in assembly.GetTypes())
        {
            IPluginEnumerator instance = null;
            if (type.GetInterface("IPluginEnumerator") != null)
                instance = (IPluginEnumerator)Activator.CreateInstance(type);
            if (instance != null)
            {
                factories.AddRange(instance.EnumerateFactories());
            }
        }
    }

    // Here, I would usually collate the plugins into List<ISpecificPlugin>, etc.
}

The first few concerns I have:

  1. This function reads the entire directory and doesn't care about what assemblies it loads, and instead just loads all of them. Is there a way to detect whether an assembly is a valid, functional .NET assembly before loading it with Assembly.LoadFile()?
  2. What kind of exception handling should be added to the function to prevent initialization of the assembly from halting my code?
  3. If I want to deny the assembly the right to do the following: Read/Write files, Read/Wite the registry, etc, how would I do that?

Are there any other security concerns I should be worried about?

EDIT: Keep in mind that I want anybody to be able to write a plug-in, but I still want to be secure.

like image 440
John Gietzen Avatar asked May 31 '09 16:05

John Gietzen


2 Answers

1) strong name the assembly with a certain key.

  • you do not have to put it in the GAC
  • you can re-use a key to sign more than one assembly
  • when you re-use the key, you get the same "public key" on each signed assembly

2) on load, check that the assembly has been strong named with the key you're expecting

  • You can store the public key as a binary file, an embedded resource, or use the existing public key of the executing assembly
  • this last way may not be the best way as you may want to differentiate assemblies signed with the "plugin" key from those signed with a regular key)

Example:

public static StrongName GetStrongName(Assembly assembly)
{
    if(assembly == null)
        throw new ArgumentNullException("assembly");
    AssemblyName assemblyName = assembly.GetName();

    // get the public key blob
    byte[] publicKey = assemblyName.GetPublicKey();
    if(publicKey == null || publicKey.Length == 0)
       throw new InvalidOperationException( String.Format("{0} is not strongly named", assembly));

    StrongNamePublicKeyBlob keyBlob = new StrongNamePublicKeyBlob(publicKey);

    // create the StrongName
    return new StrongName(keyBlob, assemblyName.Name, assemblyName.Version);
}


// load the assembly:
Assembly asm = Assembly.LoadFile(path);
StrongName sn = GetStrongName(asm);

// at this point
// A: assembly is loaded
// B: assembly is signed
// C: we're reasonably certain the assembly has not been tampered with
// (the mechanism for this check, and it's weaknesses, are documented elsewhere)

// all that remains is to compare the assembly's public key with 
// a copy you've stored for this purpose, let's use the executing assembly's strong name
StrongName mySn = GetStrongName(Assembly.GetExecutingAssembly());

// if the sn does not match, put this loaded assembly in jail
if (mySn.PublicKey!=sn.PublicKey)
    return false;

note: code has not been tested or compiled, may contain syntax errors.

like image 65
Blue Toque Avatar answered Nov 13 '22 05:11

Blue Toque


I don't know if this is the best way but when you call LoadFile on an invalid assembly you will get a BadImageFOrmatException because the assembly does not have a manifest.

The way your code is written currently you are pretty wide open to a Process Control Attack. Anyone who can get access to the directory and drop down an assembly that implements your interface can execute this attack. They do not even have to implement the interface very well, they can just provide a default constructor and do all the damage in that. This allows an attacker to execute code under the privilege of your application which is always bad.

So your only current protection is to protect access to the directory on an OS level. This may work for you but it is only one level of defense and you are reliant on security state you cannot control.

Here are two things you could look at:

  1. Strong naming the assembly and requiring the assembly be registered in the GAC is most likely the safest way to do this. If you can do this you need to find a way to provide the Assembly's full name to your application and load it with Assembly.Load().

However I doubt you want to install these plugins into the GAC so you could do it this way:

  1. Within your application provide a way for users to register a plugin, essentially a mini-GAC. When they do you store the location and name of the Assembly as well as the public key. This requires that the Assembly is strong named.

This way you will only load Assemblies that someone with privilege to your application has provided, most likely someone who has rights to add a plugin. Before you load the Assembly you can check that the Public Key matches what was provided when the Assembly was registered to prevent an attacker from just replacing the Assembly. This code is fairly simple:

    private bool DoPublicKeysCompare(Assembly assembly, byte[] expectedPublicKey)
    {
        byte[] assemblyKey = assembly.GetName().GetPublicKey();
        return expectedPublicKey.SequenceEqual(assemblyKey);
    }

So now to execute an attack on you I must somehow get privileged to change the PublicToken value and get access to the directory and change out the file.

like image 41
Flory Avatar answered Nov 13 '22 04:11

Flory