OpenSSH: świeże zmiany w konfiguracji, które warto wdrożyć od ręki

0
14
Rate this post

Nawigacja:

Krótka scenka z serwerowni: „działało od lat, aż przyszły audyty”

Administrator łączy się jak co dzień przez SSH do starego, ale kluczowego serwera. Loguje się, odpala narzędzia i kątem oka widzi w logach ostrzeżenia: „deprecated algorithm”, „server offers weak kex”. Do tej pory nikt się tym nie przejmował – wszystko przecież „działa od lat”.

Do czasu, aż pojawia się audyt bezpieczeństwa albo nowe wymagania compliance. Raport wprost wskazuje: przestarzałe algorytmy w OpenSSH, brak ograniczeń dostępu, logowanie na roota, dopuszczone hasła. I nagle proste „działa” przestaje wystarczać – trzeba modernizować konfigurację, ale tak, żeby nie odciąć sobie zdalnego dostępu do produkcji w środku dnia.

Konfiguracja OpenSSH przestaje być kwestią „fanaberii security”. To realne ryzyko: wycieki kluczy, łatwiejsze ataki brute-force, zgodność z RODO/ISO/PCI-DSS/SOC2. Dobra wiadomość jest taka, że część zmian da się wdrożyć niemal od ręki – świadomie, z planem, z możliwością szybkiego wycofania – i od razu mocno podnieść poziom bezpieczeństwa serwera SSH.

Dlaczego klasyczne szablony sshd_config są dziś problemem

Stare poradniki i domyślne konfiguracje działają na Twoją niekorzyść

Wiele serwerów nadal działa na konfiguracji OpenSSH skopiowanej lata temu z pierwszego wyniku wyszukiwarki albo z domyślnego pliku sshd_config. Przez lata ciężko było „zepsuć” SSH – domyślne ustawienia były wystarczająco dobre. Problem w tym, że krajobraz zagrożeń i sam OpenSSH mocno się zmieniły.

Stare szablony zawierają dziś:

  • algorytmy szyfrowania uznawane za słabe lub przestarzałe,
  • mechanizmy wymiany kluczy (KEX) oparte na SHA1 i małych grupach,
  • słabe MAC (mechanizmy sprawdzania integralności),
  • przyzwolenie na logowanie hasłem, często nawet na konto root,
  • niepotrzebne funkcje typu X11Forwarding, TCP forwarding bez kontroli.

Do tego dochodzi fakt, że OpenSSH w kolejnych wersjach stopniowo wycofuje archaiczne elementy, a Ty – mając starą konfigurację – możesz nagle zobaczyć, że upgrade pakietu zmienia domyślne zachowanie. W efekcie nie tylko masz gorsze bezpieczeństwo, ale także zaskoczenia przy aktualizacjach systemu.

Wycofywane algorytmy: DSA, SHA1, słabe MAC i KEX

Twórcy OpenSSH od lat zapowiadają i realizują „sprzątanie” w algorytmach. Część z nich jest już domyślnie wyłączona, inne są oznaczone jako przestarzałe i prędzej czy później znikną. Dotyczy to m.in.:

  • kluczy DSA – praktycznie martwe, nie należy ich używać,
  • algorytmów wymiany kluczy z SHA1 – np. diffie-hellman-group1-sha1,
  • MAC opartych o MD5 i słabe warianty HMAC-SHA1,
  • szyfrów takich jak 3des, arcfour czy tryb CBC w słabszych długościach klucza.

Jeżeli konfiguracja serwera eksplicytnie dopuszcza takie algorytmy (wpisami Ciphers, KexAlgorithms, MACs skopiowanymi sprzed lat), nawet aktualny OpenSSH może nadal je oferować, o ile nie zostały twardo usunięte z kodu.

Konsekwencje pozostawienia starej konfiguracji

Trzymanie się starego sshd_config to nie tylko problem estetyczny. Pociąga to za sobą kilka praktycznych skutków:

  • Większa powierzchnia ataku – atakujący może wybrać najsłabszy dopuszczony przez serwer algorytm.
  • Problemy z klientami – nowoczesne klienty (np. OpenSSH 9.x, PuTTY w nowych wersjach) zaczynają domyślnie odrzucać słabe algorytmy, co generuje ostrzeżenia lub błędy po ich stronie.
  • Brak zgodności z politykami bezpieczeństwa – audyty szybko wyłapują logowanie hasłem, brak ograniczeń dla roota, przestarzałe szyfry.
  • Ryzyko „niespodzianek” przy aktualizacji – zmiany domyślnych ustawień w OpenSSH mogą zerwać kompatybilność z bardzo starymi klientami, jeśli od lat nie było żadnej kontroli nad konfiguracją.

Efekt jest taki, że serwer „działa”, ale jest coraz bardziej odstający od dobrych praktyk. W pewnym momencie nie da się już przeprowadzić małej kosmetycznej korekty – trzeba robić skokową modernizację.

Czerwone flagi w typowym starym sshd_config

