Share via


Criar um Provedor de Autenticação do FTP com Restrições de IP Dinâmico

por Robert McMurray

A Microsoft criou um novo serviço FTP que foi completamente reescrito para o Windows Server® 2008. Esse novo serviço FTP incorpora muitos novos recursos que permitem que os autores da Web publiquem conteúdo com mais facilidade do que antes e oferece aos administradores da Web mais opções de segurança e implantação.

O novo serviço do FTP 7.5 dá suporte à extensibilidade que permite a você estender a funcionalidade interna incluída no serviço do FTP. Mais especificamente, o FTP 7.5 dá suporte à criação de seus próprios provedores de autenticação. Também é possível criar provedores para registro em log do FTP personalizado e para determinar as informações de diretório base para os usuários do FTP.

Este passo a passo levará você pelas etapas para usar o código gerenciado para um provedor de autenticação do FTP, que fornece suporte para restrições de IP dinâmicas que usam um banco de dados do SQL Server, para armazenar informações da conta. Esse provedor implementa essa lógica registrando o número de falhas de endereços IP remotos e, em seguida, usando essas informações para bloquear endereços IP que não conseguem fazer logon no servidor em um determinado período de tempo.

Importante

A versão mais recente do serviço FTP 7.5 deve ser instalada para usar o provedor neste passo a passo. Uma versão FTP 7.5 foi lançada em 3 de agosto de 2009 que resolveu um problema em que os endereços IP locais e remotos no método IFtpLogProvider.Log(), estavam incorretos. Por isso, o uso de uma versão anterior do serviço do FTP impedirá que esse provedor funcione.

Pré-requisitos

Os seguintes itens são necessários para concluir os procedimentos nessa seção:

  1. O IIS 7.0 ou superior deve ser instalado no Windows Server 2008 e o Gerenciador do IIS (Serviços de Informações da Internet) também deve ser instalado.

  2. O novo serviço do FTP 7.5 deve ser instalado.

    Importante

    Conforme mencionado anteriormente neste passo a passo, a versão mais recente do serviço FTP 7.5 deve ser instalada para usar o provedor neste passo a passo. Uma versão FTP 7.5 foi lançada em 3 de agosto de 2009 que resolveu um problema em que os endereços IP locais e remotos no método IFtpLogProvider.Log(), estavam incorretos. Por isso, o uso de uma versão anterior do serviço do FTP impedirá que esse provedor funcione.

  3. Para um site, a publicação do FTP deve ser habilitada.

  4. Você deve usar o Visual Studio 2008.

    Observação

    Caso use uma versão anterior do Visual Studio, algumas das etapas neste passo a passo poderão não estar corretas.

  5. Use um banco de dados do SQL Server para a lista de contas de usuário e as listas de restrições associadas. Este exemplo não pode ser usado com a autenticação Básica do FTP. A seção "Informações Adicionais" deste passo a passo contém um script para o SQL Server que cria as tabelas necessárias para este exemplo.

  6. Você precisará do Gacutil.exe em seu computador do IIS. Isto é necessário para adicionar os assemblies ao GAC (Cache de Assembly Global).

Importante

Para ajudar a melhorar o desempenho das solicitações de autenticação, o serviço do FTP armazena em cache as credenciais para logons bem-sucedidos, por padrão, por 15 minutos. Esse provedor de autenticação negará solicitações de um invasor imediatamente, mas se o invasor conseguir adivinhar com êxito a senha de um usuário que fez logon recentemente, ele poderá obter acesso por meio das credenciais armazenadas em cache. Isso pode ter a consequência não intencional de permitir que um usuário mal-intencionado ataque seu servidor depois que esse provedor bloqueou seu endereço IP. Para aliviar esse possível caminho de ataque, desabilite o cache de credenciais para o serviço do FTP. Para fazer isso, execute as seguintes etapas:

  1. Abra um prompt de comando.

  2. Digite os seguintes comandos:

    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. Feche o prompt de comando.

Depois de fazer essas alterações, o provedor de autenticação neste exemplo poderá negar imediatamente todas as solicitações de um potencial invasor.

Descrição do provedor

Este passo a passo contém vários pontos que exigem alguma discussão. Ataques baseados na Internet geralmente exploram um servidor de FTP na tentativa de obter o nome de usuário e a senha de uma conta em um sistema. Detectar esse comportamento é possível por meio da análise dos logs de atividades do FTP e examinar os endereços IP que estão sendo usados para atacar seu sistema e bloquear esses endereços do acesso futuro. Infelizmente, isso é uma espécie de processo manual e, mesmo que esse processo seja automatizado, ele não será em tempo real.

