Поделиться через


Создание поставщика проверки подлинности FTP с динамическими ограничениями IP-адресов

Роберт Макмюррей

Корпорация Майкрософт создала новую службу FTP, которая была полностью перезаписана для Windows Server® 2008. Эта новая служба FTP включает множество новых функций, позволяющих веб-авторам публиковать содержимое проще, чем раньше, и предлагает веб-администраторам больше возможностей безопасности и развертывания.

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

В этом пошаговом руководстве вы узнаете, как использовать управляемый код к поставщику проверки подлинности FTP, который обеспечивает поддержку динамических ограничений IP-адресов, использующих базу данных SQL Server для хранения сведений об учетной записи. Этот поставщик реализует эту логику путем ведения журнала количества сбоев с удаленных IP-адресов, а затем с помощью этой информации для блокировки IP-адресов, которые не входить на сервер в течение заданного интервала времени.

Внимание

Для использования поставщика в этом пошаговом руководстве необходимо установить последнюю версию службы FTP 7.5. Версия FTP 7.5 была выпущена 3 августа 2009 г., которая устранена проблема, из-за которой локальные и удаленные IP-адреса в методе IFtpLogProvider.Log() были неверными. Из-за этого использование более ранней версии службы FTP не позволит этому поставщику работать.

Необходимые компоненты

Для выполнения процедур в этой статье требуются следующие элементы:

  1. На сервере Windows Server 2008 должны быть установлены службы IIS 7.0 или более поздней версии, а также должен быть установлен диспетчер службы IIS (IIS).

  2. Необходимо установить новую службу FTP 7.5.

    Внимание

    Как упоминание ранее в этом пошаговом руководстве, для использования поставщика в этом пошаговом руководстве необходимо установить последнюю версию службы FTP 7.5. Версия FTP 7.5 была выпущена 3 августа 2009 г., которая устранена проблема, из-за которой локальные и удаленные IP-адреса в методе IFtpLogProvider.Log() были неверными. Из-за этого использование более ранней версии службы FTP не позволит этому поставщику работать.

  3. Для сайта должна быть включена публикация FTP.

  4. Необходимо использовать Visual Studio 2008.

    Примечание.

    Если вы используете более раннюю версию Visual Studio, некоторые из шагов, описанных в этом пошаговом руководстве, могут быть неверными.

  5. Необходимо использовать базу данных SQL Server для списка учетных записей пользователей и связанных списков ограничений; этот пример нельзя использовать с проверкой подлинности FTP Basic. В разделе "Дополнительные сведения" этого пошагового руководства содержится сценарий для SQL Server, который создает необходимые таблицы для этого примера.

  6. Вам потребуется Gacutil.exe на компьютере IIS; Это необходимо для добавления сборок в глобальный кэш сборок (GAC).

Внимание

Чтобы повысить производительность запросов проверки подлинности, служба FTP кэширует учетные данные для успешных имен входа в течение 15 минут по умолчанию. Этот поставщик проверки подлинности немедленно отклонит запросы от злоумышленника, но если злоумышленник смог успешно угадать пароль для пользователя, который недавно вошел в систему, он может получить доступ через кэшированные учетные данные. Это может иметь непреднамеренное следствие, что позволит злоумышленнику атаковать ваш сервер после того, как этот поставщик заблокировал свой IP-адрес. Чтобы облегчить этот потенциальный способ атаки, необходимо отключить кэширование учетных данных для службы FTP. Для этого выполните следующие действия.

  1. Откройте командную строку.

  2. Введите такие команды:

    cd /d "%SystemRoot%\System32\Inetsrv"
    Appcmd.exe set config -section:system.ftpServer/caching /credentialsCache.enabled:"False" /commit:apphost
    Net stop FTPSVC
    Net start FTPSVC
    
  3. Закройте командную строку.

После внесения этих изменений поставщик проверки подлинности в этом примере сможет немедленно запретить все запросы от потенциального злоумышленника.

Описание поставщика

В этом пошаговом руководстве содержится несколько пунктов, которые требуют обсуждения. Атаки на основе Интернета часто используют FTP-сервер в попытке получить имя пользователя и пароль для учетной записи в системе. Обнаружение этого поведения возможно путем анализа журналов действий FTP и изучения IP-адресов, которые используются для атаки системы и блокировки этих адресов из будущего доступа. К сожалению, это что-то из ручного процесса, и даже если этот процесс автоматизирован, он не будет в режиме реального времени.

