Freigeben über


Erstellen eines FTP-Authentifizierungsanbieters mit dynamischen IP-Einschränkungen

von Robert McMurray

Microsoft hat einen neuen FTP-Dienst entwickelt, der für Windows Server® 2008 komplett neu geschrieben wurde. Dieser neue FTP-Dienst enthält viele neue Funktionen, mit denen Web-Autoren und -Autorinnen Inhalte einfacher als bisher veröffentlichen können, und bietet Web-Admins mehr Sicherheit und Optionen für die Bereitstellung.

Der neue FTP 7.5-Dienst unterstützt die Erweiterbarkeit, mit der Sie die in den FTP-Dienst integrierten Funktionen erweitern können. Genauer gesagt, unterstützt FTP 7.5 die Erstellung Ihrer eigenen Authentifizierungsanbieter. Sie können auch Anbieter für die benutzerdefinierte FTP-Protokollierung und für die Ermittlung der Informationen zum Startverzeichnis Ihrer FTP-Benutzer erstellen.

Diese exemplarische Vorgehensweise führt Sie durch die Schritte zum Verwenden von verwaltetem Code zu einem FTP-Authentifizierungsanbieter, der Unterstützung für dynamische IP-Einschränkungen bereitstellt, die eine SQL Server-Datenbank zum Speichern von Kontoinformationen verwenden. Dieser Anbieter implementiert diese Logik, indem die Anzahl der Fehler von Remote-IP-Adressen protokolliert und diese Informationen dann verwendet werden, um IP-Adressen zu blockieren, die sich innerhalb eines bestimmten Zeitraums nicht beim Server anmelden.

Wichtig

Die neueste Version des FTP 7.5-Diensts muss installiert sein, um den Anbieter in dieser exemplarischen Vorgehensweise zu verwenden. Am 3. August 2009 wurde eine Version FTP 7.5 veröffentlicht, die ein Problem behoben hat, bei dem die lokalen und Remote-IP-Adressen in der IFtpLogProvider.Log()-Methode falsch waren. Aus diesem Gründen wird die Verwendung einer früheren Version des FTP-Diensts verhindern, dass dieser Anbieter funktioniert.

Voraussetzungen

Die folgenden Elemente sind erforderlich, um die Verfahren in diesem Artikel abzuschließen:

  1. IIS 7.0 oder höher muss auf Ihrem Windows Server 2008-Server installiert sein, und der Internetinformationsdienste (IIS)-Manager muss ebenfalls installiert sein.

  2. Der neue FTP 7.5-Dienst muss installiert sein.

    Wichtig

    Wie weiter oben in dieser exemplarischen Vorgehensweise erwähnt, muss die neueste Version des FTP 7.5-Diensts installiert werden, um den Anbieter in dieser exemplarischen Vorgehensweise zu verwenden. Am 3. August 2009 wurde eine Version FTP 7.5 veröffentlicht, die ein Problem behoben hat, bei dem die lokalen und Remote-IP-Adressen in der IFtpLogProvider.Log()-Methode falsch waren. Aus diesem Gründen wird die Verwendung einer früheren Version des FTP-Diensts verhindern, dass dieser Anbieter funktioniert.

  3. Sie müssen die FTP-Veröffentlichung für eine Website aktiviert haben.

  4. Sie müssen Visual Studio 2008 verwenden.

    Hinweis

    Wenn Sie eine frühere Version von Visual Studio verwenden, sind einige der Schritte in dieser Anleitung möglicherweise nicht korrekt.

  5. Sie müssen eine SQL Server-Datenbank für die Liste der Benutzerkonten und die zugehörigen Einschränkungslisten verwenden; Dieses Beispiel kann nicht mit FTP Basic-Authentifizierung verwendet werden. Der Abschnitt "Zusätzliche Informationen" dieser exemplarischen Vorgehensweise enthält ein Skript für SQL Server, das die erforderlichen Tabellen für dieses Beispiel erstellt.

  6. Sie benötigen Gacutil.exe auf Ihrem IIS-Computer; Dies ist erforderlich, um die Assemblys zum globalen Assemblycache (Global Assembly Cache, GAC) hinzuzufügen.