Przy szybkim przeglądzie konfiguracji można wychwycić kilka klasycznych „czerwonych flag”:

  • PermitRootLogin yes – pełne logowanie na roota po SSH, często z hasłem.
  • PasswordAuthentication yes – dopuszczenie hasła, czyli ataki brute-force i password spraying.
  • ChallengeResponseAuthentication yes – otwarta furtka na różne mechanizmy haseł jednorazowych/klasycznych, czasem źle skonfigurowanych.
  • X11Forwarding yes – niepotrzebne ryzyko, jeśli nikt tego świadomie nie używa.
  • AllowUsers bez przemyślanej listy lub w ogóle brak ograniczeń użytkowników.
  • Brak wpisów typu ClientAliveInterval – sesje wiszące latami bez nadzoru.

Do tego nierzadko dochodzi brak logowania do osobnych plików, rozdzielenia portów administracyjnych, kontroli nad agent forwardingiem. Sam fakt, że serwer jest dostępny po domyślnym porcie 22 z całego internetu, nie jest jeszcze grzechem śmiertelnym, ale pokazuje brak refleksji nad całością.

Świadomy minimalizm zamiast „zostawmy wszystko”

Zamiast doglądać listy dziesiątek wspieranych algorytmów i opcji, lepiej przyjąć zasadę świadomego minimalizmu: tylko to, czego naprawdę trzeba. Mniej dopuszczonych szyfrów oznacza mniej wektorów ataku i mniej niespodzianek przy aktualizacji. Klarowny sshd_config jest łatwiejszy do przeglądu przy audycie i prostszy w utrzymaniu.

Kilka dobrze przemyślanych zmian potrafi zrobić większą różnicę niż rozbudowane, „magiczne” konfiguracje bez zrozumienia. Kluczem jest wiedzieć, które elementy można od razu zaostrzyć, a które wymagają fazy przejściowej, żeby nie odciąć użytkowników.

Zbliżenie na szafę serwerową z migającymi diodami i kablami Ethernet
Źródło: Pexels | Autor: Brett Sayles

Szyfry, MAC i Kex – nowoczesny zestaw, który można wprowadzić od razu

Algorytmy szyfrowania – co wyłączyć, co zostawić

OpenSSH obsługuje sporo szyfrów, ale praktycznie nie ma powodu, by dziś trzymać archaiczne opcje typu 3des, arcfour czy tryby CBC dla krótkich kluczy. Współczesny, bezpieczny zestaw można uprościć do kilku pozycji.

Przydatna, „od ręki” stosowana linia w sshd_config może wyglądać tak:

Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com

Dlaczego taki wybór:

  • chacha20-poly1305@openssh.com – bardzo szybki na maszynach bez sprzętowego AES (np. VPS, stare CPU), dobrze zaprojektowany, nowoczesny szyfr strumieniowy z wbudowanym uwierzytelnianiem.
  • aes256-gcm@openssh.com – dla środowisk, które wolą AES-256 i mają wsparcie AES-NI, tryb GCM zapewnia autentyczność i lepszą wydajność niż stare CBC.
  • aes128-gcm@openssh.com – AES-128 w GCM, często wystarczający, a bardziej wydajny.

Jeżeli serwer musi wspierać naprawdę starego klienta, można dodać awaryjnie np. aes256-ctr,aes128-ctr, ale lepiej nie robić z tego standardu, tylko wyjątek z konkretnym uzasadnieniem.

KexAlgorithms – bezpieczna wymiana kluczy

Mechanizmy wymiany kluczy (KEX) gwarantują, że sesja SSH ma właściwości poufności nawet przy podsłuchu. Stare algorytmy, takie jak diffie-hellman-group1-sha1 czy diffie-hellman-group14-sha1, są dziś zbyt słabe i nie powinny się znaleźć w nowym sshd_config.

Bezpieczna, nowoczesna linia może wyglądać tak:

KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512

Co tu się dzieje:

  • curve25519-sha256 – obecnie złoty standard w OpenSSH, szybki, dobrze przeanalizowany, domyślny w wielu instalacjach.
  • curve25519-sha256@libssh.org – kompatybilność z klientami, które nadal używają wariantu z nazwą domenową.
  • diffie-hellman-group16-sha512 oraz group18-sha512 – silne grupy DH dopełniające ofertę w razie potrzeby.

Przy tym zestawie wycinasz stare algorytmy oparte na SHA1, minimalizujesz ryzyko i jednocześnie zachowujesz kompatybilność z większością współczesnych klientów OpenSSH.

MACs – ochrona integralności sesji

MAC (Message Authentication Code) zapewnia, że dane przesyłane w sesji SSH nie zostały zmodyfikowane. Tu też zaszło sporo zmian. Starsze HMAC-SHA1 i w ogóle MD5 wypadają z obiegu.

Bezpieczna konfiguracja MAC może wyglądać tak:

MACs umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com

Kluczowe elementy:

  • umac-128-etm@openssh.com – szybki MAC, typowo pierwszy wybór, bardzo wydajny.
  • hmac-sha2-256-etm@openssh.com oraz hmac-sha2-512-etm@openssh.com – nowoczesne, bezpieczne HMAC oparte na SHA-2.
  • Dopisek etm@openssh.com (Encrypt-then-MAC) – preferowana konstrukcja, gdzie najpierw szyfrujemy, potem liczymy MAC; lepsza od starszych „mac-then-encrypt”.

