Платформа .NET Assembly Plugin

Я использовал следующий код в ряде приложений для загрузки сборников .DLL, которые выставляют плагины.

Однако раньше я всегда был связан с функциональностью, а не с безопасностью.

Теперь я планирую использовать этот метод в веб-приложении, которое может использоваться группами, отличными от меня, и я хотел бы удостовериться, что безопасность этой функции является актуальной.

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

Первые мои проблемы:

  • Эта функция считывает весь каталог и не заботится о том, какие сборки он загружает, а вместо этого просто загружает все из них. Есть ли способ определить, является ли сборка действительной функциональной сборкой .NET перед ее загрузкой с помощью Assembly.LoadFile()?
  • Какую обработку исключений следует добавить к функции, чтобы предотвратить инициализацию сборки от остановки моего кода?
  • Если я хочу запретить сборке право делать следующее: читать/записывать файлы, читать/записывать реестр и т.д., как бы я это сделал?

Есть ли другие проблемы безопасности, о которых я должен беспокоиться?

РЕДАКТИРОВАТЬ: Имейте в виду, что я хочу, чтобы кто-нибудь мог написать плагин, но я все еще хочу быть в безопасности.

Ответ 1

1) сильное имя сборки с определенным ключом.

  • вы не должны помещать его в GAC
  • вы можете повторно использовать ключ для подписывания более чем одной сборки
  • При повторном использовании ключа вы получаете тот же "открытый ключ" на каждой подписанной ассемблере

2) при загрузке убедитесь, что сборка была сильно названа с помощью клавиши, которую вы ожидаете

  • Вы можете хранить открытый ключ как двоичный файл, встроенный ресурс,  или использовать существующий открытый ключ исполняющей сборки.
  • этот последний способ может быть не лучшим, поскольку вы можете различать сборки  подписанный с ключом "плагин" из подписанных с помощью обычного ключа)

Пример:

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 weaknesses, are documented elsewhere)

// all that remains is to compare the assembly public key with 
// a copy you've stored for this purpose, let use the executing assembly 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: код не был протестирован или скомпилирован, может содержать синтаксические ошибки.

Ответ 2

Я не знаю, является ли это лучшим способом, но когда вы вызываете LoadFile на недопустимой сборке, вы получите исключение BadImageFOrmatException, потому что сборка не имеет манифеста.

Как ваш код написан в настоящее время, вы довольно широко открыты для Attack Control Attack. Любой, кто может получить доступ к каталогу и выпустить сборку, которая реализует ваш интерфейс, может выполнить эту атаку. Им даже не нужно реализовывать интерфейс очень хорошо, они могут просто предоставить конструктор по умолчанию и сделать весь ущерб в этом. Это позволяет злоумышленнику выполнять код под привилегией вашего приложения, который всегда плох.

Итак, ваша единственная текущая защита - защита доступа к каталогу на уровне ОС. Это может сработать для вас, но это только один уровень защиты, и вы полагаетесь на состояние безопасности, которое вы не можете контролировать.

Вот две вещи, на которые вы могли бы обратить внимание:

  • Сильное именование сборки и требование регистрации сборки в GAC, скорее всего, самый безопасный способ сделать это. Если вы можете это сделать, вам нужно найти способ предоставить полное имя Ассамблеи вашему приложению и загрузить его с помощью Assembly.Load().

Однако я сомневаюсь, что вы хотите установить эти плагины в GAC, чтобы вы могли сделать это следующим образом:

  1. В вашем приложении предоставляется пользователям возможность зарегистрировать плагин, в основном мини-GAC. Когда они хранят местоположение и имя Ассамблеи, а также открытый ключ. Это требует, чтобы Ассамблея была названа сильной.

Таким образом вы будете загружать только сборки, предоставленные кому-то с привилегией вашего приложения, скорее всего, тот, кто имеет права на добавление плагина. Перед загрузкой сборки вы можете проверить, соответствует ли открытый ключ тем, что было предоставлено при регистрации Ассамблеи, чтобы злоумышленник не мог просто заменить сборку. Этот код довольно прост:

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

Итак, теперь, чтобы выполнить атаку на вас, я должен каким-то образом получить привилегию изменить значение PublicToken и получить доступ к каталогу и изменить файл.

Ответ 3

  • Если вы заинтересованы в безопасности своих сборок, вы должны Strongly Name их. Это обеспечивает высокий уровень безопасности, что сборка действительно та, которую вы намереваетесь сделать.

  • Исключения, которые могут возникнуть во время загрузки, следующие. Добавьте попытку/уловить попытку загрузки в Assembly.Load() и реагируйте в соответствии с типом ошибки:

    • ArgumentNullException
    • FileLoadException
    • FileNotFoundException
    • BadImageFormatException
  • Агрегаты, которые вы загружаете динамически, должны иметь те же права, что и учетная запись пользователя, которая загружала их, если эта сборка не находится в GAC. Создайте учетную запись службы с правами, которые вы хотите, и запустите приложение, используя эту учетную запись, для контроля доступа.

Ответ 4

Если вы используете 3.5, вам нужно многое сказать о новом материале System.AddIns - просмотрите http://www.codeplex.com/clraddins для примеров.

-Oisin

Ответ 5

Взгляните на структуру AddIn от Microsoft, поскольку она предоставляет некоторые из возможностей, которые вы ищете, такие как безопасность и изоляция.

Я также рекомендовал бы использовать только методы отражения, такие как ReflectionOnlyLoad и ReflectionOnlyLoadFrom, чтобы гарантировать, что ничто из сборки, которую вы запрашиваете метаданные, не будет выполняться при ее проверке. Как только вы определите, что на сборке есть то, что вы ищете, вы можете загрузить ее.