Служба FTP содержит функцию ограничения подключений на основе IP-адресов, но список IP-адресов хранится в файлах конфигурации IIS и требует административного доступа к обновлению. Процесс расширяемости для службы FTP выполняется как учетная запись с низким уровнем привилегий, которая не имеет разрешений на обновление необходимых параметров файлов конфигурации IIS. Вы можете написать поставщик ведения журнала FTP, который обнаруживает наводнение имени пользователя и записывает эти сведения в хранилище данных и отдельную службу, которая выполняется как более привилегированная учетная запись, которая может обновлять файлы конфигурации IIS, но для этого требуется больше знаний о системной архитектуре и требуется ряд сложных сведений о реализации. Из-за этого необходимо альтернативное хранилище данных.

База данных делает идеальный выбор из-за простоты доступа к данным и общедоступной доступности средств, доступных для управления данными в базе данных. Следующая задача — использовать существующие интерфейсы расширяемости FTP для реализации необходимой логики для обнаружения наводнения входа, используемого злоумышленником. По возможности проверки доступные интерфейсы расширяемости:

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

  • IFtpAuthenticationProvider — поставщик будет использовать этот интерфейс для разрешения или запрета доступа к FTP-серверу.
  • IFtpLogProvider — поставщик будет использовать этот интерфейс в качестве универсального прослушивателя событий.

Служба FTP не содержит фактических уведомлений о событиях, которые поставщик может зарегистрировать, но вы можете написать поставщиков, использующих метод IFtpLogProvider.Log() для предоставления обработки после события. Например, любые неудачные попытки входа записывают команду PASS с кодом состояния, отличном от "230", который является кодом состояния для успешного входа FTP. Записывая дополнительные сведения о неудачной попытке входа, например IP-адрес клиента, который не выполнил вход, эту информацию можно использовать для предоставления дополнительных функциональных возможностей, таких как блокировка IP-адресов от доступа к FTP-серверу в будущем.

Архитектура поставщика и логика

Следующие описания обобщают поведение этого поставщика проверки подлинности:

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

  • Когда служба FTP загружает поставщика, она предоставляет значения из файлов конфигурации IIS в метод Initialize() поставщика. После хранения этих значений в глобальных параметрах метод Initialize() выполняет некоторую начальную сборку мусора для очистки любой информации из предыдущих сеансов FTP, которые могут находиться в базе данных.

  • Когда FTP-клиент подключается к FTP-серверу, метод Log() поставщика отправляет сообщение ControlChannelOpened службой FTP. Метод Log() проверка базу данных, чтобы узнать, заблокирован ли IP-адрес клиента; если да, он помечает сеанс в базе данных.

  • Когда пользователь вводит имя пользователя и пароль, служба FTP вызывает метод AuthenticationUser() поставщика, который проверка, чтобы узнать, помечен ли сеанс. Если сеанс помечен, поставщик возвращает значение false, указав, что пользователь не выполнил вход. Если сеанс не помечен, имя пользователя и пароль проверка с базой данных, чтобы узнать, являются ли они допустимыми. Если они допустимы, метод возвращает значение true, указывая, что пользователь действителен и может войти в систему.

  • Если пользователь не удается ввести допустимое имя пользователя и пароль, метод Log() вызывается службой FTP, а метод запускает периодическую сборку мусора, чтобы убедиться, что количество сбоев меньше времени ожидания наводнения. Затем метод проверка, чтобы узнать, меньше ли оставшееся количество сбоев, чем максимальное число сбоев:

    • Если максимальное количество сбоев не достигнуто, метод добавляет уведомление об ошибке для IP-адреса клиента в базу данных.
    • Если достигнуто максимальное количество сбоев, метод добавляет IP-адрес клиента в список заблокированных IP-адресов в базе данных.
  • Когда FTP-клиент отключается от сервера, служба FTP вызывает метод Log() поставщика и отправляет сообщение ControlChannelClosed. Метод Log() использует это уведомление для выполнения сборки мусора для сеанса.

Дополнительные замечания

  • Этот поставщик предоставляет функциональные возможности для проверки пользовательских и IP-адресов, но не предоставляет реализацию для поиска ролей. Это говорится, что было бы относительно легко добавить дополнительную таблицу для сопоставлений пользователей и ролей и добавить метод IFtpRoleProvider.IsUserInRole() к поставщику, но это вне область этого пошагового руководства.
  • Этот поставщик делает небольшое количество вызовов к серверу базы данных SQL во время процесса проверки подлинности. Благодаря консолидации нескольких инструкций SQL в один составные запросы или хранимые процедуры можно еще больше сократить количество цикловых обходов в базу данных, но это находится за пределами область этого пошагового руководства.