Krótka lista zamiast przypadkowej mieszanki

W wielu instalacjach lista Ciphers/KexAlgorithms/MACs przez lata była rozbudowywana – coś dopisywane, coś zostawione „na wszelki wypadek”. Rezultat: przypadkowa, długa mieszanka, której nikt już nie rozumie.

Bezpieczniej jest wyczyścić te linie i wpisać krótką, celową listę dobrze przeanalizowanych algorytmów. Zmniejszasz tym samym ryzyko, że serwer „zsunie się” podczas negocjacji do starego, słabego algorytmu tylko po to, by dogadać się z zapomnianym, dawnym klientem.

Klucze i uwierzytelnianie – przejście na Ed25519, FIDO2 i koniec z hasłami

Dlaczego Ed25519 i co z RSA w praktyce

W świecie kluczy SSH nastąpiła wyraźna zmiana trendu: Ed25519 stał się domyślnym, pierwszym wyborem dla nowych kluczy użytkowników. Jest krótki, szybki, oferuje bardzo dobry poziom bezpieczeństwa i jest dobrze wspierany przez współczesne klienty OpenSSH.

Klucze RSA nadal są powszechne, ale:

  • muszą mieć odpowiednią długość (co najmniej 3072, a lepiej 4096 bitów),
  • są wolniejsze w generowaniu i w niektórych operacjach,
  • mogą powodować problemy z bardzo starymi urządzeniami przy dłuższych długościach klucza.

Bezpieczna strategia migracji wygląda tak:

  1. Wygenerowanie dla użytkowników nowych kluczy Ed25519:
    ssh-keygen -t ed25519 -a 100
  2. Dodanie nowych kluczy do authorized_keys przy pozostawieniu kluczy RSA na krótki okres przejściowy.
  3. Ustawienie w sshd_config preferencji algorytmów host key (np. HostKeyAlgorithms, jeśli trzeba), tak aby Ed25519 był pierwszy.
  4. Stopniowe wycofywanie starych kluczy RSA po potwierdzeniu, że wszystkie automaty i użytkownicy działają już na Ed25519.

Taki krok po kroku scenariusz minimalizuje ryzyko, że po wyłączeniu RSA nagle jakiś stary skrypt czy embedded device przestanie się łączyć z serwerem.

Klucze sprzętowe i FIDO2/U2F – ecdsa-sk, ed25519-sk

Nowością, która naprawdę zmienia zasady gry, są klucze sprzętowe: tokeny FIDO2/U2F, które integrują się z OpenSSH pod postacią typów ecdsa-sk i ed25519-sk. Zamiast pliku z kluczem prywatnym na dysku użytkownika, faktyczne operacje kryptograficzne wykonuje fizyczny token (np. YubiKey).

Kluczowe zalety:

  • Utrudniona kradzież klucza – nawet jeśli ktoś skopiuje plik publiczny, bez fizycznego tokena i potwierdzenia (dotknięcia, PIN) nic nie zrobi.
  • Odporność na proste ataki phishingowe – token jest powiązany z konkretną domeną/serwerem.
  • Praktyczne przejście na klucze sprzętowe

    Moment, w którym pierwszy raz ktoś zgubi token FIDO, zwykle kończy się telefonem „nic nie działa, odetnijcie mi blokadę”. Zamiast wtedy nerwowo grzebać w konfiguracji, lepiej mieć wcześniej przygotowaną procedurę i odpowiednie wpisy w sshd_config.

    Podstawowy scenariusz wdrożenia kluczy sprzętowych może wyglądać tak:

    1. Włączenie obsługi FIDO2 po stronie klienta (nowszy OpenSSH, biblioteki U2F/FIDO w systemie użytkownika).
    2. Wygenerowanie klucza na tokenie, np.:
      ssh-keygen -t ed25519-sk -O resident -O application=ssh://twoj-serwer
    3. Dodanie wygenerowanego klucza publicznego do authorized_keys.
    4. Testowe logowanie, sprawdzenie, czy użytkownik rzeczywiście musi dotknąć tokena lub podać PIN.

    Na początku rozsądnie jest zostawić dotychczasowy klucz Ed25519 jako awaryjny, ale od razu komunikować, że celem jest pełne przejście na klucz sprzętowy. W większych zespołach dobrze sprawdza się zasada „konto produkcyjne tylko z tokenem, konto awaryjne z kluczem plikowym ograniczone do konkretnych komend”.

    Dla administratorów, którzy zarządzają wieloma serwerami, dobrą praktyką jest utworzenie osobnego klucza sprzętowego do środowisk produkcyjnych, a innego do testowych. Ogranicza to skutki zgubienia jednego z nich i zmusza do uporządkowania dostępu.

    Wyłączanie logowania hasłami – bezpieczne przejście

    Największy opór zwykle pojawia się, gdy na spotkaniu ktoś mówi: „wyłączmy logowanie hasłem na wszystkich serwerach”. Natychmiast wracają historie o serwerze „gdzieś w magazynie”, do którego loguje się tylko jedna aplikacja z twardo zakodowanymi danymi.

    Bezpieczniejszy wariant to dwuetapowe zaostrzenie:

    1. Najpierw wymuszenie kluczy tam, gdzie to możliwe, ale bez globalnego wyłączania haseł.
    2. Później stopniowe blokowanie haseł przy pomocy reguł Match, zaczynając od najbardziej krytycznych serwerów i kont.

    Minimalnym celem powinno być sprowadzenie haseł do absolutnych wyjątków. Dla zwykłych użytkowników i administratorów docelowe ustawienia mogą wyglądać np. tak:

    PubkeyAuthentication yes
    PasswordAuthentication no
    KbdInteractiveAuthentication no
    ChallengeResponseAuthentication no
    

    Problemem pozostają różne stare automaty i urządzenia. Zamiast utrzymywać dla nich słabe ustawienia globalnie, lepiej wydzielić osobne konto lub osobny serwer z mocno ograniczonym dostępem, a w idealnym scenariuszu – przepisać integrację na klucze.

    Dodatkowym krokiem, który świetnie działa w praktyce, jest czasowe zaostrzenie ustawień na jednym, mniej krytycznym serwerze i dokładne przejrzenie logów. Jeśli w ciągu kilku tygodni nie pojawią się błędy uwierzytelniania dla legalnych użytkowników, konfigurację można skopiować szerzej.

    authorized_keys – więcej niż zwykła lista kluczy

    W wielu środowiskach authorized_keys rośnie latami, aż w końcu nikt nie wie, do kogo należą poszczególne wpisy. Przy zaostrzaniu konfiguracji warto potraktować ten plik jak prosty, ale bardzo skuteczny mechanizm kontroli dostępu.

    Każdy wpis w authorized_keys może mieć poprzedzające go opcje. Najważniejsze z nich, które przydają się od ręki:

  • command= – wymuszenie konkretnej komendy, np. dla kont backupowych;
  • from= – ograniczenie źródłowego adresu IP lub zakresu sieci;
  • permitopen= – kontrola, na jakie porty lokalne/zdalne można tunelować;
  • no-agent-forwarding, no-port-forwarding, no-pty – twarde ograniczenia funkcji SSH.