Wichtig

Um die Leistung für Authentifizierungsanforderungen zu verbessern, speichert der FTP-Dienst die Anmeldeinformationen für erfolgreiche Anmeldungen standardmäßig 15 Minuten zwischen. Dieser Authentifizierungsanbieter verweigert Anforderungen eines Angreifers sofort, aber wenn der Angreifer das Kennwort für einen Benutzer, der sich kürzlich angemeldet hat, erfolgreich erraten konnte, erhält er möglicherweise Zugriff über die zwischengespeicherten Anmeldeinformationen. Dies kann die unbeabsichtigte Folge haben, dass ein böswilliger Benutzer Ihren Server angreifen kann, nachdem dieser Anbieter seine IP-Adresse blockiert hat. Um diesen potenziellen Angriffsweg zu verringern, sollten Sie die Zwischenspeicherung von Anmeldeinformationen für den FTP-Dienst deaktivieren. Führen Sie dazu die folgenden Schritte aus:

  1. Öffnen Sie eine Eingabeaufforderung.

  2. Geben Sie folgende Befehle ein:

    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. Schließen Sie die Eingabeaufforderung.

Nachdem Sie diese Änderungen vorgenommen haben, kann der Authentifizierungsanbieter in diesem Beispiel alle Anforderungen eines potenziellen Angreifers sofort verweigern.

Anbieterbeschreibung

Diese exemplarische Vorgehensweise enthält mehrere Punkte, die einige Diskussionen erfordern. Internetbasierte Angriffe nutzen häufig einen FTP-Server, um den Benutzernamen und das Kennwort für ein Konto auf einem System zu erhalten. Sie können dieses Verhalten erkennen, indem Sie die FTP-Aktivitätsprotokolle analysieren und die IP-Adressen untersuchen, die für Angriffe auf Ihr System verwendet werden, und diese Adressen für den zukünftigen Zugriff sperren. Leider handelt es sich dabei um einen manuellen Prozess, und selbst wenn dieser Prozess automatisiert ist, erfolgt er nicht in Echtzeit.

Der FTP-Dienst enthält ein Feature zum Einschränken von Verbindungen basierend auf IP-Adressen, aber die Liste der IP-Adressen wird in den IIS-Konfigurationsdateien gespeichert und erfordert administratorbasierten Zugriff zum Aktualisieren. Der Erweiterbarkeitsprozess für den FTP-Dienst wird als ein Konto mit niedrigeren Rechten ausgeführt, das keine Berechtigungen zum Aktualisieren der erforderlichen Einstellungen für die IIS-Konfigurationsdateien besitzt. Sie könnten einen FTP-Protokollierungsanbieter schreiben, der eine Überschwemmung von Benutzernamen erkennt und diese Informationen in einen Datenspeicher und einen separaten Dienst schreibt, der als höher privilegiertes Konto ausgeführt wird und die IIS-Konfigurationsdateien aktualisieren kann. Dies erfordert jedoch größere Kenntnisse der Systemarchitektur und erfordert mehr eine Reihe schwieriger Implementierungsdetails. Aus diesem Grund ist ein alternativer Datenspeicher erforderlich.

Eine Datenbank macht eine ideale Wahl aufgrund der erleichterten Datenzugriffs und der allgemeinen Verfügbarkeit von Tools, die zum Bearbeiten von Daten in einer Datenbank verfügbar sind. Die nächste Herausforderung besteht darin, die vorhandenen FTP-Erweiterbarkeitsschnittstellen zu verwenden, um die erforderliche Logik zu implementieren, um Anmeldefluten zu erkennen, die ein Angreifer verwenden würde. Zur Überprüfung sind die verfügbaren Erweiterbarkeitsschnittstellen:

Sie können problemlos einen Anbieter schreiben, der alle diese Schnittstellen nutzt, um die Sicherheit zu erhöhen, aber der Anbieter in dieser exemplarischen Vorgehensweise verwendet nur die folgenden Schnittstellen:

  • IFtpAuthenticationProvider – Der Anbieter verwendet diese Schnittstelle, um den Zugriff auf den FTP-Server zuzulassen oder zu verweigern.
  • IFtpLogProvider – Der Anbieter verwendet diese Schnittstelle als generischen Ereignislistener.