Шаг 1. Настройка среды проекта

На этом шаге вы создадите проект в Visual Studio 2008 для демонстрационного поставщика.

  1. Откройте Microsoft Visual Studio 2008.

  2. Выберите меню "Файл", а затем "Создать" и "Проект".

  3. В диалоговом окне "Новый проект":

    • Выберите Visual C# в качестве типа проекта.
    • Выберите библиотеку классов в качестве шаблона.
    • Введите FTPAddressRestrictionAuthentication в качестве имени проекта.
    • Щелкните OK.
  4. Когда проект откроется, добавьте ссылочный путь к библиотеке расширяемости FTP:

    • Щелкните "Проект" и выберите "Свойства ftpAddressRestrictionAuthentication".

    • Перейдите на вкладку "Пути ссылки".

    • Введите путь к сборке расширяемости FTP для вашей версии Windows, где C: — это диск операционной системы.

      • Для Windows Server 2008 и Windows Vista:

        C:\Windows\assembly\GAC_MSIL\Microsoft.Web.FtpServer\7.5.0.0__31bf3856ad364e35
        
      • Для Windows 7:

        C:\Program Files\Reference Assemblies\Microsoft\IIS
        
    • Щелкните Добавить папку.

  5. Добавьте в проект ключ строгого имени:

    • Щелкните "Проект" и выберите "Свойства ftpAddressRestrictionAuthentication".
    • Откройте вкладку Подписание .
    • Установите флажок "Подписать сборку" проверка.
    • Выберите <"Создать"> из раскрывающегося списка строгого имени ключа.
    • Введите ftpAddressRestrictionAuthenticationKey для имени файла ключа.
    • При необходимости введите пароль для файла ключа; В противном случае снимите флажок "Защитить файл ключа" с помощью поля проверка пароля.
    • Щелкните OK.
  6. Необязательно. Вы можете добавить настраиваемое событие сборки, чтобы автоматически добавить библиотеку DLL в глобальный кэш сборок (GAC) на компьютере разработки:

    • Щелкните "Проект" и выберите "Свойства ftpAddressRestrictionAuthentication".

    • Перейдите на вкладку "События сборки".

    • Введите следующее в диалоговом окне командной строки события после сборки:

      net stop ftpsvc
      call "%VS90COMNTOOLS%\vsvars32.bat">null
      gacutil.exe /if "$(TargetPath)"
      net start ftpsvc
      
  7. Сохраните проект.

Шаг 2. Создание класса расширяемости