Przykład konta backupowego, które ma prawo tylko pobierać dane, z jednego adresu i nic więcej:

from="192.0.2.10",command="/usr/local/sbin/run-backup.sh",
no-agent-forwarding,no-port-forwarding,no-X11-forwarding,no-pty 
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... backup@skrypt

Taki wpis powoduje, że nawet jeśli klucz backupowy wycieknie, napastnik nie wejdzie w normalną powłokę, nie zestawi tunelu i nie uruchomi własnych binarek. Zobaczy jedynie to, co wykona skrypt backupowy.

Dobrym nawykiem jest także konsekwentne dodawanie komentarzy (ostatnie pole w linii z kluczem) w formacie np. imię.nazwisko@dział oraz data dodania. Ułatwia to później coroczne czyszczenie nieużywanych wpisów.

Specjalistka IT sprawdza serwery w nowoczesnym centrum danych
Źródło: Pexels | Autor: Christina Morillo

Uprawnienia użytkowników i dostęp: od PermitRootLogin po Match User

Root na SSH – kiedy naprawdę jest potrzebny

Jeden z klasycznych obrazków z audytu: kilka serwerów, a na każdym PermitRootLogin yes. Uzasadnienie: „przecież trzeba się czasem zalogować jako root”. Po bliższym przyjrzeniu okazuje się, że znacznie rzadziej niż wszystkim się wydaje.

Pierwszy, podstawowy krok to całkowite wyłączenie logowania roota hasłem i ograniczenie go tylko do kluczy:

PermitRootLogin prohibit-password

Jeszcze lepszym wariantem jest całkowite zablokowanie bezpośredniego logowania roota i wymuszenie sudo z konta imiennego:

PermitRootLogin no

W takim modelu każdy administrator ma swoje konto, loguje się kluczem, a podniesienie uprawnień odbywa się sudo z odpowiednim logowaniem w syslog. Dzięki temu audyt logów ma sens: wiadomo, kto, kiedy i na którym hoście wykonywał polecenia z prawami roota.

Czasem jednak pojawiają się specyficzne potrzeby, np. serwer „ratunkowy” lub środowisko, gdzie logowanie roota z kluczem sprzętowym jest dopuszczalne, ale tylko z określonych adresów IP. Tu do gry wchodzą reguły Match.

Match User, Match Address – selektywne zaostrzanie dostępu

Zamiast jednej, uniwersalnej polityki dla wszystkich, można skonstruować kilka, dopasowanych do typu użytkownika lub źródła połączenia. Administrator pracujący z sieci VPN może mieć inne zasady niż konto automatu z konkretnego hosta aplikacyjnego.

Prosty przykład: globalnie wyłączone hasła, dodatkowe zaostrzenie dla root, ale delikatniejszy tryb dla konta serwisowego z lokalnej sieci zarządzającej:

# Globalne, bezpieczne domyślne ustawienia
PasswordAuthentication no
PubkeyAuthentication yes