O serviço do FTP contém um recurso para restringir conexões com base em endereços IP, mas a lista de endereços IP é armazenada nos arquivos de configuração do IIS e requer acesso administrativo para atualização. O processo de extensibilidade do serviço do FTP é executado como uma conta com privilégios inferiores que não tem permissões para atualizar as configurações necessárias dos arquivos de configuração do IIS. É possível gravar um provedor de log do FTP que detecta inundações de nome de usuário e grava essas informações em um armazenamento de dados e um serviço separado que é executado como uma conta com privilégios mais altos que pode atualizar os arquivos de configuração do IIS, mas que requer um conhecimento maior da arquitetura do sistema e requer uma série de detalhes difíceis de implementação. Por isso, um armazenamento de dados alternativo é necessário.

Um banco de dados faz uma escolha ideal devido à facilidade de acesso a dados e à disponibilidade geral de ferramentas disponíveis para manipulação de dados em um banco de dados. O próximo desafio é usar as interfaces de extensibilidade do FTP existentes, para implementar a lógica necessária para detectar inundações de logon que um invasor usaria. Por meio da revisão, as interfaces de extensibilidade disponíveis são:

Você poderia facilmente escrever um provedor que aproveitasse todas essas interfaces para aumentar a segurança em um grau maior, mas o provedor neste passo a passo usará apenas as seguintes interfaces:

  • IFtpAuthenticationProvider – O provedor usará essa interface para permitir ou negar o acesso ao servidor de FTP.
  • IFtpLogProvider – O provedor usará essa interface como um ouvinte de eventos genérico.

O serviço do FTP não tem notificações de evento reais para as quais um provedor pode se registrar, mas você pode gravar provedores que usam o método IFtpLogProvider.Log(), para fornecer processamento pós-evento. Por exemplo, todas as tentativas de logon com falha registrarão o comando "PASS" com um código de status diferente de "230", que é o código de status para um logon do FTP bem-sucedido. Ao capturar informações adicionais sobre a tentativa de logon com falha, como o endereço IP do cliente que não fez logon, torna-se possível usar essas informações para fornecer funcionalidades adicionais, como impedir que endereços IP acessem seu servidor de FTP no futuro.

Arquitetura e lógica do provedor

As descrições a seguir resumem o comportamento desse provedor de autenticação:

  • Ao registrar o provedor em seu sistema, você especifica a conexão de banco de dados a ser usada e os valores para o número de tentativas de logon com falha e tempo limite de inundação nos arquivos de configuração do IIS.

  • Quando o serviço do FTP carrega seu provedor, ele fornece os valores dos arquivos de configuração do IIS para o método Initialize() do provedor. Depois que esses valores são armazenados em configurações globais, o método Initialize() executa alguma coleta de lixo inicial para limpar qualquer informação de sessões do FTP anteriores que possam estar no banco de dados.

  • Quando um cliente FTP se conecta ao servidor de FTP, o método Log() do provedor é enviado a mensagem "ControlChannelOpened" pelo serviço do FTP. O método Log() verifica o banco de dados para visualizar se o endereço IP do cliente foi bloqueado, nesse caso, ele sinaliza a sessão no banco de dados.

  • Quando o usuário insere seu nome de usuário e senha, o serviço do FTP chama o método AuthenticateUser() do provedor, que verifica se a sessão está sinalizada. Se a sessão for sinalizada, o provedor retornará falso, significando que o usuário falhou ao fazer logon. Se a sessão não estiver sinalizada, o nome de usuário e a senha serão verificados com o banco de dados para ver se eles são válidos. Se forem válidos, o método retornará verdadeiro, indicando que o usuário é válido e pode fazer logon.

  • Se o usuário não inserir um nome de usuário e uma senha válidos, o método Log() será chamado pelo serviço do FTP e o método executará a coleta de lixo periódica para garantir que o número de falhas seja menor do que o tempo limite de inundação. Em seguida, o método verifica se o número restante de falhas é menor que o número máximo de falhas:

    • Se o número máximo de falhas não tiver sido atingido, o método adicionará uma notificação de falha para o endereço IP do cliente ao banco de dados.
    • Se o número máximo de falhas tiver sido atingido, o método adicionará o endereço IP do cliente à lista de endereços IP bloqueados no banco de dados.
  • Quando um cliente FTP se desconecta do servidor, o serviço do FTP chama o método Log() do provedor e envia a mensagem "ControlChannelClosed". O método Log() aproveita essa notificação para executar a coleta de lixo para a sessão.