На этом шаге вы реализуете интерфейс расширения ведения журнала для демонстрационного поставщика.

  1. Добавьте ссылку на библиотеку расширяемости FTP для проекта:

    • Нажмите кнопку "Проект", а затем нажмите кнопку "Добавить ссылку " ...
    • На вкладке .NET щелкните Microsoft.Web.FtpServer.
    • Щелкните OK.
  2. Добавьте ссылку на System.Web для проекта:

    • Нажмите кнопку "Проект", а затем нажмите кнопку "Добавить ссылку " ...
    • На вкладке .NET щелкните System.Web.
    • Щелкните OK.
  3. Добавьте ссылку на System.Configuration для проекта:

    • Нажмите кнопку "Проект", а затем нажмите кнопку "Добавить ссылку " ...
    • На вкладке .NET щелкните System.Configuration.
    • Щелкните OK.
  4. Добавьте ссылку на System.Data для проекта:

    • Щелкните "Проект" и нажмите кнопку "Добавить ссылку".
    • На вкладке .NET щелкните System.Data.
    • Щелкните OK.
  5. Добавьте код для класса проверки подлинности:

    • В Обозреватель решений дважды щелкните файл Class1.cs.

    • Удалите существующий код.

    • Скопируйте приведенный ниже код и вставьте его в редактор.

      using System;
      using System.Collections.Generic;
      using System.Collections.Specialized;
      using System.Configuration.Provider;
      using System.Data;
      using System.Data.SqlClient;
      using System.Text;
      using Microsoft.Web.FtpServer;
      
      public class FtpAddressRestrictionAuthentication :
        BaseProvider,
        IFtpLogProvider,
        IFtpAuthenticationProvider
      {
        // Define the default values - these are only
        // used if the configuration settings are not set.
        const int defaultLogonAttempts = 5;
        const int defaultFloodSeconds = 30;
      
        // Define a connection string with no default.
        private static string _connectionString;
      
        // Initialize the private variables with the default values.
        private static int _logonAttempts = defaultLogonAttempts;
        private static int _floodSeconds = defaultFloodSeconds;
      
        // Flag the application as uninitialized.
        private static bool _initialized = false;
      
        // Define a list that will contain the list of flagged sessions.
        private static List<string> _flaggedSessions;
      
        // Initialize the provider.
        protected override void Initialize(StringDictionary config)
        {
          // Test if the application has already been initialized.
          if (_initialized == false)
          {
            // Create the flagged sessions list.
            _flaggedSessions = new List<string>();
      
            // Retrieve the connection string for the database connection.
            _connectionString = config["connectionString"];
            if (string.IsNullOrEmpty(_connectionString))
            {
              // Raise an exception if the connection string is missing or empty.
              throw new ArgumentException(
                "Missing connectionString value in configuration.");
            }
            else
            {
              // Determine whether the database is a Microsoft Access database.
              if (_connectionString.Contains("Microsoft.Jet"))
              {
                // Throw an exception if the database is a Microsoft Access database.
                throw new ProviderException("Microsoft Access databases are not supported.");
              }
            }
      
            // Retrieve the number of failures before an IP
            // address is locked out - or use the default value.
            if (int.TryParse(config["logonAttempts"], out _logonAttempts) == false)
            {
              // Set to the default if the number of logon attempts is not valid.
              _logonAttempts = defaultLogonAttempts;
            }
      
            // Retrieve the number of seconds for flood
            // prevention - or use the default value.
            if (int.TryParse(config["floodSeconds"], out _floodSeconds) == false)
            {
              // Set to the default if the number of logon attempts is not valid.
              _floodSeconds = defaultFloodSeconds;
            }
      
            // Test if the number is a positive integer and less than 10 minutes.
            if ((_floodSeconds <= 0) || (_floodSeconds > 600))
            {
              // Set to the default if the number of logon attempts is not valid.
              _floodSeconds = defaultFloodSeconds;
            }
      
            // Initial garbage collection.
            GarbageCollection(true);
            // Flag the provider as initialized.
            _initialized = true;
          }
        }
      
        // Dispose of the provider.
        protected override void Dispose(bool disposing)
        {
          base.Dispose(disposing);
      
          // Test if the application has already been uninitialized.
          if (_initialized == true)
          {
            // Final garbage collection.
            GarbageCollection(true);
            // Flag the provider as uninitialized.
            _initialized = false;
          }
        }
      
        // Authenticate a user.
        bool IFtpAuthenticationProvider.AuthenticateUser(
          string sessionId,
          string siteName,
          string userName,
          string userPassword,
          out string canonicalUserName)
        {
          // Define the canonical user name.
          canonicalUserName = userName;
      
          // Check if the session is flagged.
          if (IsSessionFlagged(sessionId) == true)
          {
            // Return false (authentication failed) if the session is flagged.
            return false;
          }
      
          // Check the user credentials and return the status.
          return IsValidUser(userName, userPassword);
        }
      
        // Implement custom actions by using the Log() method.
        void IFtpLogProvider.Log(FtpLogEntry loggingParameters)
        {
          // Test if the control channel was opened or the USER command was sent.
          if ((String.Compare(loggingParameters.Command,
            "ControlChannelOpened", true) == 0)
            || (String.Compare(loggingParameters.Command,
            "USER", true) == 0))
          {
            // Check if the IP address is banned.
            if (IsAddressBanned(loggingParameters.RemoteIPAddress) == true)
            {
              // If the IP is banned, flag the session.
              FlagSession(loggingParameters.SessionId);
              return;
            }
          }
          // Test if the PASS command was sent.
          if (String.Compare(loggingParameters.Command,
            "PASS", true) == 0)
          {
            // Check for password failures (230 is a success).
            if (loggingParameters.FtpStatus != 230)
            {
              // Periodic garbage collection - remove authentication
              // failures that are older than the flood timeout.
              GarbageCollection(false);
      
              // Test if the existing number of failures exceeds the maximum logon attempts.
              if (GetRecordCountByCriteria("[Failures]",
                "[IPAddress]='" + loggingParameters.RemoteIPAddress +
                "'") < _logonAttempts)
              {
                // Add the failure to the list of failures.
                InsertDataIntoTable("[Failures]",
                  "[IPAddress],[FailureDateTime]",
                  "'" + loggingParameters.RemoteIPAddress +
                  "','" + DateTime.Now.ToString() + "'");
              }
              else
              {
                // Ban the IP address if authentication has failed
                // from that IP more than the defined number of failures.
                BanAddress(loggingParameters.RemoteIPAddress);
                FlagSession(loggingParameters.SessionId);
              }
              return;
            }
          }
          // Test if the control channel was closed.
          if (String.Compare(loggingParameters.Command,
            "ControlChannelClosed", true) == 0)
          {
            // Session-based garbage collection - remove the
            // current session from the list of flagged sessions.
            _flaggedSessions.Remove(loggingParameters.SessionId);
            return;
          }
        }
      
        // Check for a valid username/password.
        private static bool IsValidUser(
          string userName,
          string userPassword)
        {
          // Define the initial status as the credentials are not valid.
          try
          {
            // Create a new SQL connection object.
            using (SqlConnection connection = new SqlConnection(_connectionString))
            {
              // Create a new SQL command object.
              using (SqlCommand command = new SqlCommand())
              {
                // Specify the connection for the command object.
                command.Connection = connection;
                // Specify a text command type.
                command.CommandType = CommandType.Text;
      
                // Specify the SQL text for the command object.
                command.CommandText = "SELECT COUNT(*) AS [NumRecords] " +
                  "FROM [Users] WHERE [UID]=@UID AND [PWD]=@PWD AND [Locked]=0";
      
                // Add parameters for the user name and password.
                command.Parameters.Add("@UID", SqlDbType.NVarChar).Value = userName;
                command.Parameters.Add("@PWD", SqlDbType.NVarChar).Value = userPassword;
      
                // Open the database connection.
                connection.Open();
                // Return the valid status for the credentials.
                return ((int)command.ExecuteScalar() > 0);
              }
            }
          }
          catch (Exception ex)
          {
            // Raise an exception if an error occurs.
            throw new ProviderException(ex.Message);
          }
        }
      
        // Check if the IP is banned.
        private bool IsAddressBanned(string ipAddress)
        {
          // Return whether the IP address was found in the banned addresses table.
          return (GetRecordCountByCriteria("[BannedAddresses]",
            "[IPAddress]='" + ipAddress + "'") != 0);
        }
      
        // Check if the session is flagged.
        private bool IsSessionFlagged(string sessionId)
        {
          // Return whether the session ID was found in the flagged sessions table.
          return _flaggedSessions.Contains(sessionId);
        }
      
        // Mark a session as flagged.
        private void FlagSession(string sessionId)
        {
          // Check if the session is already flagged.
          if (IsSessionFlagged(sessionId) == false)
          {
            // Flag the session if it is not already flagged.
            _flaggedSessions.Add(sessionId);
          }
        }
      
        // Mark an IP address as banned.
        private void BanAddress(string ipAddress)
        {
          // Check if the IP address is already banned.
          if (IsAddressBanned(ipAddress) == false)
          {
            // Ban the IP address if it is not already banned.
            InsertDataIntoTable("[BannedAddresses]",
              "[IPAddress]", "'" + ipAddress + "'");
          }
        }
      
        // Perform garbage collection tasks.
        private void GarbageCollection(bool deleteSessions)
        {
          // Remove any authentication failures that are older than the flood timeout.
          DeleteRecordsByCriteria("[Failures]",
            String.Format("DATEDIFF(second,[FailureDateTime],'{0}')>{1}",
            DateTime.Now.ToString(),_floodSeconds.ToString()));
      
          // Test if flagged sessions should be deleted.
          if (deleteSessions == true)
          {
            // Remove any sessions from the list of flagged sessions.
            _flaggedSessions.Clear();
          }
        }
      
        // Retrieve the count of records based on definable criteria.
        private int GetRecordCountByCriteria(
          string tableName,
          string criteria)
        {
          // Create a SQL string to retrieve the count of records 
          // that are found in a table based on the criteria.
          StringBuilder sqlString = new StringBuilder();
          sqlString.Append("SELECT COUNT(*) AS [NumRecords]");
          sqlString.Append(String.Format(
            " FROM {0}",tableName));
          sqlString.Append(String.Format(
            " WHERE {0}",criteria));
          // Execute the query.
          return ExecuteQuery(true, sqlString.ToString());
        }
      
        // Insert records into a database table.
        private void InsertDataIntoTable(
          string tableName,
          string fieldNames,
          string fieldValues)
        {
          // Create a SQL string to insert data into a table.
          StringBuilder sqlString = new StringBuilder();
          sqlString.Append(String.Format(
            "INSERT INTO {0}",tableName));
          sqlString.Append(String.Format(
            "({0}) VALUES({1})",fieldNames, fieldValues));
          // Execute the query.
          ExecuteQuery(false, sqlString.ToString());
        }
      
        // Remove records from a table based on criteria.
        private void DeleteRecordsByCriteria(
          string tableName,
          string queryCriteria)
        {
          // Create a SQL string to delete data from a table.
          StringBuilder sqlString = new StringBuilder();
          sqlString.Append(String.Format(
            "DELETE FROM {0}",tableName));
          // Test if any criteria is specified.
          if (string.IsNullOrEmpty(queryCriteria) == false)
          {
            // Append the criteria to the SQL string.
            sqlString.Append(String.Format(
              " WHERE {0}",queryCriteria));
          }
          // Execute the query.
          ExecuteQuery(false, sqlString.ToString());
        }
      
        // Execute SQL queries.
        private int ExecuteQuery(bool returnRecordCount, string sqlQuery)
        {
          try
          {
            // Create a new SQL connection object.
            using (SqlConnection connection =
              new SqlConnection(_connectionString))
            {
              // Create a new SQL command object.
              using (SqlCommand command =
                new SqlCommand(sqlQuery, connection))
              {
                // Open the connection.
                connection.Open();
                // Test whether the method should return a record count.
                if (returnRecordCount == true)
                {
                  // Run the database query.
                  SqlDataReader dataReader = command.ExecuteReader();
                  // Test if data reader has returned any rows.
                  if (dataReader.HasRows)
                  {
                    // Read a single row.
                    dataReader.Read();
                    // Return the number of records.
                    return ((int)dataReader["NumRecords"]);
                  }
                }
                else
                {
                  // Run the database query.
                  command.ExecuteNonQuery();
                }
              }
            }
            // Return a zero record count.
            return 0;
          }
          catch (Exception ex)
          {
            // Raise an exception if an error occurs.
            throw new ProviderException(ex.Message);
          }
        }
      }
      
  6. Сохраните и скомпилируйте проект.