Der FTP-Dienst verfügt nicht über tatsächliche Ereignisbenachrichtigungen, für die sich ein Anbieter registrieren kann, sie können jedoch Anbieter schreiben, die die IFtpLogProvider.Log()-Methode verwenden, um die Verarbeitung nach dem Ereignis bereitzustellen. Beispielsweise protokollieren alle fehlgeschlagenen Anmeldeversuche den Befehl "PASS" mit einem anderen Statuscode als "230", dem Statuscode für eine erfolgreiche FTP-Anmeldung. Durch die Erfassung zusätzlicher Informationen über den fehlgeschlagenen Anmeldeversuch, z. B. der IP-Adresse des Clients, der sich nicht angemeldet hat, ist es möglich, diese Informationen für die Bereitstellung zusätzlicher Funktionen zu nutzen, z. B. um den zukünftigen Zugriff von IP-Adressen auf Ihren FTP-Server zu blockieren.

Anbieterarchitektur und -logik

Die folgenden Beschreibungen fassen das Verhalten für diesen Authentifizierungsanbieter zusammen:

  • Wenn Sie den Anbieter auf Ihrem System registrieren, geben Sie die zu verwendende Datenbankverbindung und die Werte für die Anzahl der fehlgeschlagenen Anmeldeversuche und das Flut-Timeout in Ihren IIS-Konfigurationsdateien an.

  • Wenn der FTP-Dienst Ihren Anbieter lädt, stellt er die Werte aus Ihren IIS-Konfigurationsdateien an die Initialize()-Methode Ihres Anbieters bereit. Nachdem diese Werte in globalen Einstellungen gespeichert wurden, führt die Initialize()-Methode eine anfängliche Garbage Collection durch, um alle Informationen aus früheren FTP-Sitzungen zu säubern, die sich möglicherweise in der Datenbank befinden.

  • Wenn ein FTP-Client eine Verbindung mit dem FTP-Server herstellt, wird der Log()-Methode des Anbieters die Nachricht "ControlChannelOpened" vom FTP-Dienst gesendet. Die Log()-Methode überprüft die Datenbank, um festzustellen, ob die IP-Adresse des Clients blockiert wurde. Wenn ja, wird die Sitzung in der Datenbank gekennzeichnet.

  • Wenn der Benutzer seinen Benutzernamen und sein Kennwort eingibt, ruft der FTP-Dienst die AuthenticateUser()-Methode des Anbieters auf, wodurch überprüft wird, ob die Sitzung gekennzeichnet ist. Wenn die Sitzung gekennzeichnet ist, gibt der Anbieter false zurück und weist darauf hin, dass der Benutzer sich nicht anmelden konnte. Wenn die Sitzung nicht gekennzeichnet ist, werden der Benutzername und das Kennwort mit der Datenbank überprüft, um festzustellen, ob sie gültig sind. Wenn sie gültig sind, gibt die Methode true zurück, die angibt, dass der Benutzer gültig ist und sich anmelden kann.

  • Wenn der Benutzer keinen gültigen Benutzernamen und ein gültiges Kennwort eingibt, wird die Log()-Methode vom FTP-Dienst aufgerufen, und die Methode führt eine regelmäßige Garbage Collection aus, um sicherzustellen, dass die Anzahl der Fehler kleiner ist als das Flut-Timeout. Als Nächstes überprüft die Methode, ob die verbleibende Anzahl von Fehlern kleiner als die maximale Anzahl von Fehlern ist:

    • Wenn die maximale Anzahl von Fehlern nicht erreicht wurde, fügt die Methode der Datenbank eine Fehlerbenachrichtigung für die IP-Adresse des Clients hinzu.
    • Wenn die maximale Anzahl von Fehlern erreicht wurde, fügt die Methode die IP-Adresse des Clients der Liste der blockierten IP-Adressen in der Datenbank hinzu.
  • Wenn ein FTP-Client vom Server getrennt wird, ruft der FTP-Dienst die Log()-Methode des Anbieters auf und sendet die Nachricht "ControlChannelClosed". Die Log()-Methode nutzt diese Benachrichtigung, um die Garbage Collection für die Sitzung auszuführen.