# Root tylko z kluczem i tylko z sieci administracyjnej
Match User root Address 10.0.0.0/24
    PermitRootLogin prohibit-password
    AuthenticationMethods publickey

# Konto serwisowe z sieci backupowej z możliwością hasła (tymczasowo)
Match User backup Address 192.0.2.0/24
    PasswordAuthentication yes
    AuthenticationMethods publickey,password

Taki układ pozwala schować „techniczny dług” (jak stare konto backupowe na hasło) w bardzo wąskiej regule, a nie rozwadniać polityki bezpieczeństwa dla całego serwera. Kiedy przychodzi czas na porządki, widać dokładnie, które wyjątki trzeba zlikwidować.

Reguły Match dobrze działają również w środowiskach z podwójnymi standardami: np. pracownicy łączą się z zewnątrz wyłącznie kluczami sprzętowymi, ale z sieci biurowej można wejść także zwykłym kluczem Ed25519. Konfiguracja „opisuje” politykę, zamiast ją rozmywać.

Ograniczenia powłoki i ForceCommand dla wybranych kont

Na niemal każdym serwerze znajdzie się kontenerowy użytkownik „do jednej aplikacji”, który w praktyce nie powinien mieć pełnej powłoki. Często jednak ma zwykłe /bin/bash lub /bin/sh, bo „inaczej nie działało”.

Jeśli dane konto ma pełnić ściśle określoną funkcję – np. odbierać tylko klucze do rsync lub uruchamiać określoną aplikację – można użyć kombinacji ForceCommand w sshd_config oraz opcji w authorized_keys.

Przykładowy fragment dla konta deploy, które ma prawo jedynie uruchamiać dedykowany skrypt wdrożeniowy:

Match User deploy
    X11Forwarding no
    AllowTcpForwarding no
    PermitTunnel no
    ForceCommand /usr/local/bin/deploy.sh

Po stronie klucza można to jeszcze wzmocnić:

command="/usr/local/bin/deploy.sh",no-port-forwarding,no-pty 
ssh-ed25519 AAAAC3Nza... deploy@ci

Dzięki temu nawet jeśli ktoś przechwyci klucz używany przez system CI, nie uruchomi na serwerze dowolnej komendy – zawsze skończy w skrypcie wdrożeniowym. Ewentualna szkoda jest ograniczona do tego, co potrafi ten skrypt.

Ograniczanie port forwarding i agent forwarding

Forwarding portów to jedna z najpotężniejszych funkcji SSH, ale i jedno z częstszych źródeł niespodzianek. Zdarza się, że ktoś latami używa serwera pośredniczącego jako wygodnego „tunelu do wszystkiego”, a potem nagle ruch produkcyjny przechodzi przez prywatny laptop.

Domyślnie warto podejść do tunelowania konserwatywnie:

AllowTcpForwarding no
PermitOpen none
X11Forwarding no
AllowAgentForwarding no

Następnie, tam gdzie tunelowanie jest rzeczywiście potrzebne (np. administracja bazą danych wyłącznie przez bastion), można włączyć je tylko dla wybranych użytkowników lub adresów:

Match User dba Address 198.51.100.0/24
    AllowTcpForwarding yes
    PermitOpen 127.0.0.1:5432

Podobnie z agent forwarding. Zostawianie globalnego AllowAgentForwarding yes zachęca do budowania kaskad SSH, które trudno potem prześledzić. Znacznie bezpieczniej jest włączyć agent forwarding tylko wtedy, gdy naprawdę jest potrzebny (np. dla krótkich sesji administracyjnych z zaufanego bastiona) i pod rygorem jasnych reguł w Match.

Drobny, ale praktyczny nawyk: gdy tylko pojawi się potrzeba „otworzenia tunelu”, można zadać sobie dwa pytania – czy naprawdę nie da się tego rozwiązać bezpośrednim dostępem z sieci administracyjnej oraz czy jest to rozwiązanie stałe, czy jednorazowe. W wielu przypadkach tymczasowy tunel na czas incydentu nie powinien trafiać do stałej konfiguracji.

Segmentacja dostępu przy pomocy wielu instancji sshd

Na bardziej wrażliwych serwerach dobrym narzędziem staje się nie tylko konfiguracja per użytkownik, ale fizyczne rozdzielenie dostępu przy pomocy kilku instancji sshd. Jedna instancja może nasłuchiwać na porcie 22 z twardszą polityką, druga na porcie administracyjnym dostępna wyłącznie z sieci zarządzającej.

Typowy scenariusz:

  • sshd@public – port 22, tylko klucze Ed25519, brak tunelowania, brak logowania roota, dostęp dla zwykłych użytkowników;
  • sshd@admin – port np. 2222, dostępny wyłącznie z sieci VPN/admin, dopuszczone klucze sprzętowe dla administratorów, możliwość tunelowania do zaplecza.

Technicznie sprowadza się to do dwóch plików konfiguracyjnych, np. /etc/ssh/sshd_config_public i /etc/ssh/sshd_config_admin, oraz dwóch jednostek systemd. Dzięki temu audyt staje się prostszy: wiadomo, że zwykły ruch użytkowników nigdy nie powinien trafiać na port administracyjny, a polityki można rozwijać niezależnie.

