Wyciek Collection #1 - przegląd domen z użyciem Pythona
Wstęp
Około 16 stycznia 2019 (czyli z miesiąc przed tym wpisem) pojawiły się informacje o wielkim wycieku haseł zwanym Collection #1
.
Nazwano go tak od nazwy katalog na portalu MEGA, który zawierał około 3000 plików z hasłami do różnych portali.
Całość chyba dość szybko znikła z MEGA - choć na pewnych portalach pojawiła się informacja o ponownym dostępie do Collection #1-5
Pierwsze informacje o wycieku podał Troy Hunt we wpisie The 773 Million Record "Collection #1" Data Breach. Autor jest twórcą portalu Have I Been Pwned, który zbiera dane z różnych wycieków i pozwala sprawdzić czy nasz email pojawił się w jakimś z takich wycieków. Pozwala też zapisać się aby otrzymywać powiadomienia gdy nasz email pojawi się w przyszłych wyciekach.
Tak więc dostęp do wszystkich danych jest trudny ale za to na PasteBin jest dostępna lista plików w Collection #1 i do tego można ją ściągnąć w postaci czystego tekstu (RAW) więc za pomocą Pythona można ją łatwo pobrać i przejrzeć nazwy domen występujące w nazwach plików.
W ten sposób można sprawdzić jakie polskie domeny zostały "skompromitowane" lub z jakich krajów najwięcej domen pojawia się na liście.
Przegląd listy z użyciem Pythona
Krótki skrypt z użyciem standardowego modułu urllib.request
pozwala pobrać i wyświetlić całą listę.
Nie trzeba do tego instalować ani requests
ani beautifulsoup
lub lxml
do
import urllib.request
url = 'https://pastebin.com/raw/UsxU4gXA'
text = urllib.request.urlopen(url).read().decode()
print(text)
-rwxrwxrwx 33405108 Jan 22 2016 000webhost.txt
-rwxrwxrwx 165025 Jul 29 2017 01nii.ru {1.931} [HASH].txt
-rwxrwxrwx 1157635 Jun 17 2018 048235631.com {20.677} [HASH+NOHASH].txt
-rwxrwxrwx 37117 Jun 24 2018 0933779000.com {1.156} [HASH] [NOHASH].txt
-rwxrwxrwx 507968 Okt 1 2017 10th.holdgold.ru {9.283} [HASH].txt
-rwxrwxrwx 300417 Mär 25 2018 116.125.120.57 {5.612} [HASH].txt
-rwxrwxrwx 1396742 Jan 27 2018 124sold.gr {26.361} [HASH].txt
-rwxrwxrwx 86998 Apr 15 2018 1452arte.com {2.826} [NOHASH].txt
-rwxrwxrwx 1461800 Dez 4 2017 14.63.169.59 {39.634} [HASH].txt
-rwxrwxrwx 216683 Dez 2 2017 163.24.21.143 {3.645} [HASH].txt
-rwxrwxrwx 193992 Feb 3 2018 173.221.254.15 {4.923} [HASH].txt
-rwxrwxrwx 506613 Nov 9 2017 1924.holdgold.ru {9.256} [HASH].txt
-rwxrwxrwx 785272 Jul 26 2017 1.html
-rwxrwxrwx 139565 Nov 4 2017 1shopram.ga {2.456} [HASH].txt
Tekst dzielę na wiersze i w każdym wierszu wyrzucam początkowe 35 znaków, które uprawnienia, rozmiar i datę.
Pozbywam się też roszerzenia .txt
choć później doszedłem, że nie ma takiej potrzeby
lub można by zrobić przegląd plików według rozszerzeń bo występują też inne rozszerzenia niż .txt
.
# podział na wiersze
rows = text.split('\n')
# pozbycie się początku i rozszerzenia `.txt`
filenames = [item[35:].replace('.txt', '') for item in rows]
print('\n'.join(filenames))
000webhost
01nii.ru {1.931} [HASH]
048235631.com {20.677} [HASH+NOHASH]
0933779000.com {1.156} [HASH] [NOHASH]
10th.holdgold.ru {9.283} [HASH]
116.125.120.57 {5.612} [HASH]
124sold.gr {26.361} [HASH]
1452arte.com {2.826} [NOHASH]
14.63.169.59 {39.634} [HASH]
163.24.21.143 {3.645} [HASH]
173.221.254.15 {4.923} [HASH]
1924.holdgold.ru {9.256} [HASH]
1.html
1shopram.ga {2.456} [HASH]
Ponieważ wierszy jest dużo - co można sprawdzić za pomocą len(filenames)
- więc dla podglądu tylko części danych można wykorzystać, że teraz to jest lista wierszy i użyć przedziału [poczatek:koniec]
.
Przykładowo wyświetlenie pierwszych 10 wierszy [:10]
i ostatnich 10 wierszy [-10:]
print('\n'.join(filenames[:10]))
print('\n'.join(filenames[-10:]))
Przeglądając listę zauważyłem, że nie wszystkie nazwy plików zawierają nazwę domeny a te z domenami mają postać domena {liczba} [HASH]
tak więc zostawie tylko te które mają {
i do tego z nazwy zostawie tylko część występującą przed {
czyli wszystko przed pierwszą spacją.
To powinno dać mi same nazwy domen.
# pozostawienie nazw zawierających ` { `
filenames_filtered = [item for item in filenames if ' {' in item]
# domeny
domains = [item.split()[0] for item in filenames_filtered]
print('\n'.join(domains))
Teraz mogę wybrać domeny kończące się na .pl
. W ten sam sposób można też sprawdzać inne końcówki lub występowanie jakiś ciągów wewnątrz nazwy np. .co.
# polskie domeny
domains_pl = [item for item in domains if item.endswith('.pl')]
print('\n'.join(domains_pl))
cad-akademia.pl
fotografia.interklasa.pl
gmina.e-sochaczew.pl
iceneo.pl
jest-lirycznie.pl
mojetesty.pl
nieruchomoscikwiatkowski.pl
rapido.com.pl
skaner.p.lodz.pl
www.centrumdruku.com.pl
www.djembe.pl
www.eis.com.pl
www.ekonomicznie.pl
www.teatrgombrowicza.art.pl
Zauważyłem, że kilka domen zawiera ciąg xn--
co oznacza, że są to nazwy z użyciem znaków narodowych.
# domeny ze znakami unicode xn--
domains_xn = [item for item in domains if 'xn--' in item]
print('\n'.join(domains_xn))
Za pomocą .decode('idna')
można przekonwertować domeny z xn--
na znaki narodowe.
Przy okazji zauważyłem, że jedna nazwa ma niepotrzebne http
na początku nazwy - co nie pozwala na jej dekodowanie - więc go usuwam.
for item in domains_xn:
item = item.replace('http', '')
item_unicode = item.encode().decode('idna')
print('{0:55} | {1}'.format(item, item_unicode))
xn--76-vlci2a3g.xn--p1ai | ярик76.рф
www.xn--13-6kc3bfr2e.xn--90anbvlob.xn--p1ai | www.школа13.уоирбит.рф
www.xn----7sbajczhbabsdpgnsadrv0b8t.xn--p1ai | www.магазин-мотоэкипировки.рф
www.xn--c1akva.xn--p1ai | www.гипп.рф
www.xn--j1adjbfs.xn--p1ai | www.мпкпру.рф
xn--55qy60o.tw | 公雞.tw
xn--5-8sbgm4byg.com.ua | 5авеню.com.ua
xn--72c6aal6b6cub7c1c1e.ga | รองเท้าบูท.ga
xn--76-vlci2a3g.xn--p1ai | ярик76.рф
xn----7sbabarxgef8afscas9amg1h.xn--p1ai | мотозапчасти-магазин.рф
xn--80aiggch7bar.xn-----8kciivmbg1bdd2adjgh9n.xn--p1ai | картриджи.купить-с-доставкой.рф
xn--80asehdb.xn--80aeiluelyj.xn--p1ai | онлайн.анимевост.рф
xn--90aacmb0bd4a8i.xn--p1ai | брвмебель.рф
xn--90aacmb0bd4a8i.xn--p1ai | брвмебель.рф
xn--b1aabe4awacbof.xn--p1ai | городковров.рф
xn--b3cupa4b7cm1eua.tk | ชุดนอนหมี.tk
xn--www-2ddamtijqp.vetlek.ru | синоптикwww.vetlek.ru
Jak wspomniałem wcześniej możemy użyć len()
do sprawdzenia ile jest poszczególnych domen
print('ilość wszystkich nazw :', len(filenames)) # 2890
print('ilość po odfiltrowaniu:', len(filenames_filtered)) # 2701
print('ilość z .pl :', len(domains_pl)) # 14
print('ilość z xn-- :', len(domains_xn)) # 17
ilość wszystkich nazw : 2890
ilość po odfiltrowaniu: 2701
ilość z .pl : 14
ilość z xn-- : 17
Wśród nazw plików zauważyłem też takie, które nie zawierają nazwy domeny
ale powtarzające się słowa jak accounts
czy .mega
. Zainteresowało mnie też w ilu nazwach występuje [NOHASH]
# (dla nazw plików przed filtrowaniem i obcięciem do samej domeny)
filenames_accounts = [item for item in filenames if 'accounts' in item]
filenames_mega = [item for item in filenames if '.mega' in item]
filenames_nohash = [item for item in filenames if '[NOHASH]' in item]
print('ilość z accounts:', len(filenames_accounts))
print('ilość z .mega :', len(filenames_mega))
print('ilość z [NOHASH]:', len(filenames_nohash))
ilość z accounts: 32
ilość z .mega : 9
ilość z [NOHASH]: 843
Na koniec postanowiłem policzyć ile razy występują poszczególne domeny najwyższego poziomu (TLD - Top Level Domain)
więc do tego postanowiłem użyć klasy Counter
ze standarowego modułu collections
from collections import Counter
domains_TLD = [item.split('.')[-1] for item in domains]
domains_TLD_count = Counter(domains_TLD)
for key, value in domains_TLD_count.most_common():
print(' {:10} | {}'.format(key, value))
Początek wyniku
com | 953
ru | 313
net | 140
de | 116
org | 109
uk | 96
br | 67
it | 52
cz | 51
kr | 51
info | 45
hu | 43
jp | 38
ua | 37
in | 36
au | 31
th | 29
fr | 28
tw | 23
eu | 21
ro | 21
ca | 17
ar | 15
be | 14
pl | 14
by | 13
az | 13
biz | 13
za | 12
sk | 12
xn--p1ai | 12
pt | 11
es | 11
nl | 11
Sprawdzenie NameServer z użyciem Pythona
W komentarzach do kilku polskich artykułów na temat wycieku przeczytałem, że na liście są polskie strony, które nie mają końcówki .pl
.
Postanowiłem więc sprawdzić z jakich NameServerów korzytają domeny licząc, że polskie strony będą raczej korzystać z polskich hostingów, które mają .pl
w nazwie.
Choć to nie musi zawsze być prawda bo dostępny w polsce OVH.pl
wykorzystuje ovh.net
Do tego celu doinstalowałem moduł dnspython
pip install dnspython
Przykład użycia
import dns.resolver
domain = 'onet.pl'
for ns in dns.resolver.query(domain, 'NS'):
print(ns)
i wynik
dns3.onet.pl.
ns1.ikp.pl.
dns2.onet.pl.
dns1.onet.pl.
W trakcie sprawdzania pojawiały mi się wyjątki
dns.resolver.NoAnswer
gdy sprawdzana była subdomena więc trzeba było obciąć pierwszy człon i spróbować ponownie,dns.resolver.NXDOMAIN
lubdns.resolver.NoNameservers
gdy domena już nie istniała,dns.resolver.Timeout
gdy serwer nie odpowiedział na czas.
import dns.resolver
domain = 'onet.pl'
try:
all_ns = [str(ns) for ns in dns.resolver.query(domain, 'NS')]
print(all_ns)
except dns.resolver.NoAnswer as ex:
print('[DEBUG] dns.resolver.NoAnswer:', ex)
# to może być subdomena więc obcinamy pierwszy człon i sprawdzamy ponownie
# o ile nie zostanie tylko ostatni człon czyli TLD
parts = domain.split('.')
if len(parts) > 2:
subdomain = '.'.join(parts[1:])
try:
all_ns = [str(ns) for ns in dns.resolver.query(subdomain, 'NS')]
print(all_ns)
except Exception as ex:
print('[DEBUG] Exception:', ex)
except dns.resolver.NXDOMAIN as ex:
print('[DEBUG] dns.resolver.NXDOMAIN:', ex)
except dns.resolver.NoNameservers as ex:
print('[DEBUG] dns.resolver.NoNameservers:', ex)
except dns.resolver.Timeout as ex:
print('[DEBUG] dns.resolver.Timeout:', ex)
W ten sposób znalazłem następujące domeny z polskimi NameServerami:
falowniki.com
wsw.polskiearaby.com
www.dospelprofessional.com
www.fx-portal.com
indexcopernicus.com # kilka subdomen
oraz jedną domenę .pl
, która już nie istnieje:
iceneo.pl
Kod wymaga domains
i domains_pl
z wcześniejszych przykładów.
import dns.resolver
def get_all_ns(domain, debug=False):
"""Pobieranie NameServerów dla podanej domeny
Return: (domain, all_ns)
"""
if debug:
print('[DEBUG] domain:', domain)
try:
all_ns = [str(ns) for ns in dns.resolver.query(domain, 'NS')]
return domain, all_ns
except dns.resolver.NoAnswer as ex:
if debug:
print('[DEBUG] dns.resolver.NoAnswer:')#, ex)
# to może być subdomena więc obcinamy pierwszy człon i sprawdzamy ponownie
# o ile nie zostanie tylko ostatni człon czyli TLD
parts = domain.split('.')
if debug:
print('[DEBUG] parts:', parts)
if len(parts) > 2:
subdomain = '.'.join(parts[1:])
if debug:
print('[DEBUG]', domain, '->', subdomain)
try:
all_ns = [str(ns) for ns in dns.resolver.query(subdomain, 'NS')]
return (subdomain, all_ns)
except Exception as ex:
if debug:
print('[DEBUG] Exception:', ex)
except dns.resolver.NXDOMAIN as ex:
print('[DEBUG] dns.resolver.NXDOMAIN:', ex)
except dns.resolver.NoNameservers as ex:
print('[DEBUG] dns.resolver.NoNameservers:', ex)
except dns.resolver.Timeout as ex:
print('[DEBUG] dns.resolver.Timeout:', ex)
return (domain, [])
# sprawdzanie tylko domen .pl
for domain in domains_pl:
print('domain:', domain)
domain, all_ns = get_all_ns(domain)
print(' ', domain)
for item in all_ns:
print(' ->', item)
# sprawdzanie domen .com
for domain in domains:
if domain.endswith('.com'):
print('domain:', domain)
domain, all_ns = get_all_ns(domain)
print(' ', domain)
for item in all_ns:
if item.endswith('pl.'):
print(' ->', item)
Jeśli potrzebne są dodatkowe informacje o domenie to można jeszcze ręcznie sprawdzić na MXToolBox.com lub użyć requests + beautifulsoup/lxml
lub selenium
w tym celu. Ale to już pomijam.
Portal MXToolBox.com
ma też RestAPI MojoDNS.com do darmowego sprawdzania DNS (ale nie więcej niż jedną domenę na sekundę). Tego narzędzia jednak nie sprawdzałem w działąniu.
Buy a Coffee