Иногда полезно узнать, какие данные находятся за адресом сайта, за его доменным именем. Конечно же, есть множество сервисов, которые с радостью предоставят вам эту информацию. Но, все это нужно будет делать вручную. Думаю, что вы понимаете, что информация о домене на Whois не заканчивается. Я, на основании некоторых примеров кода, сделал свою попытку разобраться, как же можно получить данные с помощью скриптов на Python. Конечно же, никто не даст вам стопроцентного результата поиска. Вот и у меня, не всегда, получается, найти полные данные по сайту. Впрочем, не мне судить, получилось у меня что-то или нет. Давайте начинать писать код.
Я также как и DeathDay полностью на стороне добра, а потому:
В данном случае моей целью было попробовать, как работают те или иные технологии поиска информации в интернете. Давайте начнем с того, что понадобиться установить для работы скрипта, а уж после будем более детально рассматривать его код.
Что потребуется?
Для получения информации Whois о домене нужно установить модули работы с сервисами. Для установки пишем в терминале:
Для работы модуля, который будет детектировать Cloudflare, понадобиться установить библиотеку requests. Впрочем, впоследствии она понадобиться не только здесь. Также, для получения данных со страницы, то есть для её парсинга, потребуется установить библиотеку bs4 и модуль для парсинга xml – lxml:
Для получения данных из сертификата, с сервиса crt.sh, нужно установить библиотеку для подключения к базе данных PostgreSQL - psycopg2. Также, для того, чтобы пинговать доступность ресурса установим библиотеку ping3, и библиотеку gevent для обработки исключений. Пишем в терминале:
Для получения данных из SLL-сертификата домена нужно установить библиотеку pyOpenSSL. Пишем в терминале:
Ну и напоследок, чтобы вывод в терминале не был слишком скучным, установим библиотеку colorama.
По библиотекам всё. Теперь хочу сказать, что в этот раз я не стал пихать весь код в один модуль. Конечно же, можно было бы так сделать, но тогда его код был бы на самом деле, очень большим и неудобным. Поэтому я разделил код по различным модулям, каждый из которых выполняет определенный функционал и ничего более, а результат возвращает в основной модуль для обработки. Это делает скрипт более гибким и расширяемым. То есть, я могу дописать какой-то функционал в еще один модуль и добавить в основной код его обработку. А теперь, давайте начнем писать код.
Конечно же, ничего сверхъестественного тут не происходит. Просто это моя попытка научиться писать определенные вещи, так как я всё ещё учусь это делать ).
Получение информации Whois о домене
Создадим модуль whois_info.py. Импортируем в него необходимые библиотеки для его работы:
Создадим функцию whois_domain(domain), которая на входе принимает имя домена для получения информации. Теперь создадим словарь, в котором определим, какие из ключей полученного json будем заменять на нужные нам значения. Можно было бы этого не делать, а просто вывести все как есть. Но приятнее, когда не нужно напрягаться и просто читать то, что пришло в ответе )).
Теперь создадим словарь, в который будем складывать уже полученные и обработанные значения из ответа.
Делаем запрос whois, в котором указываем доменное имя и флаг, который не обязателен. Но, как написано в документации, он позволяет подавить вывод предупреждений. И да, сейчас у библиотеки с этим более-менее все хорошо. Но было время, когда не помогали никакие ключи, и выскакивало неприятное предупреждение, хотя ответ приходил исправно.
Теперь в цикле пробежимся по полученному в ответе json и если значение ключа не является None, добавляем его в словарь, попутно заменяя ключи на нужные нам значения. Заключим данный код в блок try – except, чтобы обработать ошибку типа данных. Также заключим весь код в блок try – except, где будем обрабатывать ошибку получения данных и просто возвращать пустой словарь. А если все прошло отлично, возвращаем из функции словарь со значениями.
Проверка ip-адреса на принадлежность к Cloudflare
Для того, чтобы проверить, скрыт ip-адрес за Cloudflare или нет, нужно его прогнать по всему диапазону адресов, которые за ним закреплены. И если данный адрес будет найден, возвратить значение True. В противном же случае, если адрес не найден, вернуть False.
Тут, на самом деле, можно пойти двумя путями. Либо, как у меня, проверять адрес на принадлежность к определенным диапазонам, либо получить данные ssl-сертификата и посмотреть, на кого он выдан. У тех доменов, что прячутся за Cloudflare, обычно он на него и выдан.
Создадим модуль cloudflare_detect.py. Импортируем в него необходимые для работы библиотеки:
Создадим список, в который будем добавлять диапазоны адресов Cloudflare.
Как видите, у меня список не пустой и в нем уже есть один из диапазонов. Дело в том, что на официальной странице диапазонов адресов, данного диапазона нет, но при проверке информации whios видно, что данный диапазон также принадлежит к Cloudflare. Поэтому, пришлось добавить его вручную.
Создадим функцию ip_in_range(ip, addr), которая на входе принимает ip-адрес для проверки, и диапазон адресов, в которых его нужно проверить. Воспользуемся модулем ipaddress, а точнее его функциями ip_network и ip_address. Первая функция создает из строки адрес, а вторая диапазон. И делается проверка на принадлежность к данному диапазону. Если принадлежит, возвращает True, нет – False.
Создадим в этом же модуле еще одну функцию cloudf_detect(domain), которая на входе принимает домен. Теперь, для того, чтобы сделать проверку, нам нужно получить диапазоны адресов, которые принадлежат Cloudflare. Можно для этого сделать список, но, чтобы иметь актуальную информацию, получим данные со страницы с диапазонами.
Теперь разделим полученный список по «\n» и пробежимся по нему циклом для заполнения нашего списка диапазонов.
Затем нужно получить ip-адрес домена, который мы будем проверять. Для этого воспользуемся socket. Также обработаем ошибку получения данных:
Создадим цикл в котором пробежимся по списку диапазонов адресов, в котором будем вызывать функцию проверки на принадлежность к диапазону, передавая ей ip и диапазон. Если функция возвращает True, то на этом работу функции можно завершить, так как ip-адрес принадлежит к диапазону. Если False, продолжаем итерации цикла. А если же ничего не найдено, то есть ip не принадлежит диапазону, соответственно возвращаем False.
Поиск реального адреса домена за Cloudflare с помощью CrimeFlare
По поводу данного способа все не однозначно. Для начала, это уже «второе пришествие» данного проекта, так как первый проект был заблокирован и снесен с хостинга. Теперь он возродился в виде PHP скрипта на GitHub и приложения на Heroku. Во-вторых, насколько я понял, поиск адресов происходит по некой базе. И не совсем понятно, обновляется она или нет. Не ясно как наполняется данная база адресами. Так как разработчик не особо об этом распространяется. Но, что имеем, то имеем. Ну и в-третьих, приложение на Heroku может просто перестать работать.
Такое уже произошло примерно около месяца назад. Я тогда заинтересовался попытками определения адресов за Cloudflare и наткнулся на данное приложение. Когда мне перестали приходить ответы от приложения я перешел на его страницу и обнаружил, что оно просто не работает. Тогда я связался с автором и сообщил ему о проблеме, на что он ответил, что в курсе. Но ничего пока поделать не может, так как аккаунт платный, а денег для его оплаты пока нет. Но, похоже через какое-то время деньги все же нашлись, так как приложение снова заработало. Я попытался спросить, как работает данное приложение, но ответа до сих пор не получил. Ну да ладно. Это предыстория и попытка сказать, что использование данного модуля не даст стопроцентного результата и его работа может прерваться в любой момент.
Создадим модуль crimeflare_search.py. Импортируем в него библиотеки нужные для работы:
Создадим функцию crimf_search(domain), которая на входе принимает имя домена. А дальше идет банальный парсинг. Делаем запрос request к странице базы с параметрами и парсим из ответа адрес, если он есть. Если нет, обрабатываем ошибку поиска индекса и возвращаем значение False. Если же адрес найден, возвращаем адрес.
Полный код модуля crimeflare_search.py:
Получение субдоменов из базы crt.sh
Создадим модуль subdomain_cert.py. Импортируем в него библиотеки и инициализируем модуль colorama:
Создадим функцию subdomain_in_sert(domain), которая на входе принимает домен. Создадим множество для добавления в него субдоменов из сертификата. Множество нужно для того, чтобы отсеять повторяющиеся результаты, так как в выборке из базы данных возвращается много одинаковых значений, так как выборка идет по всем найденным сертификатам с доменным именем.
Теперь создадим подключение к базе данных, после чего сформируем запрос, выполним его и обработаем полученные значения. Надо признаться, что данный код я позаимствовал из какой-то библиотеки для поиска субдоменов. Потому как изначально я просто делал, зарос с помощью request и парсил результаты. Так тоже работало, но довольно нестабильно. Пока не наткнулся на данный код. Если честно, то даже и не знал, что под капотом крутиться PostgreSQL. С помощью данного кода получение данных сертификатов работает гораздо стабильнее.
Ну, а дальше идет выборка нужных значений, из которых, для начала исключаем все субдомены со «*», затем проверяем, был ли уже такой субдомен или нет. Если был, то пропускаем итерацию. Пингуем полученный домен из выборки и если ответ не False добавляем в множество. После того, как обработка адресов завершиться, возвращаем множество из функции.
Что же, на этом первая часть закончена. Продолжение описания модулей в следующей части.
Спасибо за внимание. Надеюсь, что данная информация будет вам полезна
Я также как и DeathDay полностью на стороне добра, а потому:
Дисклеймер: Все данные, предоставленные в данной статье, взяты из открытых источников, не призывают к действию и являются только лишь данными для ознакомления, и изучения механизмов используемых технологий.
В данном случае моей целью было попробовать, как работают те или иные технологии поиска информации в интернете. Давайте начнем с того, что понадобиться установить для работы скрипта, а уж после будем более детально рассматривать его код.
Что потребуется?
Для получения информации Whois о домене нужно установить модули работы с сервисами. Для установки пишем в терминале:
pip install python-whois PySocks
Для работы модуля, который будет детектировать Cloudflare, понадобиться установить библиотеку requests. Впрочем, впоследствии она понадобиться не только здесь. Также, для получения данных со страницы, то есть для её парсинга, потребуется установить библиотеку bs4 и модуль для парсинга xml – lxml:
pip install requests bs4 lxml
Для получения данных из сертификата, с сервиса crt.sh, нужно установить библиотеку для подключения к базе данных PostgreSQL - psycopg2. Также, для того, чтобы пинговать доступность ресурса установим библиотеку ping3, и библиотеку gevent для обработки исключений. Пишем в терминале:
pip install psycopg2 ping3 gevent
Для получения данных из SLL-сертификата домена нужно установить библиотеку pyOpenSSL. Пишем в терминале:
pip install pyOpenSSL
Ну и напоследок, чтобы вывод в терминале не был слишком скучным, установим библиотеку colorama.
pip install colorama
По библиотекам всё. Теперь хочу сказать, что в этот раз я не стал пихать весь код в один модуль. Конечно же, можно было бы так сделать, но тогда его код был бы на самом деле, очень большим и неудобным. Поэтому я разделил код по различным модулям, каждый из которых выполняет определенный функционал и ничего более, а результат возвращает в основной модуль для обработки. Это делает скрипт более гибким и расширяемым. То есть, я могу дописать какой-то функционал в еще один модуль и добавить в основной код его обработку. А теперь, давайте начнем писать код.
Конечно же, ничего сверхъестественного тут не происходит. Просто это моя попытка научиться писать определенные вещи, так как я всё ещё учусь это делать ).
Получение информации Whois о домене
Создадим модуль whois_info.py. Импортируем в него необходимые библиотеки для его работы:
import whois
Создадим функцию whois_domain(domain), которая на входе принимает имя домена для получения информации. Теперь создадим словарь, в котором определим, какие из ключей полученного json будем заменять на нужные нам значения. Можно было бы этого не делать, а просто вывести все как есть. Но приятнее, когда не нужно напрягаться и просто читать то, что пришло в ответе )).
Python:
list_field = {"domain_name": "Доменное имя", "registrar": "Регистратор", "updated_date": "Дата обновления",
"creation_date": "Дата создания", "expiration_date": "Дата истечения срока регистрации",
"name_servers": "Серверы NS", "dnssec": "dnssec", "name": "Владелец", "org": "Организация",
"address": "Адрес", "city": "Город", "state": "Штат", "zipcode": "Индекс", "country": "Страна"}
Теперь создадим словарь, в который будем складывать уже полученные и обработанные значения из ответа.
whois_dict = dict()
Делаем запрос whois, в котором указываем доменное имя и флаг, который не обязателен. Но, как написано в документации, он позволяет подавить вывод предупреждений. И да, сейчас у библиотеки с этим более-менее все хорошо. Но было время, когда не помогали никакие ключи, и выскакивало неприятное предупреждение, хотя ответ приходил исправно.
wh = whois.whois(domain, flags=0)
Теперь в цикле пробежимся по полученному в ответе json и если значение ключа не является None, добавляем его в словарь, попутно заменяя ключи на нужные нам значения. Заключим данный код в блок try – except, чтобы обработать ошибку типа данных. Также заключим весь код в блок try – except, где будем обрабатывать ошибку получения данных и просто возвращать пустой словарь. А если все прошло отлично, возвращаем из функции словарь со значениями.
Python:
# pip install python-whois PySocks
import whois
def whois_domain(domain):
list_field = {"domain_name": "Доменное имя", "registrar": "Регистратор", "updated_date": "Дата обновления",
"creation_date": "Дата создания", "expiration_date": "Дата истечения срока регистрации",
"name_servers": "Серверы NS", "dnssec": "dnssec", "name": "Владелец", "org": "Организация",
"address": "Адрес", "city": "Город", "state": "Штат", "zipcode": "Индекс", "country": "Страна"}
whois_dict = dict()
try:
wh = whois.whois(domain, flags=0)
for field in list_field:
if wh.get(field) is not None:
try:
if field == 'updated_date':
date = [str(dat) for dat in wh.get(field)]
if str(wh[field]) not in whois_dict:
whois_dict[list_field[field]] = date
elif field == 'creation_date':
date = [str(dat) for dat in wh.get(field)]
if str(wh[field]) not in whois_dict:
whois_dict[list_field[field]] = date
elif field == 'expiration_date':
date = [str(dat) for dat in wh.get(field)]
if str(wh[field]) not in whois_dict:
whois_dict[list_field[field]] = date
else:
if str(wh[field]) not in whois_dict:
whois_dict[list_field[field]] = wh[field]
except TypeError:
if str(wh[field]) not in whois_dict:
whois_dict[list_field[field]] = str(wh[field])
except whois.parser.PywhoisError:
return whois_dict
return whois_dict
Проверка ip-адреса на принадлежность к Cloudflare
Для того, чтобы проверить, скрыт ip-адрес за Cloudflare или нет, нужно его прогнать по всему диапазону адресов, которые за ним закреплены. И если данный адрес будет найден, возвратить значение True. В противном же случае, если адрес не найден, вернуть False.
Тут, на самом деле, можно пойти двумя путями. Либо, как у меня, проверять адрес на принадлежность к определенным диапазонам, либо получить данные ssl-сертификата и посмотреть, на кого он выдан. У тех доменов, что прячутся за Cloudflare, обычно он на него и выдан.
Создадим модуль cloudflare_detect.py. Импортируем в него необходимые для работы библиотеки:
Python:
import socket
from ipaddress import ip_network, ip_address
import requests
Создадим список, в который будем добавлять диапазоны адресов Cloudflare.
list_addr = ["104.16.0.0/12"]
Как видите, у меня список не пустой и в нем уже есть один из диапазонов. Дело в том, что на официальной странице диапазонов адресов, данного диапазона нет, но при проверке информации whios видно, что данный диапазон также принадлежит к Cloudflare. Поэтому, пришлось добавить его вручную.
Создадим функцию ip_in_range(ip, addr), которая на входе принимает ip-адрес для проверки, и диапазон адресов, в которых его нужно проверить. Воспользуемся модулем ipaddress, а точнее его функциями ip_network и ip_address. Первая функция создает из строки адрес, а вторая диапазон. И делается проверка на принадлежность к данному диапазону. Если принадлежит, возвращает True, нет – False.
Python:
def ip_in_range(ip, addr):
if ip_address(ip) in ip_network(addr):
return True
return False
Создадим в этом же модуле еще одну функцию cloudf_detect(domain), которая на входе принимает домен. Теперь, для того, чтобы сделать проверку, нам нужно получить диапазоны адресов, которые принадлежат Cloudflare. Можно для этого сделать список, но, чтобы иметь актуальную информацию, получим данные со страницы с диапазонами.
Python:
url = 'https://www.cloudflare.com/ips-v4'
req = requests.get(url=url)
Теперь разделим полученный список по «\n» и пробежимся по нему циклом для заполнения нашего списка диапазонов.
Python:
for adr in req.text.split("\n"):
list_addr.append(adr)
Затем нужно получить ip-адрес домена, который мы будем проверять. Для этого воспользуемся socket. Также обработаем ошибку получения данных:
Python:
try:
ip = socket.gethostbyname(domain)
except socket.gaierror:
return
Создадим цикл в котором пробежимся по списку диапазонов адресов, в котором будем вызывать функцию проверки на принадлежность к диапазону, передавая ей ip и диапазон. Если функция возвращает True, то на этом работу функции можно завершить, так как ip-адрес принадлежит к диапазону. Если False, продолжаем итерации цикла. А если же ничего не найдено, то есть ip не принадлежит диапазону, соответственно возвращаем False.
Python:
for addr in list_addr:
detect = ip_in_range(ip, addr)
if detect:
return True
return False
Python:
# pip install requests
import socket
from ipaddress import ip_network, ip_address
import requests
list_addr = ["104.16.0.0/12"]
# проверка адреса в диапазоне ip подсети
def ip_in_range(ip, addr):
if ip_address(ip) in ip_network(addr):
return True
return False
# проверка на принадлежность адреса Cloudflair
def cloudf_detect(domain):
url = 'https://www.cloudflare.com/ips-v4'
req = requests.get(url=url)
for adr in req.text.split("\n"):
list_addr.append(adr)
try:
ip = socket.gethostbyname(domain)
except socket.gaierror:
return
for addr in list_addr:
detect = ip_in_range(ip, addr)
if detect:
return True
return False
Поиск реального адреса домена за Cloudflare с помощью CrimeFlare
По поводу данного способа все не однозначно. Для начала, это уже «второе пришествие» данного проекта, так как первый проект был заблокирован и снесен с хостинга. Теперь он возродился в виде PHP скрипта на GitHub и приложения на Heroku. Во-вторых, насколько я понял, поиск адресов происходит по некой базе. И не совсем понятно, обновляется она или нет. Не ясно как наполняется данная база адресами. Так как разработчик не особо об этом распространяется. Но, что имеем, то имеем. Ну и в-третьих, приложение на Heroku может просто перестать работать.
Такое уже произошло примерно около месяца назад. Я тогда заинтересовался попытками определения адресов за Cloudflare и наткнулся на данное приложение. Когда мне перестали приходить ответы от приложения я перешел на его страницу и обнаружил, что оно просто не работает. Тогда я связался с автором и сообщил ему о проблеме, на что он ответил, что в курсе. Но ничего пока поделать не может, так как аккаунт платный, а денег для его оплаты пока нет. Но, похоже через какое-то время деньги все же нашлись, так как приложение снова заработало. Я попытался спросить, как работает данное приложение, но ответа до сих пор не получил. Ну да ладно. Это предыстория и попытка сказать, что использование данного модуля не даст стопроцентного результата и его работа может прерваться в любой момент.
Создадим модуль crimeflare_search.py. Импортируем в него библиотеки нужные для работы:
import requests
Создадим функцию crimf_search(domain), которая на входе принимает имя домена. А дальше идет банальный парсинг. Делаем запрос request к странице базы с параметрами и парсим из ответа адрес, если он есть. Если нет, обрабатываем ошибку поиска индекса и возвращаем значение False. Если же адрес найден, возвращаем адрес.
Полный код модуля crimeflare_search.py:
Python:
# pip install requests
import requests
def crimf_search(domain):
url = f'https://crimeflare.herokuapp.com/?url={domain}'
try:
req = requests.get(url).text.split("</pre>")[0].split('Real IP:')[1].strip().split()[1]
return req
except IndexError:
return False
Получение субдоменов из базы crt.sh
Создадим модуль subdomain_cert.py. Импортируем в него библиотеки и инициализируем модуль colorama:
Python:
import psycopg2
from colorama import Fore
from colorama import init
from gevent import exceptions
from ping3 import ping
init()
Создадим функцию subdomain_in_sert(domain), которая на входе принимает домен. Создадим множество для добавления в него субдоменов из сертификата. Множество нужно для того, чтобы отсеять повторяющиеся результаты, так как в выборке из базы данных возвращается много одинаковых значений, так как выборка идет по всем найденным сертификатам с доменным именем.
sub_list = set()
Теперь создадим подключение к базе данных, после чего сформируем запрос, выполним его и обработаем полученные значения. Надо признаться, что данный код я позаимствовал из какой-то библиотеки для поиска субдоменов. Потому как изначально я просто делал, зарос с помощью request и парсил результаты. Так тоже работало, но довольно нестабильно. Пока не наткнулся на данный код. Если честно, то даже и не знал, что под капотом крутиться PostgreSQL. С помощью данного кода получение данных сертификатов работает гораздо стабильнее.
Python:
conn = psycopg2.connect(host="crt.sh", database="certwatch", user="guest", port="5432")
conn.autocommit = True
cur = conn.cursor()
query = f"SELECT ci.NAME_VALUE NAME_VALUE FROM certificate_identity ci WHERE ci.NAME_TYPE = 'dNSName' AND " \
f"reverse(lower(ci.NAME_VALUE)) LIKE reverse(lower('%.{domain}')) "
cur.execute(query)
result = cur.fetchall()
cur.close()
conn.close()
Ну, а дальше идет выборка нужных значений, из которых, для начала исключаем все субдомены со «*», затем проверяем, был ли уже такой субдомен или нет. Если был, то пропускаем итерацию. Пингуем полученный домен из выборки и если ответ не False добавляем в множество. После того, как обработка адресов завершиться, возвращаем множество из функции.
Python:
# pip install psycopg2 ping3 gevent
import psycopg2
from colorama import Fore
from colorama import init
from gevent import exceptions
from ping3 import ping
init()
def subdomain_in_sert(domain):
sub_list = set()
conn = psycopg2.connect(host="crt.sh", database="certwatch", user="guest", port="5432")
conn.autocommit = True
cur = conn.cursor()
query = f"SELECT ci.NAME_VALUE NAME_VALUE FROM certificate_identity ci WHERE ci.NAME_TYPE = 'dNSName' AND " \
f"reverse(lower(ci.NAME_VALUE)) LIKE reverse(lower('%.{domain}')) "
cur.execute(query)
result = cur.fetchall()
cur.close()
conn.close()
old_url = ''
if len(result) > 0:
print(Fore.YELLOW + f'\r- Добавляю субдомены из сертификата')
for item in result:
if "*" not in item[0]:
if item[0] == old_url:
continue
old_url = item[0]
try:
p = ping(item[0])
if p != False:
sub_list.add(item[0])
print(Fore.GREEN + f'\r[+] {item[0]}', end='')
else:
print(Fore.RED + f'\r[-] {item[0]}', end='')
except exceptions.LoopExit:
return
return sub_list
Что же, на этом первая часть закончена. Продолжение описания модулей в следующей части.
Спасибо за внимание. Надеюсь, что данная информация будет вам полезна