Weitere Hinweise

  • Dieser Anbieter macht Funktionen für die Benutzer- und IP-Adressüberprüfung verfügbar, stellt jedoch keine Implementierung für Rollensuchvorgänge bereit. Das heißt, es wäre relativ einfach, eine zusätzliche Tabelle für Benutzer-zu-Rollen-Zuordnungen hinzuzufügen und die IFtpRoleProvider.IsUserInRole()-Methode dem Anbieter hinzuzufügen, aber das liegt außerhalb des Umfangs dieser exemplarischen Vorgehensweise.
  • Dieser Anbieter führt während des Authentifizierungsprozesses eine kleine Anzahl von Aufrufen an den SQL-Datenbankserver durch. Durch die Konsolidierung einiger SQL-Anweisungen in einzelne zusammengesetzte Abfragen oder gespeicherte Prozeduren können Sie die Anzahl der Abrufe der Datenbank weiter reduzieren, aber das liegt außerhalb des Umfangs dieser exemplarischen Vorgehensweise.

Schritt 1: Einrichten der Projektumgebung

In diesem Schritt erstellen Sie ein Projekt in Visual Studio 2008 für den Demoanbieter.

  1. Öffnen Sie Microsoft Visual Studio 2008.

  2. Klicken Sie auf das Menü Datei, dann auf Neu und dann auf Projekt.

  3. Im Dialogfeld Neues Projekt:

    • Wählen Sie Visual C# als Projekttyp aus.
    • Wählen Sie Klassenbibliothek als Vorlage aus.
    • Geben Sie FtpAddressRestrictionAuthentication als Namen des Projekts ein.
    • Klicken Sie auf OK.
  4. Wenn das Projekt geöffnet wird, fügen Sie der FTP-Erweiterbarkeitsbibliothek einen Verweispfad hinzu:

    • Klicken Sie auf Projekt und dann auf FtpAddressRestrictionAuthentication-Eigenschaften.

    • Klicken Sie auf die Schaltfläche Verweispfade.

    • Geben Sie den Pfad zur FTP-Erweiterbarkeitsassembly für Ihre Windows-Version ein, wobei „C:“ Ihr Betriebssystemlaufwerk ist.

      • Für Windows Server 2008 und Windows Vista:

        C:\Windows\assembly\GAC_MSIL\Microsoft.Web.FtpServer\7.5.0.0__31bf3856ad364e35
        
      • Für Windows 7:

        C:\Program Files\Reference Assemblies\Microsoft\IIS
        
    • Klicken Sie auf Ordner hinzufügen.

  5. Fügen Sie dem Projekt einen Schlüssel mit einem starken Namen hinzu:

    • Klicken Sie auf Projekt und dann auf FtpAddressRestrictionAuthentication-Eigenschaften.
    • Klicken Sie auf die Registerkarte Signierung.
    • Aktivieren Sie das Kontrollkästchen Assembly signieren.
    • Wählen Sie aus dem Dropdownfeld mit starkem Schlüsselnamen <Neu …> aus.
    • Geben Sie FtpAddressRestrictionAuthenticationKey für den Schlüsseldateinamen ein.
    • Geben Sie bei Bedarf ein Kennwort für die Schlüsseldatei ein; deaktivieren Sie andernfalls das Kontrollkästchen Meine Schlüsseldatei mit einem Kennwort schützen.
    • Klicken Sie auf OK.
  6. Optional: Sie können ein benutzerdefiniertes Build-Ereignis hinzufügen, um die DLL automatisch dem globalen Assemblycache (GAC) auf Ihrem Entwicklungscomputer hinzuzufügen:

    • Klicken Sie auf Projekt und dann auf FtpAddressRestrictionAuthentication-Eigenschaften.

    • Klicken Sie auf die Registerkarte Buildereignisse.

    • Geben Sie im Dialogfeld Befehlszeile für Post-Build-Ereignis Folgendes ein:

      net stop ftpsvc
      call "%VS90COMNTOOLS%\vsvars32.bat">null
      gacutil.exe /if "$(TargetPath)"
      net start ftpsvc
      
  7. Speichern Sie das Projekt.