Примечание.

Если вы не использовали необязательные шаги для регистрации сборок в GAC, необходимо вручную скопировать сборки на компьютер IIS и добавить сборки в GAC с помощью средства Gacutil.exe. Дополнительные сведения см. в разделе Gacutil.exe (средство глобального кэша сборок).

Шаг 3. Добавление демонстрационного поставщика в FTP

На этом шаге вы добавите демонстрационного поставщика в службу FTP и веб-сайт по умолчанию.

  1. Определите сведения о сборке для поставщика расширяемости:

    • В Windows Обозреватель откройте свой C:\Windows\assembly путь, где C: — это диск операционной системы.
    • Найдите сборку FTPAddressRestrictionAuthentication .
    • Щелкните правой кнопкой мыши сборку и выберите пункт "Свойства".
    • Скопируйте значение языка и региональных параметров. Например, нейтральный.
    • Скопируйте номер версии, например 1.0.0.0.0.
    • Скопируйте значение маркера открытого ключа , например 426f62526f636b73.
    • Щелкните Отмена.
  2. Используя сведения из предыдущих шагов, добавьте поставщика расширяемости в глобальный список поставщиков FTP и настройте параметры для поставщика:

    • На данный момент нет пользовательского интерфейса, который позволяет добавлять свойства для пользовательского модуля проверки подлинности, поэтому вам потребуется использовать следующую командную строку:

      cd %SystemRoot%\System32\Inetsrv
      
      appcmd.exe set config -section:system.ftpServer/providerDefinitions /+"[name='FtpAddressRestrictionAuthentication',type='FtpAddressRestrictionAuthentication,FtpAddressRestrictionAuthentication,version=1.0.0.0,Culture=neutral,PublicKeyToken=426f62526f636b73']" /commit:apphost
      
      appcmd.exe set config -section:system.ftpServer/providerDefinitions /+"activation.[name='FtpAddressRestrictionAuthentication']" /commit:apphost
      
      appcmd.exe set config -section:system.ftpServer/providerDefinitions /+"activation.[name='FtpAddressRestrictionAuthentication'].[key='connectionString',value='Server=localhost;Database=FtpAuthentication;User ID=FtpLogin;Password=P@ssw0rd']" /commit:apphost
      
      appcmd.exe set config -section:system.ftpServer/providerDefinitions /+"activation.[name='FtpAddressRestrictionAuthentication'].[key='logonAttempts',value='5']" /commit:apphost
      
      appcmd.exe set config -section:system.ftpServer/providerDefinitions /+"activation.[name='FtpAddressRestrictionAuthentication'].[key='floodSeconds',value='30']" /commit:apphost
      

    Примечание.

    Строка подключения, указанный в атрибуте connectionString, должен быть допустимым именем входа для базы данных.

  3. Добавьте настраиваемого поставщика на сайт:

    • На данный момент нет пользовательского интерфейса, который позволяет добавлять пользовательские функции на сайт, поэтому вам потребуется использовать следующую командную строку:

      AppCmd.exe set config -section:system.applicationHost/sites /"[name='Default Web Site'].ftpServer.security.authentication.basicAuthentication.enabled:False" /commit:apphost
      
      AppCmd.exe set config -section:system.applicationHost/sites /+"[name='Default Web Site'].ftpServer.security.authentication.customAuthentication.providers.[name='FtpAddressRestrictionAuthentication',enabled='True']" /commit:apphost
      
      AppCmd set site "Default Web Site" /+ftpServer.customFeatures.providers.[name='FtpAddressRestrictionAuthentication',enabled='true'] /commit:apphost
      

    Примечание.

    Этот синтаксис отключает обычную проверку подлинности FTP и важно отключить обычную проверку подлинности при использовании этого поставщика проверки подлинности. В противном случае, если IP-адрес злоумышленника заблокирован этим поставщиком проверки подлинности, злоумышленник по-прежнему сможет атаковать учетные записи, использующие обычную проверку подлинности.

  4. Добавьте правило авторизации для поставщика проверки подлинности:

    • Дважды щелкните правила авторизации FTP в главном окне.

    • Нажмите кнопку "Добавить правило разрешения"... на панели "Действия ".

    • Выберите указанных пользователей для параметра доступа.

    • Введите имя пользователя.

      Примечание.

      Имя пользователя должно быть введено в базу данных за пределами этого списка шагов.

    • Выберите параметр "Чтение и запись" для параметра "Разрешения".

    • Щелкните OK.

