Single Packet Authorization za pomocą fwknop

W ostatnim poście opisywałem grupę SSHPsychos, która przeprowadza ataki brute force na usługę SSH. Grupa w dalszym ciągu aktywnie działa, co potwierdzają logi z moich honeypotów 😉

W nawiązaniu do tego posta opiszę krok po kroku jak wdrożyć i skonfigurować jedną z rekomendacji którą zaproponowałem na końcu wpisu czyli Port Knocking, a konkretniej rozszerzenie tej techniki o nazwie Single Packet Authorization (w skrócie SPA).

Czym jest Port Knocking?

Port Knocking jest techniką pozwalającą zdalnie otwierać porty na firewallu. Realizowane jest to za pomocą sekwencji pakietów wysyłanych na określone, zamknięte porty.

Dla lepszego zobrazowania sytuacji posłużę się przykładem: serwer na świat zewnętrzny ma wystawiony jeden port – 80. Przynajmniej tak widzą to skanery jak np. nmap.

“Pod spodem” działa jeszcze usługa SSH na domyślnym porcie 22, który nasłuchuje tylko wtedy, gdy wcześniej dostanie dwa pakiety na odpowiednio na porty 12345 i 54321. W innym wypadku usługa jest zablokowana poprzez firewall i nie możemy się do niej połączyć.

Niestety dużą wadą tego rozwiązania jest możliwość podsłuchania sekwencji wysyłanych na wskazane porty – napastnik może wysłać taką samą do serwera, a w konsekwencji jego IP otrzyma dostęp do zablokowanego portu.

Czym jest SPA?

SPA, w naszym scenariuszu, funkcjonalnie oferuje to samo co Port Knocking – odblokowuje zamknięte porty. Istotne różnice in plus następują w sposobie uwierzytelniania klienta. Odbywa się to za pomocą wysłania zaszyfrowanego pakietu przez inicjującego połączenie. W przypadku pomyślnego odszyfrowania wiadomości przez serwer następuje otwarcie portu dla danego klienta.

Instalacja fwknop na serwerze

Po niezbędnej teorii czas na wdrożenie. Wszystkie przedstawione poniżej kroki tyczą się wszystkich dystrybucji pochodnych od Debiana – w poście wykorzystuję Ubuntu Server 14.04.

Wydajemy polecenie

sudo apt-get update
sudo apt-get install -y fwknop-server

Instalacja fwknop na kliencie

Podobnie jak na serwerze:

sudo apt-get update
sudo apt-get install -y fwknop-client

Konfiguracja kluczy GPG

Fwknop używa GPG jako systemu kryptografii asymetrycznej celem szyfrowania i odszyfrowania pakietów otwierających porty. Z racji tego musimy wygenerować po parze kluczy (publiczny i prywatny) dla serwera i klienta.

Na obydwu maszynach wykonujemy polecenie:

gpg --gen-key

Pojawi nam się coś takiego:

gpg (GnuPG) 1.4.16; Copyright (C) 2013 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