Observações adicionais

  • Esse provedor expõe a funcionalidade para validação de endereço IP e de usuário, mas não fornece uma implementação para pesquisas de função. Dito isto, seria relativamente fácil adicionar uma tabela adicional para mapeamentos de usuário para função e adicionar o método IFtpRoleProvider.IsUserInRole() ao provedor, mas que está fora do escopo deste passo a passo.
  • Esse provedor faz um pequeno número de chamadas para o servidor de banco de dados SQL durante o processo de autenticação. Por meio da consolidação de algumas das instruções do SQL em consultas compostas simples ou procedimentos armazenados, você pode reduzir ainda mais o número de viagens de ida e volta para o banco de dados, mas isso está fora do escopo deste passo a passo.

Etapa 1: configurar o ambiente do projeto

Nesta etapa, você vai criar um projeto no Visual Studio 2008 para o provedor de demonstração.

  1. Abra o Microsoft Visual Studio 2008.

  2. Selecione o menu Arquivo, em Novo e em Projeto.

  3. Na caixa de diálogo Novo Projeto:

    • Escolha Visual C# como o tipo de projeto.
    • Escolha Biblioteca de Classes como o modelo.
    • Digite FtpAddressRestrictionAuthentication como o nome do projeto.
    • Clique em OK.
  4. Quando o projeto for aberto, adicione um caminho de referência à biblioteca de extensibilidade do FTP:

    • Selecione Projetoe, em seguida, selecione Propriedades FtpAddressRestrictionAuthentication.

    • Selecione o guia Caminhos de referência.

    • Insira o caminho para o assembly de extensibilidade do FTP para sua versão do Windows, em que C: é a unidade do sistema operacional.

      • Para Windows Server 2008 e Windows Vista:

        C:\Windows\assembly\GAC_MSIL\Microsoft.Web.FtpServer\7.5.0.0__31bf3856ad364e35
        
      • Para Windows 7:

        C:\Program Files\Reference Assemblies\Microsoft\IIS
        
    • Selecione Adicionar Pasta.

  5. Adicione uma chave de nome forte ao projeto:

    • Selecione Projetoe, em seguida, selecione Propriedades FtpAddressRestrictionAuthentication.
    • Clique na guia Assinatura .
    • Marque a caixa de seleção Assinar o assembly.
    • Escolha <Novo...> na caixa suspensa nome de chave forte.
    • Insira FtpAddressRestrictionAuthenticationKey para o nome do arquivo de chave.
    • Se desejar, insira uma senha para o arquivo de chave; caso contrário, desmarque a caixa de seleção Proteger meu arquivo de chave com uma senha.
    • Clique em OK.
  6. Opcional: você pode adicionar um evento de build personalizado para adicionar a DLL automaticamente ao GAC (Cache de Assembly Global) em seu computador de desenvolvimento:

    • Selecione Projetoe, em seguida, selecione Propriedades FtpAddressRestrictionAuthentication.

    • Selecione o guia Eventos de Build.

    • Insira o seguinte na caixa de diálogo Linha de Comando do evento pós-build:

      net stop ftpsvc
      call "%VS90COMNTOOLS%\vsvars32.bat">null
      gacutil.exe /if "$(TargetPath)"
      net start ftpsvc
      
  7. Salvaro projeto.

Etapa 2: criar a classe de extensibilidade

Nesta etapa, você vai implementar a interface de extensibilidade de log para o provedor de demonstração.

  1. Adicione uma referência à biblioteca de extensibilidade do FTP para o projeto:

    • Selecione Projeto e selecione Adicionar Referência...
    • No guia .NET, selecione Microsoft.Web.FtpServer.
    • Clique em OK.
  2. Adicione uma referência ao System.Web para o projeto:

    • Selecione Projeto e selecione Adicionar Referência...
    • No guia .NET, selecione System.Web.
    • Clique em OK.
  3. Adicione uma referência ao System.Configuration para o projeto:

    • Selecione Projeto e selecione Adicionar Referência...
    • Selecione .NET, selecione System.Configuration.
    • Clique em OK.
  4. Adicione uma referência ao System.Data para o projeto:

    • Selecione Projeto e selecioneAdicionar referência.
    • No guia .NET, selecione System.Data.
    • Clique em OK.
  5. Adicione o código para a classe de autenticação:

    • No Gerenciador de Soluções, clique duas vezes no arquivo Class1.cs.

    • Remova o código existente.

    • Cole o código a seguir no editor:

      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. Salve e compile o projeto.

