Статья Проверка IP-адреса в Blacklists (DNSBL) с помощью Python

«И не было спасения от спама, пока люди не придумали DNSBL!» :LOL: А если серьёзно, то DNSBL – это DNS blocklist, то есть, черные списки, которые хранятся на серверах с использованием структуры DNS. Уже из самого названия понятно, что данные списки предназначены для того, чтобы почтовые сервера проверяли их, и, на основании полученных данных формировали репутацию адреса или хоста. Попасть в эти списки легко, а вот выбраться уже тяжелее. Но, речь сейчас не о том, как удалить адрес из черного списка, а о том, как проверить, не попал ли адрес вашего домена туда, с помощью Python.

000.jpg


Попасть в черные списки легко. Тут, можно сказать, все зависит от того, кто формирует правила добавления в них адреса. И если Луна совпала с Сатурном, то может статься так, что ваш белый и пушистый домен попадет в него с очень большой легкостью. Для примера, попасть можно по следующим причинам: подозрительная активность почтового сервера; неправильная настройка конфигурации сервера; жалобы клиентов; прокси и множество других причин.

Черных списков хостов достаточно много. Я, при не особо настойчивом поиске нашел 97 штук, которые работают более-менее стабильно. Некоторые из этих списков, такие как, к примеру, spamhaus.org приобрели хорошую репутацию и их стали использовать самые разнообразные сервисы.


Что потребуется?

Для начала, нам потребуется библиотека для работы с dns-серверами, dnspython. Для ее установки пишем в терминале:

pip install dnspython

Так же нам потребуется библиотека requests, для того, чтобы, во-первых, получать внешний ip-адрес компьютера, хоть это и не обязательно, а во-вторых, чтобы загружать список DNSBL, в-третьих, загружать список диапазонов адресов Cloudflare. Для ее установки пишем в терминале:

pip install requests

Ну и совсем не обязательная, но, тем не менее, радующая глаз библиотека colorama. С ее помощью довольно удобно выводить различные сообщения в терминал в цвете. Установим ее с помощью команды:

pip install colorama

Теперь, когда все нужные библиотеки установлены, давайте импортируем их в скрипт, и сразу же инициализируем библиотеку colorama.

Python:
import re
import socket
from ipaddress import ip_network, ip_address

import dns
from colorama import Fore
from colorama import init
from dns import resolver
from requests import get, exceptions

init()


Проверка ip-адреса на принадлежность Cloudflare

Эта проверка делается для того, чтобы попусту не тратить ваше время и не мучить DNS-сервера. Впрочем, проверять или нет, тут уже все зависит от пользователя. А вот о том, входит ли адрес в диапазон сообщить нужно.

Создадим функцию ip_in_range(ip, addr), которая на входе принимает ip-адрес и диапазон адресов Cloudflare, проверяет, содержится ли адрес в диапазоне и возвращает в зависимости от этого True или False. Для выполнения данной проверки будем использовать библиотеку ipaddress и ее функции ip_address и ip_network. С помощью первой мы переводим строку в адрес. А с помощью второй создаем диапазон адресов из переданного адреса с указанным диапазоном.

Python:
def ip_in_range(ip, addr):
    if ip_address(ip) in ip_network(addr):
        return True
    return False

На следующем этапе нам понадобиться вторая функция, которая тоже относиться к проверке адреса на принадлежность Cloudflare. Создадим ее, и назовем, к примеру: cloudf_detect(ip). Здесь, на входе функция получает ip-адрес. Затем загружает список диапазонов адресов и в цикле помещает их в предварительно созданный для этого список list_addr. Данный список не является пустым, я уже добавил в него один диапазон, так как данного диапазона нет при загрузке. А вот при проверке адреса с помощью Whois, он вдруг находиться. Затем запускаем еще один цикл, в котором пробегаемся по списку сформированных диапазонов адресов и передаем их в функцию проверки адреса. Если адрес есть в диапазонах, возвращаем True, если нет – False.

Python:
def cloudf_detect(ip):
    list_addr = ["104.16.0.0/12"]

    url = 'https://www.cloudflare.com/ips-v4'
    req = get(url=url)

    for adr in req.text.split("\n"):
        list_addr.append(adr)

    for addr in list_addr:
        detect = ip_in_range(ip, addr)
        if detect:
            return True
    return False


Получение внешнего ip-адреса

Тут все просто. Делаем запрос с помощью requests на сайт api.ipify.org, а в ответ нам прилетает наш внешний адрес. Если же по какой-то причине соединение с данным сайтом не получиться, обработаем исключение и вернем адрес локальной петли.

Python:
def public_ip():
    try:
        return get('https://api.ipify.org/').text
    except exceptions.ConnectionError:
        return '127.0.0.1'


Проверка ip-адреса в черных списках

Для начала создадим функцию dns_bl_check(ip). На входе она принимает ip-адрес, даже если вы проверяете наличие домена в черных списках. Создаем словарь, в который будем помещать имя списка, в котором найдется адрес и, содержимое поля «TXT», в котором, преимущественно, указан адрес, куда можно обратиться за подробностями, если ваш адрес вдруг нашелся здесь.