gpg: directory `/home/kamil/.gnupg' created
gpg: new configuration file `/home/kamil/.gnupg/gpg.conf' created
gpg: WARNING: options in `/home/kamil/.gnupg/gpg.conf' are not yet active during this run
gpg: keyring `/home/kamil/.gnupg/secring.gpg' created
gpg: keyring `/home/kamil/.gnupg/pubring.gpg' created
Please select what kind of key you want:
   (1) RSA and RSA (default)
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)

Wybieramy opcję nr 1:

RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048)

Wybieramy opcję domyślną – 2048 bitów.

Uwaga! Zalecaną długością klucza dla fwknop jest 2048 bitów. Jest to związane z ograniczeniami wielkości pojedyńczego pakietu IP. Istnieje możliwość korzystania z klucza 4096 bitowego, lecz w takim wypadku drugi z kluczy (klienta lub serwera) musi mieć 2048 bitów.

Dalsze opcje (ważność klucza, jego właściwości oraz hasło) konfigurujemy według własnego uznania. Uczulam na długie i skomplikowane hasło klucza.

Może się zdarzyć, że zostaniemy uraczeni komunikatem:

Not enough random bytes available.  Please do some other work to give
the OS a chance to collect more entropy! (Need 267 more bytes)

System w trakcie swojego działania nie zgromadził wystarczającej puli losowych danych, aby wygenerować odpowiednio silne klucze. Niestety musimy przerwać proces i zainstalować paczkę rng-tools odpowiadającą za obsługę sprzętowego generatora liczb pseudolosowych (TRNG).

sudo apt-get install rng-tools

W wielu procesorach TRNG jest już wbudowany, my natomiast wykorzystamy /dev/urandom jako uniwersalne źródło entropii.

sudo nano /etc/default/rng-tools

Dodajemy linijkę HRNGDEVICE=/dev/urandom pod komentarzami:

# Configuration for the rng-tools initscript
# $Id: rng-tools.default,v 1.1.2.5 2008-06-10 19:51:37 hmh Exp $

# This is a POSIX shell fragment

# Set to the input source for random data, leave undefined
# for the initscript to attempt auto-detection.  Set to /dev/null
# for the viapadlock and tpm drivers.
#HRNGDEVICE=/dev/hwrng
#HRNGDEVICE=/dev/null
HRNGDEVICE=/dev/urandom

# Additional options to send to rngd. See the rngd(8) manpage for
# more information.  Do not specify -r/--rng-device here, use
# HRNGDEVICE for that instead.
#RNGDOPTIONS="--hrng=intelfwh --fill-watermark=90% --feed-interval=1"
#RNGDOPTIONS="--hrng=viakernel --fill-watermark=90% --feed-interval=1"
#RNGDOPTIONS="--hrng=viapadlock --fill-watermark=90% --feed-interval=1"
#RNGDOPTIONS="--hrng=tpm --fill-watermark=90% --feed-interval=1"

Uruchamiamy usługę rng-tools:

sudo service rng-tools start

I powtarzamy procedurę generacji klucza od początku:

gpg --gen-key

Po pomyślnej generacji kluczy zostaną wyświetlone ich właściwości.

Właściwości klucza klienta:

gpg: key 1285A54C marked as ultimately trusted
public and secret key created and signed.

gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0  valid:   2  signed:   1  trust: 0-, 0q, 0n, 0m, 0f, 2u
gpg: depth: 1  valid:   1  signed:   0  trust: 1-, 0q, 0n, 0m, 0f, 0u
gpg: next trustdb check due at 2017-04-16
pub   2048R/1285A54C 2015-05-07
      Key fingerprint = 027D 66AB 54BB 4998 1A63  9A70 ED05 D988 1285 A54C
uid                  SPA Client (FWKNOP) <spa_client@frankowicz.me>
sub   2048R/5FCDF18D 2015-05-07

Właściwości klucza serwera:

gpg: key AE36A5CA marked as ultimately trusted
public and secret key created and signed.

gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
pub   2048R/AE36A5CA 2015-05-07
      Key fingerprint = 6319 8C45 3BEA 15A6 6ED8  875B FB16 A3BB AE36 A5CA
uid                  SPA Server (FWKNOP) <fwknop@frankowicz.me>
sub   2048R/5F76F089 2015-05-07

Gdzieś na boku zapisujemy sobie publiczne ID kluczy serwera i klienta (przydadzą się do konfiguracji fwknop):

Klient: 1285A54C
Serwer: AE36A5CA

Ostatnim krokiem w tym punkcie jest eksport kluczy w formacie ASCII do pliku.

Eksport klucza klienta:

gpg -a --export 1285A54C > spa_client.asc

Eksport klucza serwera:

gpg -a --export AE36A5CA > spa_server.asc

Wymiana kluczy GPG pomiędzy klientem i serwerem

Fwknop do poprawnego działania wymaga, aby każdy komputer posiadał klucz publiczny serwera oraz klucz klienta (może być współdzielony pomiędzy wieloma klientami).

W związku z tym musimy w jakiś sposób wymienić klucze pomiędzy komputerami. Prosto, bezpiecznie i wygodnie możemy zrobić to za pomocą SCP (o ile na komputerze docelowym jest uruchomiony serwer SSH).

Na kliencie wykonujemy (klucz klienta -> serwer):

scp spa_client.asc [nazwa_uzytkownika]@[ip_serwera]:[sciezka_do_folderu]

Na serwerze wykonujemy (klucz serwera -> klient):

scp spa_server.asc [nazwa_uzytkownika]@[ip_klienta]:[sciezka_do_folderu]

Import i podpisanie kluczy

W tym momencie mamy jedynie pliki kluczy, które nic dla fwknop nie znaczą. Musimy je zaimportować do bazy kluczy systemu operacyjnego i podpisać, aby móc uznać klucz za zaufany.

Import na kliencie (klucz serwera -> baza kluczy klienta):

gpg --import spa_server.asc

Wynik importu (klient):

gpg: key AE36A5CA: public key "SPA Server (FWKNOP) <fwknop@frankowicz.me>" imported
gpg: Total number processed: 1
gpg:               imported: 1  (RSA: 1)

Import na serwerze (klucz klienta -> baza kluczy na serwerze):

gpg --import spa_client.asc

Wynik importu (serwer):

gpg: key 1285A54C: public key "SPA Client (FWKNOP) <spa_client@frankowicz.me>" imported
gpg: Total number processed: 1
gpg:               imported: 1  (RSA: 1)

Podpisujemy klucz serwera na kliencie:

gpg --edit-key AE36A5CA
gpg (GnuPG) 1.4.16; Copyright (C) 2013 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.


pub  2048R/AE36A5CA  created: 2015-05-07  expires: never       usage: SC
                     trust: unknown       validity: unknown
sub  2048R/5F76F089  created: 2015-05-07  expires: never       usage: E
[ unknown] (1). SPA Server (FWKNOP) <fwknop@frankowicz.me>

gpg> sign

pub  2048R/AE36A5CA  created: 2015-05-07  expires: never       usage: SC
                     trust: unknown       validity: unknown
 Primary key fingerprint: 6319 8C45 3BEA 15A6 6ED8  875B FB16 A3BB AE36 A5CA

     SPA Server (FWKNOP) <fwknop@frankowicz.me>

Are you sure that you want to sign this key with your
key "SPA Client (FWKNOP) <spa_client@frankowicz.me>" (1285A54C)

Really sign? (y/N) y

gpg> save

Podpisujemy klucz klienta na serwerze:

gpg --edit-key 1285A54C
gpg (GnuPG) 1.4.16; Copyright (C) 2013 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.


pub  2048R/1285A54C  created: 2015-05-07  expires: never       usage: SC
                     trust: unknown       validity: unknown
sub  2048R/5FCDF18D  created: 2015-05-07  expires: never       usage: E
[ unknown] (1). SPA Client (FWKNOP) <spa_client@frankowicz.me>

gpg> sign

pub  2048R/1285A54C  created: 2015-05-07  expires: never       usage: SC
                     trust: unknown       validity: unknown
 Primary key fingerprint: 027D 66AB 54BB 4998 1A63  9A70 ED05 D988 1285 A54C

     SPA Client (FWKNOP) <spa_client@frankowicz.me>

Are you sure that you want to sign this key with your
key "SPA Server (FWKNOP) <fwknop@frankowicz.me>" (AE36A5CA)

Really sign? (y/N) y

gpg> save

Konfiguracja fwknop na serwerze

Cała konfiguracja niezbędna do działania (porty, ID kluczy, itp.) fwknop znajduje się w pliku /etc/fwknop/access.conf.

Otwieramy plik z podniesionymi uprawnieniami:

sudo nano /etc/fwknop/access.conf

Komentarze do opcji dostępnych w pliku:

#### fwknopd access.conf stanzas ###

SOURCE              ANY			# IP z którego może połączyć się klient
OPEN_PORTS: tcp/22			# Port/y "ukrywane" przez fwknop (w tym wypadku SSH)
KEY_BASE64          __CHANGEME__	# Wygenerowane wartości przez klienta - patrz następny krok
HMAC_KEY_BASE64     __CHANGEME__	# J/W

# If you want to use GnuPG keys then define the following variables
#
GPG_HOME_DIR           /homedir/path/.gnupg 	# Ścieżka gdzie przechowywana jest baza kluczy GPG
GPG_DECRYPT_ID         ABCD1234			# Publiczne ID klucza serwera
GPG_DECRYPT_PW         __CHANGEME__		# Hasło klucza serwera (GPG passphrase)

# If you want to require GPG signatures:
GPG_REQUIRE_SIG                    Y		# Weryfikacja sygnatur klienta
GPG_IGNORE_SIG_VERIFY_ERROR        N		# Ignorowanie błędów weryfikacji sygnatur klienta
GPG_REMOTE_ID                      1234ABCD	# Publiczne ID klucza klienta

Aby wygenerować KEY_BASE64 oraz HMAC_KEY_BASE64 na kliencie wydajemy polecenie:

fwknop -A [tcp,udp]/[numer_chronionego_portu] -D [ip_servera_spa] --key-gen --use-hmac --save-rc-stanza

W moim wypadku wygląda to tak:

fwknop -A tcp/22 -D 192.168.41.131 --key-gen --use-hmac --save-rc-stanza
[+] Wrote Rijndael and HMAC keys to rc file: /home/kamil/.fwknoprc

Wyświetlamy plik ~/.fwknoprc:

cat ~/.fwknoprc

I kopiujemy z niego wartości KEY_BASE64 oraz HMAC_KEY_BASE64 do pliku /etc/fwknop/access.conf:

[192.168.41.131]
KEY_BASE64                  ncvBpexEthWqWPreXmHkH8p35eLeOoXPNjVCvxEfzYM=
HMAC_KEY_BASE64             fhqY7yxMuaiGc0d2yLOZJC6beUi1rHXbs+JBzrUzceTL2kyfzGosBxam4OMKEUQlgVvHDhCxIMGqR0zAZCF9qA==
ACCESS                      tcp/22
SPA_SERVER                  192.168.41.131
USE_HMAC                    Y

Gotowy plik /etc/fwknop/access.conf w moim wypadku wygląda następująco:

#### fwknopd access.conf stanzas ###

SOURCE              ANY
OPEN_PORTS: tcp/22	
KEY_BASE64          ncvBpexEthWqWPreXmHkH8p35eLeOoXPNjVCvxEfzYM=
HMAC_KEY_BASE64     fhqY7yxMuaiGc0d2yLOZJC6beUi1rHXbs+JBzrUzceTL2kyfzGosBxam4OMKEUQlgVvHDhCxIMGqR0zAZCF9qA==

# If you want to use GnuPG keys then define the following variables
#
GPG_HOME_DIR           /home/kamil/.gnupg
GPG_DECRYPT_ID         AE36A5CA
GPG_DECRYPT_PW

# If you want to require GPG signatures:
GPG_REQUIRE_SIG                    Y
GPG_IGNORE_SIG_VERIFY_ERROR        N
GPG_REMOTE_ID                      1285A54C

Konfiguracja IPTables

Ostatnią rzeczą która pozostała nam do konfiguracji na serwerze, jest firewall. Musimy go skonfigurować w taki sposób, aby odrzucał połączenia na chronionym porcie i przepuszczał te, które zostały ustanowione (kiedy otworzyliśmy port poprzez fwknop – domyślnie otwarty 30 sekund).

Akceptacja nawiązanych już połączeń (dla SSH):

sudo iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

Odrzucanie wszystkich innych połączeń na tym porcie:

sudo iptables -A INPUT -p tcp --dport 22 -j DROP

Jeżeli używasz UFW to polecenia do akceptacji nawiązanych już połączeń i odrzucania wszystkich innych połączeń na tym porcie będą wyglądały następująco:

sudo iptables -A ufw-user-input -p tcp --dport 22 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A ufw-user-input -p tcp --dport 22 -j DROP

Restartujemy usługę serwera fwknop:

sudo service fwknop-server restart

To wszystko po stronie serwera! 🙂

Łączenie się do serwera

W tym momencie, jeżeli wszystko dobrze skonfigurowaliśmy, serwer nie odpowiada na porcie 22 i nie możemy się połączyć poprzez SSH:

ssh kamil@192.168.41.131
ssh: connect to host 192.168.41.131 port 22: Connection timed out

Aby otworzyć port 22 na firewallu musimy wygenerować i wysłać zaszyfrowany pakiet za pomocą fwknop:

fwknop -n [ip_serwera] --verbose -a [ip_klienta]

Przełącznik –verbose służy do wyświetlenia informacji o wygenerowanym pakiecie i jego właściwościach

Mój output z właściwościami połączenia:

fwknop -n 192.168.41.131 --verbose -a 192.168.41.135
SPA Field Values:
=================
   Random Value: 2034471172940899
       Username: kamil
      Timestamp: 1430996763
    FKO Version: 2.0.1
   Message Type: 1 (Access msg)
 Message String: 192.168.41.135,tcp/22
     Nat Access: 
    Server Auth: 
 Client Timeout: 0
    Digest Type: 3 (SHA256)
      HMAC Type: 3 (SHA256)
Encryption Type: 1 (Rijndael)
Encryption Mode: 2 (CBC)
   Encoded Data: 2034471172940899:a2FtaWw:1430996763:2.0.1:1:MTkyLjE2OC40MS4xMzUsdGNwLzIy
SPA Data Digest: +D22fQUXYa5aFWu49l/EI2dbBdwtDByuB4kvZaj+drY
           HMAC: yB2G2hf19PJXKlrtPsjPjrKyEyuVjW2OU1RLj+gRTxo
 Final SPA Data: 8wmgenmGHRp6vxK3+7C0FT3i9YQP76Lz9z4zEVA1jtI14EcyFxCzN2xzvVQXCY+oywSsLPLyX4MBJMKpAUQA/u+VryLX1W0O0g+bk7aDXx2TjFbrUP65AqBr+4iCPLmSYzxu3xI3JO8Bha9mrIY04yPH6A1XYU428kAEjm4ixTcu/84nXgaWUnyB2G2hf19PJXKlrtPsjPjrKyEyuVjW2OU1RLj+gRTxo

Generating SPA packet:
            protocol: udp
         source port: 
    destination port: 62201
             IP/host: 192.168.41.131
send_spa_packet: bytes sent: 225

Jeżeli jesteśmy za NATem i chcemy sie dostać do serwera przez Internet, przełącznik -R umożliwi fwknop pobranie naszego publicznego IP jako adresu klienta.

Po wysłaniu pakietu mamy 30 sekund na połączenie się poprzez SSH, w przeciwnym wypadku port zostaje zamknięty i musimy ponowić procedurę wysyłania zaszyfrowanego pakietu.

To wszystko! Procedura może wydawać się skomplikowana, jednak po kilku wdrożeniach na serwerach czas konfiguracji znacząco się skraca (dla zabieganych również można rozważyć tylko jeden wspólny klucz dla wszystkich klientów).

Jeżeli masz jakieś pytania, uwagi lub sugestie zapraszam do podzielenia się w komentarzach 🙂