Observação

Se você não tiver usado as etapas opcionais para registrar os assemblies no GAC, será necessário copiar manualmente os assemblies para o computador do IIS e adicionar os assemblies ao GAC usando a ferramenta Gacutil.exe. Para obter mais informações, consulte Gacutil.exe (ferramenta de cache de assembly global).

Etapa 3: adicionar o provedor de demonstração ao FTP

Nesta etapa, você vai adicionar o provedor de demonstração ao serviço do FTP e ao Site Padrão.

  1. Determine as informações do assembly para o provedor de extensibilidade:

    • No Windows Explorer, abra seu caminho C:\Windows\assembly, em que C: é a unidade do sistema operacional.
    • Localize o assembly FtpAddressRestrictionAuthentication.
    • Clique com o botão direito do mouse no assembly e em Propriedades.
    • Copie o valor Cultura, por exemplo: Neutro.
    • Copie o número de Versão, por exemplo: 1.0.0.0.
    • Copie o valor do Token de Chave Pública, por exemplo: 426f62526f636b73.
    • Clique em Cancelar.
  2. Usando as informações das etapas anteriores, adicione o provedor de extensibilidade à lista global de provedores FTP e configure as opções para o provedor:

    • No momento, não há nenhuma interface de usuário que permita adicionar propriedades para um módulo de autenticação personalizada. Sendo assim, você terá que usar a seguinte linha de comando:

      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
      

    Observação

    A cadeia de conexão especificada no atributo connectionString deve ser um logon válido para seu banco de dados.

  3. Adicione o provedor personalizado a um site:

    • No momento, não há nenhuma interface do usuário que permita adicionar recursos personalizados a um site, portanto, você precisará usar a seguinte linha de comando:

      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
      

    Observação

    Essa sintaxe desabilita a autenticação do FTP Basic e é importante desabilitar a autenticação Básica ao usar esse provedor de autenticação. Caso contrário, quando o endereço IP de um invasor tiver sido bloqueado por esse provedor de autenticação, um invasor ainda poderá atacar contas que usam a autenticação Básica.

  4. Adicione uma regra de autorização para o provedor de autenticação:

    • Clique duas vezes em Regras de autorização FTP na janela principal.

    • Selecione Adicionar Regra de permissão... no painel Ações.

    • Selecione Usuários especificados na opção de acesso.

    • Insira um nome de usuário.

      Observação

      O nome de usuário precisará ser inserido no banco de dados fora dessa lista de etapas.

    • Selecione Leitura e/ou Gravação na opção de Permissões.

    • Clique em OK.

Etapa 4: usar o provedor com FTP 7.5

Quando os clientes FTP se conectam ao seu site do FTP, o serviço do FTP tentará autenticar usuários com seu provedor de autenticação personalizado usando contas armazenadas no banco de dados. Se um cliente FTP não conseguir se autenticar, o provedor acompanhará o endereço IP e a data/hora da falha no banco de dados. Quando um cliente FTP falha ao fazer logon de um endereço IP específico para o número de falhas especificadas na configuração logonAttempts e dentro do período especificado na configuração floodSeconds, o provedor bloqueará o endereço IP de fazer logon no serviço do FTP.

Observação

Esse provedor de exemplo implementa a lógica de autenticação para o serviço do FTP, mas não fornece um módulo de administrador para gerenciar os dados no banco de dados. Por exemplo,não é possível gerenciar a lista de contas de usuário do FTP, endereços IP proibidos ou falhas de autenticação usando esse provedor. Para gerenciar os dados usando o Gerenciador do IIS, use o Gerenciador de Banco de Dados do IIS. Para obter mais informações, consulte o tópico a seguir:

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

Informações Adicionais

Use o script SQL a seguir para o Microsoft SQL Server para criar o banco de dados e as tabelas necessários. Para usar esse script, atualize o nome do banco de dados e o local dos arquivos de banco de dados. No SQL Server, você executaria o script em uma nova janela de consulta e, em seguida, criaria um logon de banco de dados que você usará com a cadeia de conexão.

Observação

Talvez você queira alterar o script SQL para armazenar o banco de dados em um local diferente de 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

Resumo

Neste passo a passo, você aprendeu a:

  • Criar um projeto no Visual Studio 2008 para um provedor de FTP personalizado.
  • Implemente as interfaces de extensibilidade para um provedor de FTP personalizado.
  • Adicione um provedor de FTP personalizado ao serviço do FTP.