Schritt 2: Erstellen der Erweiterbarkeitsklasse

In diesem Schritt implementieren Sie die Erweiterbarkeitsschnittstelle der Protokollierung für den Demoanbieter.

  1. Fügen Sie einen Verweis auf die FTP-Erweiterbarkeitsbibliothek für das Projekt hinzu:

    • Klicken Sie auf Projekt, und klicken Sie dann auf Verweis hinzufügen...
    • Klicken Sie auf der Registerkarte .NET auf Microsoft.Web.FtpServer.
    • Klicken Sie auf OK.
  2. Fügen Sie einen Verweis auf System.Web für das Projekt hinzu:

    • Klicken Sie auf Projekt, und klicken Sie dann auf Verweis hinzufügen...
    • Klicken Sie auf der Registerkarte .NET auf System.Web.
    • Klicken Sie auf OK.
  3. Fügen Sie einen Verweis auf System.Configuration für das Projekt hinzu:

    • Klicken Sie auf Projekt, und klicken Sie dann auf Verweis hinzufügen...
    • Klicken Sie auf der Registerkarte .NET auf System.Configuration.
    • Klicken Sie auf OK.
  4. Fügen Sie einen Verweis auf System.Data für das Projekt hinzu:

    • Klicken Sie auf Projekt, und klicken Sie dann auf Verweis hinzufügen.
    • Klicken Sie auf der Registerkarte .NET auf System.Data.
    • Klicken Sie auf OK.
  5. Fügen Sie den Code für die Authentifizierungsklasse hinzu:

    • Doppelklicken Sie im Projektmappen-Explorer auf die Datei Class1.cs.

    • Entfernen Sie den vorhandenen Code.

    • Fügen Sie den folgenden Code in den Editor ein:

      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. Speichern und kompilieren Sie das Projekt.

Hinweis

Wenn Sie die optionalen Schritte zur Registrierung der Assemblys im GAC nicht verwendet haben, müssen Sie die Assemblys manuell auf Ihren IIS-Computer kopieren und die Assemblys mit dem Tool Gacutil.exe zum GAC hinzufügen. Weitere Informationen finden Sie unter Gacutil.exe (Global Assembly Cache-Tool).

Schritt 3: Hinzufügen des Demoanbieters zu FTP

In diesem Schritt fügen Sie den Demoanbieter zu Ihrem FTP-Dienst und der Standardwebsite hinzu.

  1. Ermitteln Sie die Assembly-Informationen für den Erweiterbarkeitsanbieter:

    • Öffnen Sie im Windows-Explorer Ihren Pfad C:\Windows\assembly, wobei „C:“ Ihr Betriebssystemlaufwerk ist.
    • Suchen Sie die FtpAddressRestrictionAuthentication-Assembly.
    • Klicken Sie mit der rechten Maustaste auf die Assembly und klicken Sie dann auf Eigenschaften.
    • Kopieren Sie den Wert Culture, z. B. Neutral.
    • Kopieren Sie die Version-Nummer, z. B. 1.0.0.0.
    • Kopieren Sie den Wert Public Key Token, z. B. 426f62526f636b73.
    • Klicken Sie auf Abbrechen.
  2. Fügen Sie mithilfe der Informationen aus den vorherigen Schritten den Erweiterungsanbieter zur globalen Liste der FTP-Anbieter hinzu, und konfigurieren Sie die Optionen für den Anbieter:

    • Derzeit gibt es keine Benutzeroberfläche, über die Sie Eigenschaften für ein benutzerdefiniertes Authentifizierungsmodul hinzufügen können, sodass Sie die folgende Befehlszeile verwenden müssen:

      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
      

    Hinweis

    Die Verbindungszeichenfolge, die Sie im connectionString-Attribut angeben, muss eine gültige Anmeldung für Ihre Datenbank sein.

  3. Hinzufügen des benutzerdefinierten Anbieters zu einer Site:

    • Derzeit gibt es keine Benutzeroberfläche, mit der Sie einer Site benutzerdefinierte Funktionen hinzufügen können. Sie müssen also die folgende Befehlszeile verwenden:

      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
      

    Hinweis

    Diese Syntax deaktiviert die FTP-Standardauthentifizierung, und es ist wichtig, dass Sie die Standardauthentifizierung bei Verwendung dieses Authentifizierungsanbieters deaktivieren. Andernfalls kann ein Angreifer, wenn die IP-Adresse eines Angreifers von diesem Authentifizierungsanbieter blockiert wurde, weiterhin Konten angriffen, die die Standardauthentifizierung verwenden.

  4. Hinzufügen einer Autorisierungsregel für den Authentifizierungsanbieter:

    • Doppelklicken Sie im Hauptfenster auf FTP-Autorisierungsregeln.

    • Klicken Sie im Bereich Aktionen auf Zulassungsregel hinzufügen....

    • Wählen Sie Angegebene Benutzer für die Zugriffsoption aus.

    • Geben Sie einen Benutzernamen ein.

      Hinweis

      Der Benutzername muss außerhalb dieser Liste der Schritte in die Datenbank eingegeben werden.

    • Wählen Sie Lesen und/oder Schreiben für die Option Berechtigungen aus.

    • Klicken Sie auf OK.