Z czasem taka segmentacja ułatwia też wprowadzanie zmian. Nowe, bardziej agresywne ustawienia testuje się najpierw na instancji administracyjnej (dostępnej tylko dla zespołu IT), a dopiero później przenosi na port „użytkowy”. W razie problemów powrót do starej konfiguracji sprowadza się do restartu jednej instancji, bez dotykania drugiej.

Logowanie, audyt i pułapki związane z VerboseMode

Na jednym z serwerów audyt zakończył się pozornie optymistycznie: „brak nieudanych logowań”. Dopiero rzut oka w konfigurację zdradził przyczynę – logowanie ustawione na minimum i brak sensownych danych w centralnym systemie SIEM. Problem nie był w atakach, tylko w tym, że nikt nie widział, co się dzieje.

Dla SSH logi są jedynym „taśmociągiem” informacji o tym, kto naprawdę dotyka serwerów. Domyślne poziomy często są zbyt oszczędne, szczególnie w starszych instalacjach. Rozsądny punkt wyjścia to zwiększenie szczegółowości bez zalania dysku śmieciami:

LogLevel VERBOSE
SyslogFacility AUTH

Poziom VERBOSE dodaje m.in. fingerprinty kluczy, co niezwykle ułatwia analizę sytuacji, gdy jeden użytkownik ma kilka tokenów sprzętowych albo trzeba dojść, który klucz został użyty do danego logowania. W połączeniu z centralnym logowaniem (rsyslog, journald forwarder, agent SIEM) daje to czytelny obraz aktywności.

Przy mocniej obciążonych bastionach można dla części kont dodatkowo włączyć rozszerzone logowanie komend po stronie powłoki (np. przez auditd czy tlog), natomiast poziom szczegółowości samego SSH zostawić jednolity. Nie ma sensu różnicować LogLevel per użytkownik – łatwo wtedy przeoczyć podejrzane logowania z „mniej ważnych” kont technicznych.

Jeśli serwer pełni funkcję wyłącznie „skrzynki skokowej” (jump host), logi SSH są de facto jedynym punktem odniesienia. Wtedy każda zmiana konfiguracji (nowe metody uwierzytelniania, inny zestaw Kex/HostKey) powinna iść w parze z przeglądem, czy logi wciąż zawierają wszystkie potrzebne informacje do rekonstrukcji przepływu sesji.

GracePeriod, MaxAuthTries i ochrona przed „kluczo-spamem”

Podczas jednego z testów penetracyjnych zespół zobaczył dziesiątki wpisów typu „no matching key found” dla jednego źródła IP. Atakujący po prostu „przewijał” setki kluczy, licząc na to, że któryś zostanie przyjęty. Serwer nie był przeciążony, ale noise w logach utrudniał wychwycenie realnych problemów.

OpenSSH pozwala w prosty sposób ograniczyć czas i liczbę prób uwierzytelnienia w ramach pojedynczej sesji:

LoginGraceTime 30
MaxAuthTries 3
MaxStartups 10:30:100

LoginGraceTime w sekundach określa, ile czasu klient ma na poprawne uwierzytelnienie. Skrócenie z typowych 120 na 30–45 sekund w większości środowisk nie sprawia realnego problemu, za to ogranicza długość trwania prób słownikowych. MaxAuthTries odpowiada za liczbę „podejść” w jednej sesji – zejście do 3 lub nawet 2 zniechęca ataki, które liczą na przetestowanie wielu kombinacji w ramach jednego połączenia.

Parametr MaxStartups kontroluje natomiast, ile nierozpoznanych jeszcze sesji (niezalogowanych) może być równocześnie utrzymywanych. Składnia start:rate:full pozwala zbudować „suwak”: od ilu połączeń zaczyna się losowe odrzucanie, z jakim prawdopodobieństwem, aż do całkowitego odmówienia nowych sesji. To prosty i skuteczny bezpiecznik przeciwko zalewaniu serwera tysiącami półotwartych sesji SSH.

W praktyce nawet niewielkie zaostrzenie tych trzech parametrów znacząco czyści logi. Zamiast setek ostrzeżeń o błędnych próbach z pojedynczego IP dostaje się kilka wyraźnych wpisów o przekroczeniu limitów i odcięciu klienta.

Banery, komunikaty i rozdzielenie diagnostyki od informacji dla użytkownika

Po incydencie z nieautoryzowanym logowaniem w jednej firmie ktoś wpadł na pomysł, by w banerze SSH wypisać długi komunikat o monitorowaniu, polityce bezpieczeństwa i konsekwencjach. Problemem okazało się to, że po drodze zaginęła informacja systemowa, używana automatycznie przez niektóre skrypty.

OpenSSH ma kilka miejsc, w których można „wstrzyknąć” treść dla użytkownika. Dwa najczęściej mylone to:

  • Banner – wiadomość wyświetlana przed logowaniem;
  • motd (np. /etc/motd) – wyświetlana już po udanym logowaniu.

Komunikaty o polityce i monitorowaniu najlepiej umieścić w Banner, odpowiednio konfigurowanym w sshd_config:

Banner /etc/ssh/ssh_banner

Plik banera powinien być maksymalnie prosty tekstowo – bez dynamicznych elementów, bez informacji o wersjach systemowych, bez zmiennych środowiskowych. Chodzi o jasny komunikat prawny/organizacyjny, a nie dodatkowy „kanał boczny” z informacjami o serwerze.

Z kolei /etc/motd oraz mechanizmy typu update-motd mogą dostarczać informacji operacyjnych: wersje krytycznych usług, okna serwisowe, komunikaty dla administratorów. Te treści widzą tylko zalogowani użytkownicy, więc ryzyko ujawnienia zbyt wielu szczegółów potencjalnemu atakującemu jest niższe.

Dobry nawyk to utrzymywanie jednego szablonu banera dla całej organizacji (zarządzanego np. przez Ansible/puppet), a specyficzne komunikaty systemowe zostawiać w motd lub w dedykowanych skryptach powłoki.

KeepAlive, ClientAlive i „martwe sesje” po VPN

W środowiskach z intensywnym użyciem VPN admini często narzekają na „wiszące” sesje SSH, których użytkownik dawno nie widzi, bo tunel padł lub laptop się uśpił. Z czasem taki serwer pełen jest porzuconych powłok, które teoretycznie wciąż mają otwarte pliki czy blokady.

Na poziomie SSH rozwiązaniem są parametry podtrzymywania i weryfikacji sesji:

TCPKeepAlive no
ClientAliveInterval 60
ClientAliveCountMax 3

Wyłączenie TCPKeepAlive przekłada logikę „czy z drugiej strony coś jeszcze żyje” z warstwy TCP na własny mechanizm serwera SSH. Dzięki ClientAliveInterval i ClientAliveCountMax serwer co określony czas wysyła zapytanie, a po braku odpowiedzi przez kilka cykli zrywa sesję.

Dla administratora oznacza to, że „martwe” sesje po zerwanym VPN znikną po kilku minutach, a nie będą wisiały godzinami. Dla użytkownika – że po uśpieniu laptopa lub wyłączeniu sieci sesja się sensownie zamknie, zamiast zostawiać otwartą powłokę w tle.

Na bastionach z dużą liczbą jednoczesnych użytkowników warto te wartości wyostrzyć (np. 30 sekund i 2 próby), natomiast na serwerach, gdzie działają długie zadania administracyjne z niestabilnych łączy, można je nieco poluzować, by uniknąć zbyt agresywnego zrywania sesji.

„Stare” protokoły i opcje kompatybilności – odwagą jest wyłączać

W jednym z projektów po podniesieniu wersji OpenSSH zespół włączył aktualne, rekomendowane ustawienia szyfrów, ale zostawił kilka opcji „dla kompatybilności z legacy”. Dwa lata później, podczas incydentu, okazało się, że napastnik skorzystał właśnie z tych „tymczasowo” zachowanych słabości.

Szczególnie zdradliwe są:

  • stare, słabe algorytmy wymiany kluczy (np. diffie-hellman-group1-sha1);
  • klucze hosta w formacie DSA (ssh-dss);
  • domyślnie dopuszczone „egzotyczne” algorytmy podpisu, włączane z myślą o jednym starym kliencie.

Aktualne wersje OpenSSH same wyłączają najgorsze algorytmy, ale w wielu środowiskach wciąż wiszą ręcznie dodane linie sprzed lat. Czysty stan to m.in. brak plików ssh_host_dsa_key* oraz brak ręcznego dopisywania słabych Kex/MAC w sshd_config pod pretekstem „bo jedno stare urządzenie inaczej nie działa”.

Jeśli istnieje realna potrzeba kompatybilności (np. krytyczny system magazynowy z wbudowanym klientem SSH sprzed dekady), lepiej wydzielić osobny, odseparowany serwer pośredniczący z mocno opisanymi wyjątkami, niż rozluźniać politykę całej infrastruktury. W praktyce oznacza to drugi sshd lub nawet osobną wirtualkę, która pełni rolę „strefy buforowej” dla legacy.

Dobrym zwyczajem jest okresowy przegląd konfiguracji pod kątem linii typu Ciphers, MACs, KexAlgorithms i HostKeyAlgorithms. Jeśli kiedyś zostały dopisane „na szybko”, dziś prawdopodobnie są zbędne – klienci również się aktualizują.

Centralne zarządzanie konfiguracją SSH i jak nie zrobić z tego chaosu

W organizacji, gdzie serwerów „jakoś samo” urosło z dziesięciu do kilkuset, konfiguracja SSH potrafi być unikalna niemal na każdym hoście. Przy pierwszym poważnym audycie wychodzi na wierzch, że nikt już nie wie, które różnice są celowe, a które przypadkowe.

Automatyzacja konfiguracji (Ansible, Puppet, Chef, Salt) rozwiązuje ten problem tylko częściowo – jeśli w repozytorium ląduje przypadkowa mieszanka starych i nowych ustawień, chaos się tylko utrwala. Zdrowe podejście to rozdzielenie konfiguracji na „warstwy”: wspólny trzon i precyzyjnie opisane wyjątki.