После получаем список DNSBL, в которых будем проверять адрес. Я для списка создал на GitHub файлик, в который все это и запихал. Можно было бы использовать и локальный файл. Но так мне показалось немного удобнее. В списке содержится 97 адресов. Все их я проверил на валидность, и на момент написания статьи они работали. Получаем мы список с помощью requests. Затем создаем переменную, в которую помещаем текст результата запроса и разбиваем на строки, чтобы получился список строк. А уже по этим строкам запускаем цикл, где перебираем каждую и формируем запрос. Запрос формируется в обратном порядке, то есть, у вас должен получиться реверс: 2.0.0.127.dbl.spamhaus.org. Потому мы разбиваем строку на части по строкам, делаем реверс и собираем снова. Вторым параметром для сборки запроса служит адрес черного списка из строки.

Python:
    print(Fore.YELLOW + '\n- Проверка черных списков\n')
    bad_dict = dict()
    req = get('https://raw.githubusercontent.com/evgenycc/DNSBL-list/main/DNSBL')
    read = req.text.splitlines()
    for serv in read:
        req = f"{'.'.join(reversed(ip.split('.')))}.{serv.strip()}"

Теперь создаем резольвер, устанавливаем таймаут для запроса, а также время его жизни.

Python:
            resolv = dns.resolver.Resolver()
            resolv.timeout = 5
            resolv.lifetime = 5

Делаем запросы к записям типа «А» и «TXT». Получаем из них данные. Если данные получены, то печатаем строку с сервером, с которого получали данные и результат запроса, в данном случае BAD, так как искомый адрес найден, а значит, содержится в черном списке. Далее определяем паттерн для регулярного выражения, с помощью которого будем искать ссылки в ответах TXT. Если же ссылок найдено не будет, так и пишем, что ничего нет. И напоследок добавляем найденное в словарь. То есть, добавляем имя сервера, где искали, результат ответа по записи типа А, и найденную или не найденную ссылку на подробности.

Python:
            resp = resolv.resolve(req, 'A')
            resp_txt = resolv.resolve(req, 'TXT')
            print(Fore.RED + f'{serv.strip():30}: [BAD]')
            pattern = '(?:https?:\/\/)?(?:[\w\.]+)\.(?:[a-z]{2,6}\.?)(?:\/[\w\.]*)*\/?'
            find = re.findall(pattern, str(resp_txt[0]))
            if len(find) == 0:
                find = ['No address']
            bad_dict.update({serv.strip(): f'{resp[0]} {find[0]}'})

Обрабатываем исключения, которые будут возникать при совершении запросов. Их может быть больше, но в основном, чаще всего, проявляются эти. Первое исключение возникает тогда, когда не найдено записей. Оно нам будет нужно, для того, чтобы печатать, что на данном сервере с вашим адресом все ОК. А следующие два, это исключение возникающее, когда время ожидания ответа истекает и когда нет ответа от сервера.

Python:
        except dns.resolver.NXDOMAIN:
            print(Fore.GREEN + f'{serv.strip():30}: [OK]')
        except (dns.resolver.LifetimeTimeout, dns.resolver.NoAnswer):
            continue

Дальше обрабатываем то, что получили. Если словарь, в который мы добавляли найденные записи пустой, радостно сообщаем об этом пользователю. Если же нет, печатаем его содержимое.

Python:
    if len(bad_dict) > 0:
        len_str = len(f'IP-АДРЕС: "{ip.upper()}" ОБНАРУЖЕН В ЧЕРНЫХ СПИСКАХ')
        print(Fore.BLUE + f'\nIP-АДРЕС: {ip.upper()} ОБНАРУЖЕН В ЧЕРНЫХ СПИСКАХ\n{"*"*len_str}')
        for bad in bad_dict:
            print(f' - {Fore.YELLOW + bad:30} : {Fore.RESET +bad_dict[bad]}')
    else:
        print(Fore.GREEN + '\n[+] IP-адрес в черных списках не обнаружен')


Получение данных от пользователя

И еще одна функция, main(). Здесь, для начала мы сообщаем пользователю его внешний IP-адрес. Затем запрашиваем адрес или домен для проверки. Ну и если он желает выйти, он может ввести х. После получаем ip-адрес, так как пользователь может ввести имя домена. В случае ошибки получения адреса завершаем работу скрипта. После того, как адрес получен, запускаем проверку, не является ли полученный адрес за Cloudflare. И если да, то предлагаем пользователю альтернативу, проверять или не проверять. В случае проверки запускаем функцию. Если нет, просто возвращаемся в пользовательское меню. Ну и если адреса за Cloudflare не обнаружено, просто запускаем функцию проверки.

