W pracy od czasu do czasu potrzebujemy dostać się do serwera, który siedzi za srogimi firewallami i innymi VPN-ami. Nierzadko gdzieś tam po drodze trzeba wbić kod z generatora kodów jednorazowych w postaci aplikacji na smartfonie.
Pół biedy jeżeli robimy to raz na tydzień. Gorzej, jeżeli musimy się w ten sposób "męczyć" kilka razy dziennie. Zawsze jest ryzyko, że się palec omsknie przy wpisywaniu kodu, albo że akurat bateria w smartfonie padła i trzeba go najpierw podłączyć do prundu i tak dalej.
Jako osobnik kumaty leniwy lubię sobie automatyzować różne powtarzalne czynności, żeby mi nie zabierały za dużo czasu i ambarasu. Niedawno nauczyłem się jak zautomatyzować proces 2FA, dzielę się więc tą wiedzą z Czytelnikami - a nuż komuś się przyda?
Założenie jest takie, że siedzimy na Linuksie. Może też byc WSL2 (sprawdziłem, działa). Potrzebujemy też jednorazowego kodu QR do początkowego skonfigurowania aplikacji 2FA. Kod ten - w postaci pliku obrazka lub zwykłej naklejki - na ogół dostajemy od klienta, do którego się będziemy podłączać. Tutaj zakładam, że mamy plik obrazka. Jeżeli mamy tylko naklejkę, trzeba jej pstryknąć fotkę i zapisać w postaci pliku png.
Zaczynamy od zainstalowania niezbędnych programów: zbar-tools, oathtool i expect:
sudo apt install -y zbar-tools oathtool expect
Następnie uruchamiamy:
zbarimg /home/zbyszek/Downloads/kod-qr.png
(zastępujemy rzecz jasna powyższą ścieżkę do pliku png tym, co mamy u siebie)
W wyniku dostaniemy:
QR-Code:otpauth://totp/XXX@ZZZ?secret=YYY&issuer=ZZZ
XXX i ZZZ można teraz zignorować, natomiast YYY - długi, około 30-znakowy kod złożony z cyfr i wielkich liter - zapisujemy go sobie gdzieś na boku.
Teraz sprawdzamy, czy kod działa:
oathtool --totp --base32 YYY
(zamiast YYY wstawiamy nasz długi kod z poprzedniego kroku)
W odpowiedzi powinniśmy dostać jednorazowy kod sześciocyfrowy potrzebny do zalogowania się do systemu.
W zasadzie można by tutaj się zatrzymać, bo właśnie wyeliminowaliśmy z łańcucha zdarzeń konieczność sięgnięcia po smartfona w celu przepisania zeń kodu - zamiast tego możemy go teraz przepisać (albo nawet skopiować i wkleić) bezpośrednio z ekranu - ale pójdźmy dalej! Skoro mamy już ten kod (ważny na ogół przez około 60 sekund), spróbujmy teraz zautomatyzować cały proces logowania od A do Z.
Załóżmy, że oryginalnie nasz proces wygląda tak:
ssh byniek@mojserwer.pl -L 1234:mojserwer.pl:1234 -L 2345:mojserwer.pl:2345
Password:
...wpisujemy hasło, pukamy enter...
Verification code:
...sięgamy po smartfona i generujemy sześciocyfrowy kod 2FA, wpisujemy, pukamy enter i widzimy upragnione:
Tunnel started, shell disabled by the system administrator.
Naszym zadaniem będzie teraz wysłać automagicznie hasło i kod jednorazowy do procesu ssh. W tym celu skorzystamy z aplikacji expect
(wnikliwy Czytelnik zauważy, że zainstalowaliśmy ją na samym początku), która potrafi do pewnego stopnia sterować aplikacją konsolową.
Napiszemy sobie teraz prościutki skrypt, któremu nadamy nazwę... dajmy na to zenon.sh
:
#!/usr/bin/expect -f
set PASS "moje-haslo-do-ssh\r"
set CODE [exec oathtool --totp --base32 YYY]
spawn ssh byniek@mojserwer.pl -L 1234:mojserwer.pl:1234 -L 2345:mojserwer.pl:2345
expect "Password:"
send "$PASS"
expect "Verification code:"
send "$CODE\r"
interact
Wydarza się tutaj kilka całkiem interesujących rzeczy:
#!/usr/bin/expect -f
Linuksowcy wiedzą, reszcie tłumaczę: to jest tak zwany shebang, czyli mówimy systemowi jakiego programu ma użyć do wykonania reszty skryptu. Najczęściej można tam znaleźć #!/bin/bash
ale my użyjemy expect
.
set PASS "moje-haslo-do-ssh\r"
W expect zmienne ustawia się poleceniem set - tu ustawiamy zmienną PASS na nasze supertajne hasło, z enterem na końcu.
set CODE [exec oathtool --totp --base32 YYY]
Jeżeli chcemy, żeby zmienna przyjęła wartość wygenerowaną przez proces zewnętrzny, zamykamy wywołanie tego procesu w [exec ... ]
- w tym przypadku do zmiennej CODE
trafi nasz sześciocyfrowy kod jednorazowy. Uwaga: bez entera na końcu (dlatego dodajemy go potem).
spawn ssh byniek@mojserwer.pl -L 1234:mojserwer.pl:1234 -L 2345:mojserwer.pl:2345
Tutaj uruchamiamy (za pomocą spawn
) aplikację ssh
, która za chwilę zapyta nas o login i kod, a potem zestawi tunel.
expect "Password:"
Tu wydarza się największa magia aplikacji expect: będzie ona pilnie śledzić wszystkie teksty wygenerowane przez ssh aż natrafi na "Password:"
send "$PASS"
Za pomocą polecenia send
wysyłamy do ssh
nasze hasło (zapisane w zmiennej PASS
). Przy okazji: zmienne tworzymy bez prefiksu $
, ale używamy już z prefiksem.
expect "Verification code:"
send "$CODE\r"
Identycznie jak poprzednio, czekamy teraz na zapytanie o kod jednorazowy i wysyłamy go.
Skrypt kończymy poleceniem interact
, które oddaje sterowanie do systemu operacyjnego.
Zapisujemy wszystko, jeszcze tylko szybkie chmod +x zenon.sh
i od tej pory możemy uruchomić tunel jednym poleceniem, bez potrzeby sięgania po smartfona.
Oczywiście pamiętajmy, że taka automatyzacja oznacza znaczne zmniejszenie bezpieczeństwa - pomijamy bowiem krok interaktywny, wymagający sięgnięcia po dodatkowe urządzenie z kodem. Jak się nam ktoś włamie do komputera i ukradnie ten skrypt, będzie mógł potem zestawić tunel bez potrzeby generowania kodu. Musimy sami zdecydować czy nam się takie ryzyko opłaca czy nie.
hasła w plain text. Ty się bogów nie boisz?
Nie boję się 😉
Hasło można sobie zamiast tego przechowywać w zmiennej środowiskowej albo w zewnętrznym pliku tekstowym, ale prawda jest taka, że jak ktoś mi się włamie na lokalną maszynę, to jestem w czarnej głębokiej tak czy siak…
Jako hasła możesz użyć w skrypcie funkcji skrótu do pliku na pendrivie (np sha) – będzie długie (jeśłi za długie to można trunkować), “losowe” (wg standardy typowego ludzia [citation needed]) i nie do odtworzenia bez pliku. Zmiana hasła (zgodnie z polityką firmy)? Generujesz hasło do innego pliku (np zdjęcia słodkiego koteczka z Internetu, czy też innej teściowej) z pendriva i poprawiasz ścieżkę w skrypcie.
tl;dr: Wymiana jednego zabezpieczenia sprzętowego (smartfona) na inny (pendrive).
Problem jaki widzę w tym rozwiązaniu jest taki, że tracisz… drugi czynnik i robisz z 2FA 1FA. Masz bowiem wszystko w jednym miejscu, w przypadku przejęcia Twojego konta w systemie (o adminie nie wspominam), atakujący ma komplet danych potrzebnych do zalogowania się.
Jest rozwiązanie w postaci kluczy Yubikey, na które akurat jest promocja https://twitter.com/Zaufana3Strona/status/1330587935859675139 Kody są na kluczu, aktywacja wymaga dotknięcia klucza, więc zdalne ataki odpadają. Dołóż szyfrowanie dysku silnym hasłem i nawet kradzież laptopa (z kluczem) nie da atakującemu dostępu do danych.
Jeśli boli Cię przepisywanie kodu ze smartfona, to przynajmniej wpisuj “z ręki” hasła. Nadal nie będzie to pełne 2FA, bo klawiatura też jest do podsłuchania po włamaniu do systemu, ale zawsze to jakieś zabezpieczenie.
A opis bardzo fajny, dziękuję.
Yubikey są genialne, używam od wielu lat. No a z tym 1FA to masz zupełną rację, zresztą wspominam o tym we wpisie.
A co siedzi w history po takich działaniach, nie ma tam nigdzie naszego hasła?
BTW Ja jestem dosyć zadowolony z Microsoft Autentykatora bo wystarczy na telefonie przyłożyć palec i nie trzeba nic przepisywać.
MS Autentykator jest fajny, to prawda.
Hasło w historii nie siedzi, jeżeli całą procedurę oskryptujemy. Wtedy jedyne co siedzi w historii to nazwa skryptu (tu: zenon.sh).
trzeba się było mi uczyć linuxa…