Шаг 4. Использование поставщика с FTP 7.5

При подключении FTP-клиентов к сайту FTP служба FTP попытается пройти проверку подлинности пользователей с помощью пользовательских поставщиков проверки подлинности с помощью учетных записей, хранящихся в базе данных. Если ftp-клиент не прошел проверку подлинности, поставщик будет отслеживать IP-адрес и дату и время сбоя в базе данных. Если ftp-клиент не сможет войти из определенного IP-адреса для количества сбоев, указанных в параметре входа в СистемуAttempts, и в течение указанного в параметре floodSeconds интервал времени поставщик блокирует вход в службу FTP.

Примечание.

Этот пример поставщика реализует логику проверки подлинности для службы FTP, но не предоставляет модуль администратора для управления данными в базе данных. Например, вы не можете управлять списком учетных записей пользователей FTP, запрещенных IP-адресов или сбоев проверки подлинности с помощью этого поставщика. Для управления данными с помощью диспетчера IIS можно использовать диспетчер баз данных IIS. Дополнительные сведения см. в следующей статье:

https://www.iis.net/extensions/DatabaseManager

Дополнительная информация

Чтобы создать необходимую базу данных и таблицы, можно использовать следующий скрипт SQL для Microsoft SQL Server. Чтобы использовать этот скрипт, необходимо обновить имя базы данных и расположение файлов базы данных. В SQL Server вы запустите скрипт в новом окне запроса, а затем создадите имя входа базы данных, которое будет использоваться с строка подключения.