Schritt 4: Verwenden des Anbieters mit FTP 7.5

Wenn FTP-Clients eine Verbindung mit Ihrer FTP-Site herstellen, versucht der FTP-Dienst, Benutzer mit Ihrem benutzerdefinierten Authentifizierungsanbieter mithilfe von Konten zu authentifizieren, die in der Datenbank gespeichert sind. Wenn ein FTP-Client nicht authentifiziert werden kann, verfolgt der Anbieter die IP-Adresse und das Datum/die Uhrzeit des Fehlers in der Datenbank. Wenn sich ein FTP-Client für die in der Einstellung logonAttempts angegebene Anzahl von Fehlern und innerhalb des in der Einstellung floodSeconds angegebenen Zeitrahmens nicht von einer bestimmten IP-Adresse aus anmelden kann, blockiert der Anbieter die Anmeldung bei dieser IP-Adresse den FTP-Dienst.

Hinweis

Dieser Beispielanbieter implementiert die Authentifizierungslogik für den FTP-Dienst, stellt jedoch kein Administratormodul zum Verwalten der Daten in der Datenbank bereit. Beispielsweise können Sie die Liste der FTP-Benutzerkonten, gesperrten IP-Adressen oder Authentifizierungsfehler mit diesem Anbieter nicht verwalten. Um die Daten mithilfe des IIS-Managers zu verwalten, können Sie die IIS-Datenbank-Manager verwenden. Weitere Informationen finden Sie in den folgenden Themen:

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

Zusätzliche Informationen

Sie können das folgende SQL-Skript für Microsoft SQL Server verwenden, um die erforderliche Datenbank und Tabellen zu erstellen. Um dieses Skript zu verwenden, müssen Sie den Namen der Datenbank und den Speicherort der Datenbankdateien aktualisieren. In SQL Server würden Sie das Skript in einem neuen Abfragefenster ausführen und dann eine Datenbankanmeldung erstellen, die Sie mit Ihrer Verbindungszeichenfolge verwenden werden.

Hinweis

Möglicherweise möchten Sie das SQL-Skript so ändern, dass die Datenbank an einem anderen Speicherort als c:\databases gespeichert wird.

/****** 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

Zusammenfassung

In dieser Anleitung haben Sie folgendes gelernt:

  • Ein Projekt in Visual Studio 2008 für einen benutzerdefinierten FTP-Anbieter zu erstellen.
  • Die Erweiterbarkeitsschnittstellen für einen benutzerdefinierten FTP-Anbieter zu implementieren.
  • Ihrem FTP-Dienst einen benutzerdefinierten FTP-Anbieter hinzuzufügen.