Przykładowy podział może wyglądać tak:

  • sshd_config.d/base.conf – minimalny, twardy zestaw: protokół, HostKey, zakaz haseł, nowoczesne Kex/Ciphers/MAC, LogLevel;
  • sshd_config.d/bastion.conf – dodatkowe reguły dla bastionów (Match dla użytkowników, zasady tunelowania);
  • sshd_config.d/legacy.conf – wyjątki dla pojedynczych hostów, zarządzane z osobnego katalogu inwentarza.

Środowiska, które włączają Include /etc/ssh/sshd_config.d/*.conf, zyskują naturalny punkt do porządkowania: nowe rekomendacje trafiają do base.conf, natomiast każda nietypowa potrzeba musi być nazwana i opisana w osobnym pliku. Łatwo wtedy wyszukać po repozytorium wszystkie serwery korzystające z legacy.conf i stopniowo ten dług redukować.

Do tego dochodzi spójne wersjonowanie. Zamiast „przepisaliśmy sshd_config na pięciu serwerach”, jest konkretna zmiana Git z opisem: „podniesienie minimalnej długości kluczy, wyłączenie SHA1, włączenie FIDO2”. Jeżeli po tygodniu coś przestanie działać w egzotycznym skrypcie, zawsze można wrócić do konkretnej wersji i przeanalizować różnicę.

Testowanie zmian i unikanie samodzielnego „odcięcia się”

Chyba każdy administrator ma swoją historię o tym, jak jedną linijką w sshd_config skutecznie zamknął sobie drogę na serwer produkcyjny. Najczęściej dzieje się to przy zmianach w uwierzytelnianiu lub przy zbyt agresywnych regułach Match.

Bezpieczny schemat wprowadzania zmian zawiera kilka prostych kroków:

  • edycja konfiguracji bez zamykania istniejącej sesji SSH;
  • weryfikacja składni: sshd -t -f /etc/ssh/sshd_config (lub odpowiedni plik instancji);
  • restart lub reload usług z otwartą drugą sesją testową z innego konta/IP;
  • tymczasowe pozostawienie starej instancji lub alternatywnego portu na czas testów.

Na maszynach krytycznych dobrze sprawdza się podejście z dwoma instancjami: najpierw nowe ustawienia trafiają na port administracyjny (dostępny tylko z sieci IT), a dopiero po kilku dniach – na port używany przez resztę organizacji. W razie problemów wystarczy przełączyć ruch z powrotem, nie dotykając konfiguracji, która już działa.

Dodatkowe zabezpieczenie daje at lub prosty rollback timer. Po wdrożeniu ryzykownej zmiany można zlecić za 10–15 minut automatyczne przywrócenie starego pliku sshd_config i restart usługi. Jeśli przez ten czas wszystko będzie działać poprawnie, zadanie się anuluje. Jeśli jednak administrator naprawdę odetnie sobie dostęp, mechanizm sam przywróci poprzedni stan.

Takie prozaiczne rytuały – test składni, druga sesja, delayed rollback – w praktyce robią różnicę między „u nas się nie psuje” a „każda zmiana to stres i ryzyko nocnej wycieczki do serwerowni”.

Kluczowe Wnioski

  • Serwer, który „działa od lat”, często ma przestarzałą konfigurację OpenSSH – dopóki nie przyjdzie audyt lub nowe wymagania compliance, nikt nie zauważa słabych algorytmów, logowania na roota czy haseł.
  • Stare szablony sshd_config oraz domyślne ustawienia sprzed lat utrzymują przy życiu DSA, SHA1, słabe MAC i KEX, a także niepotrzebne funkcje (X11, niekontrolowany forwarding), co realnie zwiększa powierzchnię ataku.
  • Pozostawienie historycznej konfiguracji powoduje jednocześnie gorsze bezpieczeństwo i problemy operacyjne: nowi klienci odrzucają słabe algorytmy, audyty wytykają niezgodność z politykami, a aktualizacje OpenSSH potrafią zaskoczyć zmianą domyślnych zachowań.
  • Typowe „czerwone flagi” to m.in. PermitRootLogin yes, PasswordAuthentication yes, włączone ChallengeResponseAuthentication, X11Forwarding bez faktycznej potrzeby oraz brak ograniczeń użytkowników (AllowUsers) i kontroli nad długością sesji.
  • Lepszym podejściem niż zostawianie wszystkiego „jak jest” jest świadomy minimalizm: ograniczenie listy szyfrów, MAC i KEX do bezpiecznego zestawu oraz wyłączenie zbędnych funkcji, co upraszcza audyty i zmniejsza ryzyko błędów.
  • Wiele ulepszeń da się wprowadzić od ręki – z planem i możliwością szybkiego wycofania – tak aby podnieść poziom bezpieczeństwa SSH bez ryzyka „odcięcia się” od produkcji w środku dnia.
  • Im dłużej odkłada się porządki w sshd_config, tym bardziej rośnie dystans do aktualnych dobrych praktyk i tym większa szansa, że zamiast spokojnej korekty będzie potrzebna kosztowna, skokowa modernizacja.