Kilka dni temu włączyłem na swoim publicznym adresie IP usługę SSH na porcie 22 - rzecz jasna prawie natychmiast zaczęły mi się tam dobijać tłumy losowego chakierstwa i innego piractwa. Ponieważ port ów mam otwarty na swoim NAS-ie (przez ruter dostawcy, na którym zrobiłem mapowanie portu 22 z adresu publicznego na lokalny), za każdym razem kiedy ktoś próbuje się zalogować NAS wysyła mi powiadomienie na e-mail.
Tak naprawdę powiadomienie przychodzi tylko wtedy, gdy ktoś spróbuje się zalogować z danego adresu IP więcej niż raz i zostaje zablokowany.
Póki co liczba tych e-maili nie przytłacza (średnio 3-4 maile na godzinę, pół biedy). Ponadto nie martwi mnie też zbytnio sam fakt otwartego portu, bo użytkownika domyślnego wyłączyłem już dawno, zamiast tego utworzyłem innego z dość losową i trudną do zgadnięcia nazwą więc mogę spać spokojnie.
Zachciało mi się sprawdzić skąd się owi chakierzy próbują do mnie dobijać.
Dla pojedynczego adresu IP sprawa jest prosta: kopiuję, wklejam na whatismyipaddress.com i dostaję pierdylion informacji.
Ale żeby to zrobić en-masse, trzeba już pokombinować...
Skoro za każdym razem dostaję powiadomienie na e-mail (którego nie kasuję tylko wrzucam do archiwum Gmaila), to powinno się dać wyciągnąć te informacje automatycznie, ze skrzynki pocztowej, nieprawdaż? Tym bardziej, że zablokowany adres IP pojawia się w tytule wiadomości co powinno ułatwić sprawę.
W UI Gmaila wygląda to mniej więcej tak:
Jak widać każde powiadomienie przychodzi dwukrotnie (chyba dlatego, że na tym NAS-ie mam dodatkowo software do obsługi kamer, który wykrywa próby włamu do ssh i powiadamia e-mailem niezależnie od samego OS-a). Gdyby tak odfiltrować te wiadomości i wyłuskać z nich same adresy IP... Hmmm.
Instalujemy niezbędne biblioteki...
pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib
... i lecimy z koksem:
import os import pickle import re from googleapiclient.discovery import build from google_auth_oauthlib.flow import InstalledAppFlow from google.auth.transport.requests import Request SCOPES = ['https://mail.google.com/'] def gmail_authenticate(): creds = None if os.path.exists("pygmsearch/token.pickle"): with open("pygmsearch/token.pickle", "rb") as token: creds = pickle.load(token) if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: creds.refresh(Request()) else: flow = InstalledAppFlow.from_client_secrets_file( 'pygmsearch/credentials.json', SCOPES) creds = flow.run_local_server(port=0) with open("pygmsearch/token.pickle", "wb") as token: pickle.dump(creds, token) return build('gmail', 'v1', credentials=creds) def search_messages(service, query): result = service.users().messages().list(userId='me', q=query).execute() messages = [] if 'messages' in result: messages.extend(result['messages']) while 'nextPageToken' in result: page_token = result['nextPageToken'] result = service.users().messages().list( userId='me', q=query, pageToken=page_token).execute() if 'messages' in result: messages.extend(result['messages']) return messages service = gmail_authenticate() messages = search_messages(service, "subject: zablokowany") result = [] for message in messages: msg = service.users().messages().get(userId='me', id=message['id'], format='full').execute() headers = msg['payload']['headers'] for h in headers: if(h['name'] == 'Subject'): if(h['value']).startswith('Adres IP'): v = h['value'] ips = re.findall(r'\[(?:[0-9]{1,3}\.){3}[0-9]{1,3}\]', v) if(ips): for ip in ips: result.append(ip.translate(str.maketrans('','','[]'))) result = list(dict.fromkeys(result)) print(result)
Żeby powyższy kod miał szansę w ogóle wystartować, msimy najpierw włączyć API Gmail na naszym koncie (jak to zrobić? tu jest dość dobrze opisane: https://support.google.com/googleapi/answer/6158841?hl=en) a potem jeszcze jednorazowo (czyli przy pierwszym uruchomieniu skryptu) potwierdzić w przeglądarce, że faktycznie to my próbujemy się zalogować do naszej skrzynki pocztowej. Po pierwszym zalogowaniu się skrypt zapisze token w lokalnym pliku token.pickle i następnym razem połączy się już automatycznie.
Uruchamiamy skrypt, czekamy około 3-4 minut (komunikacja z serwerami Google jest szybka, ale bez przesady...) i dostajemy na wyjściu listę adresów IP, które zostały zablokowane na naszym SSH. To jeszcze nie jest ostateczny wynik (nadal nie wiemy *skąd* dranie się do nas próbują włamać), ale od czegoś trzeba zacząć.
Zanim przejdę do kolejnego etapu, omówię teraz co ciekawsze kawałki powyższego kodu:
Funkcje gmail_authenticate
oraz search_messages
zostały żywcem zerżnięte z tej strony. Jedyne, co możesz chcieć tu zmienić to ścieżka dostępu do pliku token.pickle
- w oryginale nie było tam żadnego przedrostka, ja dodałem bo mi tak pasuje.
service = gmail_authenticate()
- tutaj logujemy się do Gmaila (za pierwszym razem otworzy się tu domyślna przeglądarka www i zapyta o potwierdzenie)
messages = search_messages(service, "subject: zablokowany")
- tu z kolei wyszukujemy wśród e-maili wszystkie, których tytuł zawiera słowo "zablokowany". Można zamiast tego wyszukać na przykład "został zablokowany przez" - w sumie wsioryba, byle wybrać taki tekst, który jednoznacznie odfiltruje szukane przez nas wiadomości.
search_messages
zwraca tak naprawdę wyłącznie listę *identyfikatorów* wiadomości pasujących do szukanego tekstu, więc potem trzeba w pętli przelecieć się po tej liście i pościągać te wiadomości po czym powydłubywać z ich tytułów adresy ip. Tu z pomocą przychodzą wyrażenia regularne - adres IP znajdujemy za pomocą '\[(?:[0-9]{1,3}\.){3}[0-9]{1,3}\]'
czyli najpierw otwarty nawias kwadratowy, potem trzy grupy liczb jedno-, dwu- lub trzycyfrowych z kropką, i na koniec jeszcze jedna taka grupa ale bez kropki zakończona zamykającym nawiasem kwadratowym.
ip.translate(str.maketrans('','','[]'))
usuwa z wyników nawiasy kwadratowe. W Pythonie 2.x było to dużo prostsze, wystarczyło zrobić ip.translate(None, '[]')
ale w trójce namieszali i trzeba dookoła - ale dzięki temu jest ponoć szybciej i bardziej uniwersalnie.
Na koniec usuwamy duplikaty i wyświetlamy wyniki, czyli zwykłą listę adresów IP.
Ale, ale. Co nam po adresach? Przecież postawione zadanie brzmiało: skąd się te ciule łyse do nas dobijają?
W tym celu skorzystamy z biblioteki geoip-lite
czyli darmowej wersji bazy danych geoip
, która potrafi przetłumaczyć adres IP na jego lokalizację.
Kiedyś był to osobny, niezależny projekt, ale potem kupiła ich firma Maxmind i teraz można wykupić dostęp do bazy adresów IP - na szczęście ciągle istnieje wersja darmowa której jedynymi wadami są mniejsza częstotliwość aktualizacji danych (około raz na dwa tygodnie) oraz dokładność samej lokalizacji. Dla naszych potrzeb - w zupełności wystarczy.
Zaczynamy od zainstalowania odpowiedniej biblioteki:
pip install maxminddb-geolite2
A potem już samo gęste:
from geolite2 import geolite2 adresy_ip = ['31.184.198.71', '61.177.173.42', '61.177.173.37', '61.177.172.19', '61.177.172.114', '61.177.172.108', '61.177.172.104', '61.177.173.43', '61.177.173.56', '183.100.29.185', '125.17.153.207', '61.177.172.61', '61.177.172.90', '61.177.172.76', '61.177.173.54', '61.177.172.98', '61.177.173.52', '61.177.173.46', '61.177.172.160', '36.110.228.254', '61.177.173.55', '61.177.172.124', '218.92.0.221', '61.177.172.87', '61.177.173.61', '67.253.48.250', '171.25.193.235', '162.247.74.74', '194.180.48.55', '18.116.202.246', '20.239.196.60', '81.70.142.138', '188.164.167.192', '49.51.19.119', '36.112.171.51', '171.225.184.81', '141.98.11.57', '172.247.194.147', '180.75.5.30', '179.43.140.246', '144.126.217.217', '221.234.230.12', '219.128.12.190', '45.133.38.41', '203.154.158.157', '138.19.142.37', '61.177.173.53', '61.177.173.39', '185.238.36.24', '45.94.142.14', '165.22.67.75', '112.85.42.88', '164.92.207.214', '64.225.103.120', '112.85.42.89', '159.223.23.82', '112.85.42.71', '69.49.229.187', '222.186.30.112', '112.85.42.87', '211.36.141.195', '112.85.42.124', '61.177.172.89', '112.85.42.227', '112.85.42.73', '122.194.229.38', '112.85.42.74', '122.194.229.37', '112.85.42.15', '61.177.172.154', '157.230.120.129', '172.104.144.235', '122.194.229.54', '112.85.42.128', '222.186.42.13', '222.186.30.76', '221.131.165.50', '185.246.130.20', '221.131.165.65', '222.186.42.7', '221.181.185.151', '221.131.165.33', '96.9.67.48', '221.131.165.75', '58.222.83.94', '221.131.165.56', '221.181.185.159', '222.186.180.130', '222.187.232.39', '221.181.185.94', '222.187.254.41', '216.235.12.116', '114.240.224.140', '222.187.238.58', '221.181.185.111', '134.209.83.158', '222.186.42.137', '188.166.60.8', '24.221.224.89', '188.166.24.204', '74.81.30.208', '83.252.70.228', '221.131.165.62', '222.179.101.18', '89.106.23.3', '77.237.73.26', '24.54.148.227', '204.191.196.151', '179.213.34.231', '208.89.176.104', '136.144.41.139', '141.98.10.246', '52.188.213.193', '209.141.42.136', '2.183.55.15', '164.68.105.148', '45.141.84.126', '119.45.167.35', '205.185.122.230', '209.141.41.46', '205.185.125.72', '101.42.107.102', '156.205.108.157', '167.71.10.210', '121.169.34.119', '104.167.223.21', '20.206.109.196', '198.98.59.119', '209.141.44.236', '167.71.2.44', '167.71.12.34', '117.248.249.70', '222.187.237.11', '45.61.186.123', '20.102.80.72', '2.57.122.192', '95.217.33.238', '198.98.50.237', '164.90.203.66', '101.34.244.235', '60.170.247.162', '20.104.53.82', '157.90.104.189', '219.147.221.123', '209.141.51.224', '67.83.0.255', '20.48.236.70', '209.141.37.136', '123.57.37.19'] reader = geolite2.reader() for ip in adresy_ip: loc = reader.get(ip) if(loc): print(loc['country']['names']['en']) else: print("!", ip, "!")
Żeby niepotrzebnie nie obciążać serwerów Ggmaila (a także nie czekać za każdym razem po 3-4 minuty na wyniki), listę adresów IP skopiowałem z wyników poprzedniego skryptu. Dodatkowy warunek if(loc)
jest niezbędny ponieważ nie wszystkie adresy IP znajdują się w bazie geoip i bez tego skrypt beknie.
Na wyjściu dostajemy:
Russia China China China China China China China China Republic of Korea India China China China China China China China China China China China China China China United States Sweden United States Germany United States United States Netherlands Russia Canada China Vietnam ! 141.98.11.57 ! United States Malaysia Switzerland United States China China ! 45.133.38.41 ! Thailand Hong Kong China China France ! 45.94.142.14 ! United States China United States United States China United States China United States China China Republic of Korea China China China China China China China China China United States Germany China China China China China Sweden China China China China Cambodia China China China China China China China China Canada China China China United States China Netherlands United States Netherlands United States Sweden China China Turkey Iran United States Canada Brazil United States United States ! 141.98.10.246 ! United States United States Iran United States ! 45.141.84.126 ! China United States United States United States China Egypt United States Republic of Korea Mali United States United States United States United States United States India China United States United States ! 2.57.122.192 ! Finland United States United States China China United States United States China United States United States United States United States China
Jak widać przeważają chakieży z Chin i (o dziwo) Ameryki. Sprawdźmy konkretne liczby:
from geolite2 import geolite2 from collections import Counter from pprint import pprint adresy_ip = ['31.184.198.71', '61.177.173.42', '61.177.173.37', '61.177.172.19', '61.177.172.114', '61.177.172.108', '61.177.172.104', '61.177.173.43', '61.177.173.56', '183.100.29.185', '125.17.153.207', '61.177.172.61', '61.177.172.90', '61.177.172.76', '61.177.173.54', '61.177.172.98', '61.177.173.52', '61.177.173.46', '61.177.172.160', '36.110.228.254', '61.177.173.55', '61.177.172.124', '218.92.0.221', '61.177.172.87', '61.177.173.61', '67.253.48.250', '171.25.193.235', '162.247.74.74', '194.180.48.55', '18.116.202.246', '20.239.196.60', '81.70.142.138', '188.164.167.192', '49.51.19.119', '36.112.171.51', '171.225.184.81', '141.98.11.57', '172.247.194.147', '180.75.5.30', '179.43.140.246', '144.126.217.217', '221.234.230.12', '219.128.12.190', '45.133.38.41', '203.154.158.157', '138.19.142.37', '61.177.173.53', '61.177.173.39', '185.238.36.24', '45.94.142.14', '165.22.67.75', '112.85.42.88', '164.92.207.214', '64.225.103.120', '112.85.42.89', '159.223.23.82', '112.85.42.71', '69.49.229.187', '222.186.30.112', '112.85.42.87', '211.36.141.195', '112.85.42.124', '61.177.172.89', '112.85.42.227', '112.85.42.73', '122.194.229.38', '112.85.42.74', '122.194.229.37', '112.85.42.15', '61.177.172.154', '157.230.120.129', '172.104.144.235', '122.194.229.54', '112.85.42.128', '222.186.42.13', '222.186.30.76', '221.131.165.50', '185.246.130.20', '221.131.165.65', '222.186.42.7', '221.181.185.151', '221.131.165.33', '96.9.67.48', '221.131.165.75', '58.222.83.94', '221.131.165.56', '221.181.185.159', '222.186.180.130', '222.187.232.39', '221.181.185.94', '222.187.254.41', '216.235.12.116', '114.240.224.140', '222.187.238.58', '221.181.185.111', '134.209.83.158', '222.186.42.137', '188.166.60.8', '24.221.224.89', '188.166.24.204', '74.81.30.208', '83.252.70.228', '221.131.165.62', '222.179.101.18', '89.106.23.3', '77.237.73.26', '24.54.148.227', '204.191.196.151', '179.213.34.231', '208.89.176.104', '136.144.41.139', '141.98.10.246', '52.188.213.193', '209.141.42.136', '2.183.55.15', '164.68.105.148', '45.141.84.126', '119.45.167.35', '205.185.122.230', '209.141.41.46', '205.185.125.72', '101.42.107.102', '156.205.108.157', '167.71.10.210', '121.169.34.119', '104.167.223.21', '20.206.109.196', '198.98.59.119', '209.141.44.236', '167.71.2.44', '167.71.12.34', '117.248.249.70', '222.187.237.11', '45.61.186.123', '20.102.80.72', '2.57.122.192', '95.217.33.238', '198.98.50.237', '164.90.203.66', '101.34.244.235', '60.170.247.162', '20.104.53.82', '157.90.104.189', '219.147.221.123', '209.141.51.224', '67.83.0.255', '20.48.236.70', '209.141.37.136', '123.57.37.19'] kraje = [] reader = geolite2.reader() for ip in adresy_ip: loc = reader.get(ip) if(loc): kraje.append(loc['country']['names']['en']) policzone = Counter(kraje) pprint(policzone)
Wynik:
'China': 71, 'United States': 40, 'Republic of Korea': 3, 'Sweden': 3, 'Netherlands': 3, 'Canada': 3, 'Russia': 2, 'India': 2, 'Germany': 2, 'Iran': 2, 'Vietnam': 1, 'Malaysia': 1, 'Switzerland': 1, 'Thailand': 1, 'Hong Kong': 1, 'France': 1, 'Cambodia': 1, 'Turkey': 1, 'Brazil': 1, 'Egypt': 1, 'Mali': 1, 'Finland': 1
Ponieważ nie wybieram się w najbliższym czasie ani do Chin, ani do USA, rozważam całkowite zablokowanie dostępu z tych dwóch krajów.
Obok głównego tematu wpisu, ale jednak nieco związane. Polecam przeniesienie SSH (w zasadzie w Twoim przypadku zmianę przekierowywanego portu na routerze) z portu 22 na powiedzmy 40. Zabezpieczenie to oczywiście żadne, ale powinno elegancko odsiać skany SSH z losowych automatów.