Powrót do strony głównej

Proxy w C# z autentykacją FTP

Uniwersalny serwer proxy w C# obsługuje HTTP, HTTPS i FTP z automatyczną autentykacją. Przechwytuje komendy USER/PASS, używa asynchronicznego tunelowania. Pełny kod i instrukcje konfiguracji dla specjalistów IT.

Tworzymy proxy FTP w C# z autoautentykacją
Advertisement 728x90

Uniwersalny serwer proxy w C# z obsługą uwierzytelniania FTP

Serwer proxy w C# akceptuje połączenia na lokalnym porcie, określa typ ruchu i przekierowuje go z przetwarzaniem. Dla FTP implementuje się automatyczne podstawianie danych logowania: przechwytywanie poleceń USER/PASS i zamiana na określone login/hasło. Obsługiwane są HTTP, HTTPS (metoda CONNECT) i FTP bez zewnętrznych bibliotek, tylko TcpClient i NetworkStream.

Architektura i inicjalizacja

Serwer używa TcpListener do nasłuchiwania na Loopback na określonym porcie. Dane logowania FTP przechowywane są w ConcurrentDictionary dla bezpiecznego dostępu wielowątkowego.

public class FullProxyServer
{
    private readonly TcpListener _listener;
    private readonly ConcurrentDictionary<string, string> _ftpCredentials;
    
    public FullProxyServer(int proxyPort, string ftpServer, string ftpUser, string ftpPassword)
    {
        _listener = new TcpListener(IPAddress.Loopback, proxyPort);
        _ftpCredentials = new ConcurrentDictionary<string, string>();
        _ftpCredentials.TryAdd(ftpServer, $"{ftpUser}:{ftpPassword}");
    }
}

Uruchomienie w Program.Main:

Google AdInline article slot
static async Task Main(string[] args)
{
    var proxy = new FullProxyServer(8888, "ftp.example.com", "my_username", "my_password");
    await proxy.StartAsync();
}

Określenie typu żądania

Po przyjęciu danych od klienta analizowany jest bufor:

string request = Encoding.ASCII.GetString(buffer, 0, bytesRead);

if (request.StartsWith("CONNECT"))
{
    await HandleConnectRequest(clientStream, request, id);
}
else if (request.Contains("FTP") || request.Contains("ftp"))
{
    await HandleFtpRequest(clientStream, buffer, bytesRead, id);
}
else
{
    await TunnelConnection(clientStream, buffer, bytesRead, id);
}

To pozwala kierować ruch według protokołu.

Obsługa HTTPS przez CONNECT

Dla HTTPS ustanawiany jest bezpośredni tunel:

Google AdInline article slot
var parts = connectLine.Split(' ');
string host = targetParts[0];
int port = int.Parse(targetParts[1]);

var targetClient = new TcpClient();
await targetClient.ConnectAsync(host, port);

string response = "HTTP/1.1 200 Connection Established\r\n\r\n";
await clientStream.WriteAsync(responseBytes);

await TunnelBidirectional(clientStream, targetStream, id);

Dwukierunkowe kopiowanie strumieni zaimplementowane asynchronicznie z CancellationTokenSource dla płynnego zamykania.

private async Task TunnelBidirectional(NetworkStream clientStream, NetworkStream targetStream, string id)
{
    var cts = new CancellationTokenSource();
    
    var clientToTarget = CopyDataAsync(clientStream, targetStream, id, "C->T", cts.Token);
    var targetToClient = CopyDataAsync(targetStream, clientStream, id, "T->C", cts.Token);
    
    await Task.WhenAny(clientToTarget, targetToClient);
    cts.Cancel();
}

Proxy FTP z automatyczną autoryzacją

Kluczową funkcjonalnością jest przechwytywanie i modyfikacja poleceń FTP. Po otrzymaniu USER od klienta podstawiany jest określony username, na 331 (password required) – wysyłany jest PASS.

private async Task FtpTunnelWithAuth(NetworkStream clientStream, NetworkStream ftpStream,
                                      string username, string password, string id)
{
    bool authenticated = false;
    
    // Przechwytujemy odpowiedzi serwera FTP
    if (!authenticated && response.Contains("331")) // Żądanie hasła
    {
        string passCmd = $"PASS {password}\r\n";
        await ftpStream.WriteAsync(passBytes);
    }
    
    // Przechwytujemy polecenia klienta
    if (!authenticated && command.ToUpper().StartsWith("USER"))
    {
        // Podmieniamy login na nasz
        string userCmd = $"USER {username}\r\n";
        await ftpStream.WriteAsync(userBytes);
    }
}

Późniejsze polecenia tunelowane są bez zmian po udanej autoryzacji (230).

Google AdInline article slot

Konfiguracja klientów

  • Przeglądarka: Proxy HTTP/HTTPS/FTP — 127.0.0.1:8888
  • FileZilla lub analog: Generic Proxy, host 127.0.0.1, port 8888

Logi pokazują proces:

[abc12345] FTP zapytanie
[abc12345] ->FTP: USER anonymous\r\n
[abc12345] FTP->: 331 User name okay, need password\r\n
[abc12345] Automatyczne wysłanie hasła
[abc12345] FTP->: 230 User logged in\r\n
[abc12345] Autoryzacja udana

Zalety implementacji

Serwer lekki, mieści się w jednym pliku, używa async/await do skalowalności. Nadaje się dla:

  • Omijania ograniczeń proxy w sieciach korporacyjnych
  • Automatyzacji dostępu do FTP bez zmiany konfiguracji klientów
  • Debugowania ruchu z logowaniem
  • Rozszerzenia o inne protokoły z analizą poleceń

Co jest ważne

  • Automatyczne podstawianie USER/PASS dla FTP bez powiadamiania klienta.
  • Asynchroniczne dwukierunkowe tunelowanie z NetworkStream.
  • Bezpieczne przechowywanie danych logowania w ConcurrentDictionary.
  • Obsługa HTTPS CONNECT i prostego HTTP.
  • Wieloplatformowość na .NET 5+.

— Editorial Team

Advertisement 728x90

Czytaj dalej