Примечание.

Может потребоваться изменить скрипт SQL для хранения базы данных в другом расположении c:\databases.

/****** Create the FtpAuthentication Database ******/

USE [master]
GO
CREATE DATABASE [FtpAuthentication] ON  PRIMARY 
( NAME = N'FtpAuthentication', FILENAME = N'c:\databases\FtpAuthentication.mdf' , SIZE = 2048KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB )
 LOG ON 
( NAME = N'FtpAuthentication_log', FILENAME = N'c:\databases\FtpAuthentication_log.ldf' , SIZE = 1024KB , MAXSIZE = 2048GB , FILEGROWTH = 10%)
 COLLATE SQL_Latin1_General_CP1_CI_AS
GO
EXEC dbo.sp_dbcmptlevel @dbname=N'FtpAuthentication', @new_cmptlevel=90
GO
IF (1 = FULLTEXTSERVICEPROPERTY('IsFullTextInstalled'))
begin
EXEC [FtpAuthentication].[dbo].[sp_fulltext_database] @action = 'enable'
end
GO
ALTER DATABASE [FtpAuthentication] SET ANSI_NULL_DEFAULT OFF 
GO
ALTER DATABASE [FtpAuthentication] SET ANSI_NULLS OFF 
GO
ALTER DATABASE [FtpAuthentication] SET ANSI_PADDING OFF 
GO
ALTER DATABASE [FtpAuthentication] SET ANSI_WARNINGS OFF 
GO
ALTER DATABASE [FtpAuthentication] SET ARITHABORT OFF 
GO
ALTER DATABASE [FtpAuthentication] SET AUTO_CLOSE OFF 
GO
ALTER DATABASE [FtpAuthentication] SET AUTO_CREATE_STATISTICS ON 
GO
ALTER DATABASE [FtpAuthentication] SET AUTO_SHRINK OFF 
GO
ALTER DATABASE [FtpAuthentication] SET AUTO_UPDATE_STATISTICS ON 
GO
ALTER DATABASE [FtpAuthentication] SET CURSOR_CLOSE_ON_COMMIT OFF 
GO
ALTER DATABASE [FtpAuthentication] SET CURSOR_DEFAULT  GLOBAL 
GO
ALTER DATABASE [FtpAuthentication] SET CONCAT_NULL_YIELDS_NULL OFF 
GO
ALTER DATABASE [FtpAuthentication] SET NUMERIC_ROUNDABORT OFF 
GO
ALTER DATABASE [FtpAuthentication] SET QUOTED_IDENTIFIER OFF 
GO
ALTER DATABASE [FtpAuthentication] SET RECURSIVE_TRIGGERS OFF 
GO
ALTER DATABASE [FtpAuthentication] SET ENABLE_BROKER 
GO
ALTER DATABASE [FtpAuthentication] SET AUTO_UPDATE_STATISTICS_ASYNC OFF 
GO
ALTER DATABASE [FtpAuthentication] SET DATE_CORRELATION_OPTIMIZATION OFF 
GO
ALTER DATABASE [FtpAuthentication] SET TRUSTWORTHY OFF 
GO
ALTER DATABASE [FtpAuthentication] SET ALLOW_SNAPSHOT_ISOLATION OFF 
GO
ALTER DATABASE [FtpAuthentication] SET PARAMETERIZATION SIMPLE 
GO
ALTER DATABASE [FtpAuthentication] SET READ_WRITE 
GO
ALTER DATABASE [FtpAuthentication] SET RECOVERY SIMPLE 
GO
ALTER DATABASE [FtpAuthentication] SET MULTI_USER 
GO
ALTER DATABASE [FtpAuthentication] SET PAGE_VERIFY CHECKSUM  
GO
ALTER DATABASE [FtpAuthentication] SET DB_CHAINING OFF 

/****** Create the Database Tables ******/

USE [FtpAuthentication]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[BannedAddresses]') AND type in (N'U'))
BEGIN
CREATE TABLE [BannedAddresses](
    [IPAddress] [nvarchar](50) NOT NULL
) ON [PRIMARY]
END
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[Failures]') AND type in (N'U'))
BEGIN
CREATE TABLE [Failures](
    [IPAddress] [nvarchar](50) NOT NULL,
    [FailureDateTime] [datetime] NOT NULL
) ON [PRIMARY]
END
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[Users]') AND type in (N'U'))
BEGIN
CREATE TABLE [Users](
    [UID] [nvarchar](50) NOT NULL,
    [PWD] [nvarchar](50) NOT NULL,
    [Locked] [bit] NOT NULL
) ON [PRIMARY]
END

Итоги

В этом пошаговом руководстве вы узнали, как:

  • Создайте проект в Visual Studio 2008 для пользовательского поставщика FTP.
  • Реализуйте интерфейсы расширяемости для пользовательского поставщика FTP.
  • Добавьте настраиваемый поставщик FTP в службу FTP.