Python:
def main():
    print(Fore.CYAN + f'\n- Ваш внешний IP-адрес: {public_ip()}')
    addr_input = input('- Введите IP-адрес или домен для проверки\n  Для выхода введите "x"\n  >>> ')
    if addr_input.lower() == "x":
        exit(0)
    ip = ''
    try:
        ip = socket.gethostbyname(addr_input)
    except socket.gaierror:
        print(Fore.RED + '\n - Не удалось получить IP-адрес')
        exit(0)
    if cloudf_detect(ip):
        print(Fore.RED + f'\n[!] ВНИМАНИЕ! Обнаружен адрес Cloudflare: {ip}')
    dns_bl_check(ip)

Сказать по правде, не особо задумывался о том, что надо проверять адреса в черных списках. Да, о проверке прокси в черных списках слышал, и даже проверял. Но вот чтобы адрес компьютера… и, как оказалось зря. К примеру, мой адрес засветился уже более чем в 5-ти листах. И не во всех случаях виноват я )), адрес то динамически. Тем более, есть там и записи, что адрес был частью ботнета. А я то думаю, почему иногда тот же Cloudflare меня тормозит и заставляет проходить проверки. А одна из причин может быть в этом.

Что же, на этом, пожалуй, все. Ниже приведу пример работы скрипта на скриншоте. А также полный его код.

screenshot2.png

screenshot1.png

Python:
# pip install dnspython
# pip install requests
# pip install colorama

import re
import socket
from ipaddress import ip_network, ip_address

import dns
from colorama import Fore
from colorama import init
from dns import resolver
from requests import get, exceptions

init()


# проверка адреса в диапазоне cloudflare
def ip_in_range(ip, addr):
    if ip_address(ip) in ip_network(addr):
        return True
    return False


# загрузка списка диапазонов адресов cloudflare
# запуск функции проверки ip-адреса
def cloudf_detect(ip):
    list_addr = ["104.16.0.0/12"]

    url = 'https://www.cloudflare.com/ips-v4'
    req = get(url=url)

    for adr in req.text.split("\n"):
        list_addr.append(adr)

    for addr in list_addr:
        detect = ip_in_range(ip, addr)
        if detect:
            return True
    return False


# получение внешнего ip-адреса компьютера
def public_ip():
    try:
        return get('https://api.ipify.org/').text
    except exceptions.ConnectionError:
        return '127.0.0.1'


# загрузка списка DNSBL, получение информации о наличии адреса в списках
def dns_bl_check(ip):
    print(Fore.YELLOW + '\n- Проверка черных списков\n')
    bad_dict = dict()
    req = get('https://raw.githubusercontent.com/evgenycc/DNSBL-list/main/DNSBL')
    read = req.text.splitlines()
    for serv in read:
        req = f"{'.'.join(reversed(ip.split('.')))}.{serv.strip()}"
        try:
            resolv = dns.resolver.Resolver()
            resolv.timeout = 5
            resolv.lifetime = 5
            resp = resolv.resolve(req, 'A')
            resp_txt = resolv.resolve(req, 'TXT')
            print(Fore.RED + f'{serv.strip():30}: [BAD]')
            pattern = '(?:https?:\/\/)?(?:[\w\.]+)\.(?:[a-z]{2,6}\.?)(?:\/[\w\.]*)*\/?'
            find = re.findall(pattern, str(resp_txt[0]))
            if len(find) == 0:
                find = ['No address']
            bad_dict.update({serv.strip(): f'{resp[0]} {find[0]}'})
        except dns.resolver.NXDOMAIN:
            print(Fore.GREEN + f'{serv.strip():30}: [OK]')
        except (dns.resolver.LifetimeTimeout, dns.resolver.NoAnswer):
            continue
    if len(bad_dict) > 0:
        len_str = len(f'IP-АДРЕС: "{ip.upper()}" ОБНАРУЖЕН В ЧЕРНЫХ СПИСКАХ')
        print(Fore.BLUE + f'\nIP-АДРЕС: {ip.upper()} ОБНАРУЖЕН В ЧЕРНЫХ СПИСКАХ\n{"*"*len_str}')
        for bad in bad_dict:
            print(f' - {Fore.YELLOW + bad:30} : {Fore.RESET +bad_dict[bad]}')
    else:
        print(Fore.GREEN + '\n[+] IP-адрес в черных списках не обнаружен')


# получение пользовательского ввода
# запуск проверки на принадлежность адреса к cloudflare
# запуск проверки адреса в DNSBL
def main():
    print(Fore.CYAN + f'\n- Ваш внешний IP-адрес: {public_ip()}')
    addr_input = input('- Введите IP-адрес или домен для проверки\n  Для выхода введите "x"\n  >>> ')
    if addr_input.lower() == "x":
        exit(0)
    ip = ''
    try:
        ip = socket.gethostbyname(addr_input)
    except socket.gaierror:
        print(Fore.RED + '\n - Не удалось получить IP-адрес')
        exit(0)
    if cloudf_detect(ip):
        print(Fore.RED + f'\n[!] ВНИМАНИЕ! Обнаружен адрес Cloudflare: {ip}')
    dns_bl_check(ip)


if __name__ == "__main__":
    main()

Спасибо за внимание. Надеюсь, что эта информация будет вам полезна
 
  • Нравится
Реакции: Сергей Сталь
Мы в соцсетях: