diff --git a/libs/bimi.py b/libs/bimi.py new file mode 100755 index 0000000..59b53e6 --- /dev/null +++ b/libs/bimi.py @@ -0,0 +1,46 @@ +import dns.resolver + + +def get_bimi_record(domain, dns_server): + """Returns the BIMI record for a given domain.""" + try: + resolver = dns.resolver.Resolver() + resolver.nameservers = [dns_server, '1.1.1.1', '8.8.8.8'] + query_result = resolver.resolve('default._bimi.' + domain, 'TXT') + for record in query_result: + if 'v=BIMI' in str(record): + return record + return None + except: + return None + + +def get_bimi_details(bimi_record): + """Returns a tuple containing policy, pct, aspf, subdomain policy, + forensic report uri, and aggregate report uri from a BIMI record""" + version = get_bimi_version(bimi_record) + location = get_bimi_location(bimi_record) + authority = get_bimi_authority(bimi_record) + return version, location, authority + + +def get_bimi_version(bimi_record): + """Returns the version value from a BIMI record.""" + if "v=" in str(bimi_record): + return str(bimi_record).split("v=")[1].split(";")[0] + else: + return None + +def get_bimi_location(bimi_record): + """Returns the location value from a BIMI record.""" + if "l=" in str(bimi_record): + return str(bimi_record).split("l=")[1].split(";")[0] + else: + return None + +def get_bimi_authority(bimi_record): + """Returns the authority value from a BIMI record.""" + if "a=" in str(bimi_record): + return str(bimi_record).split("a=")[1].split(";")[0] + else: + return None \ No newline at end of file diff --git a/libs/dns.py b/libs/dns.py old mode 100644 new mode 100755 index 5e13f94..0fe823f --- a/libs/dns.py +++ b/libs/dns.py @@ -1,6 +1,6 @@ import dns.resolver import socket -from . import spf, dmarc +from . import spf, dmarc, bimi def get_soa_record(domain): @@ -24,25 +24,27 @@ def get_soa_record(domain): def get_dns_server(domain): """Finds the DNS server that serves the domain and returns it, along with any SPF or DMARC records.""" SOA = get_soa_record(domain) - spf_record = dmarc_record = partial_spf_record = partial_dmarc_record = None + spf_record = dmarc_record = partial_spf_record = partial_dmarc_record = bimi_record = None if SOA: spf_record = spf.get_spf_record(domain, SOA) dmarc_record = dmarc.get_dmarc_record(domain, SOA) + bimi_record = bimi.get_bimi_record(domain, SOA) if spf_record and dmarc_record: - return SOA, spf_record, dmarc_record + return SOA, spf_record, dmarc_record, bimi_record for ip_address in ['1.1.1.1', '8.8.8.8', '9.9.9.9']: spf_record = spf.get_spf_record(domain, ip_address) dmarc_record = dmarc.get_dmarc_record(domain, ip_address) + bimi_record = bimi.get_bimi_record(domain, SOA) if spf_record and dmarc_record: - return ip_address, spf_record, dmarc_record + return ip_address, spf_record, dmarc_record, bimi_record if spf_record: partial_spf_record = spf_record if dmarc_record: partial_dmarc_record = dmarc_record - return '1.1.1.1', partial_spf_record, partial_dmarc_record + return '1.1.1.1', partial_spf_record, partial_dmarc_record, bimi_record def get_txt_record(domain, record_type): diff --git a/libs/logic.py b/libs/logic.py old mode 100644 new mode 100755 diff --git a/libs/report.py b/libs/report.py old mode 100644 new mode 100755 index b73a091..d88d17c --- a/libs/report.py +++ b/libs/report.py @@ -48,7 +48,7 @@ def write_to_excel(data): df.to_excel(file_name, index=False) -def printer(domain, subdomain, dns_server, spf_record, spf_all, spf_includes, dmarc_record, p, pct, aspf, sp, fo, rua, spoofable): +def printer(domain, subdomain, dns_server, spf_record, spf_all, spf_includes, dmarc_record, p, pct, aspf, sp, fo, rua, bimi_record, vbimi, location, authority, spoofable): """This function is a utility function that takes in various parameters related to the results of DMARC and SPF checks and outputs the results to the console in a human-readable format. @@ -96,11 +96,24 @@ def printer(domain, subdomain, dns_server, spf_record, spf_all, spf_includes, dm f"Aggregate reports will be sent to: {rua}" if rua else "No DMARC aggregate report location found.") else: output_warning("No DMARC record found.") - + + if(bimi_record): + output_info(f"BIMI record : {bimi_record}") + output_info(f"BIMI version : {vbimi}") + output_info(f"BIMI location : {location}") + output_info(f"BIMI authority : {authority}") + if spoofable in [0, 1, 2, 3, 4, 5, 6, 7, 8]: if spoofable == 8: output_bad("Spoofing not possible for " + domain) else: - output_good("Spoofing possible for " + domain if spoofable == 0 else "Subdomain spoofing possible for " + domain if spoofable == 1 else "Organizational domain spoofing possible for " + domain if spoofable == 2 else "Spoofing might be possible for " + domain if spoofable == 3 else "Spoofing might be possible (Mailbox dependant) for " + - domain if spoofable == 4 else "Organizational domain spoofing may be possible for " + domain if spoofable == 5 else "Subdomain spoofing might be possible (Mailbox dependant) for " + domain if spoofable == 6 else "Subdomain spoofing might be possible (Mailbox dependant) for " + domain if spoofable == 7 else "") + output_good("Spoofing possible for " + domain + if spoofable == 0 else "Subdomain spoofing possible for " + domain + if spoofable == 1 else "Organizational domain spoofing possible for " + domain + if spoofable == 2 else "Spoofing might be possible for " + domain + if spoofable == 3 else "Spoofing might be possible (Mailbox dependant) for " + domain + if spoofable == 4 else "Organizational domain spoofing may be possible for " + domain + if spoofable == 5 else "Subdomain spoofing might be possible (Mailbox dependant) for " + domain + if spoofable == 6 else "Subdomain spoofing might be possible (Mailbox dependant) for " + domain + if spoofable == 7 else "") print() # padding diff --git a/spoofy.py b/spoofy.py old mode 100644 new mode 100755 index 0bbd33d..accf68f --- a/spoofy.py +++ b/spoofy.py @@ -3,7 +3,7 @@ import tldextract import threading import os -from libs import dmarc, dns, logic, spf, report +from libs import bimi, dmarc, dns, logic, spf, report print_lock = threading.Lock() @@ -14,29 +14,45 @@ def process_domain(domain, output): and outputs the results to the console or an Excel file.""" try: dns_server = spf_record = dmarc_record = None - spf_all = spf_includes = p = pct = aspf = sp = fo = rua = None + spf_all = spf_includes = p = pct = aspf = sp = fo = rua = vbimi = location = authority = None subdomain = bool(tldextract.extract(domain).subdomain) with print_lock: - dns_server, spf_record, dmarc_record = dns.get_dns_server(domain) + dns_server, spf_record, dmarc_record, bimi_record = dns.get_dns_server(domain) if spf_record: spf_all = spf.get_spf_all_string(spf_record) spf_includes = spf.get_spf_includes(domain) if dmarc_record: p, pct, aspf, sp, fo, rua = dmarc.get_dmarc_details(dmarc_record) + if bimi_record: + vbimi, location, authority = bimi.get_bimi_details(bimi_record) spoofable = logic.is_spoofable( domain, p, aspf, spf_record, spf_all, spf_includes, sp, pct) if output == "xls": with print_lock: - data = [{'DOMAIN': domain, 'SUBDOMAIN': subdomain, 'SPF': spf_record, 'SPF MULTIPLE ALLS': spf_all, - 'SPF TOO MANY INCLUDES': spf_includes, 'DMARC': dmarc_record, 'DMARC POLICY': p, - 'DMARC PCT': pct, 'DMARC ASPF': aspf, 'DMARC SP': sp, 'DMARC FORENSIC REPORT': fo, - 'DMARC AGGREGATE REPORT': rua, 'SPOOFING POSSIBLE': spoofable}] + data = [{'DOMAIN': domain, + 'SUBDOMAIN': subdomain, + 'SPF': spf_record, + 'SPF MULTIPLE ALLS': spf_all, + 'SPF TOO MANY INCLUDES': spf_includes, + 'DMARC': dmarc_record, + 'DMARC POLICY': p, + 'DMARC PCT': pct, + 'DMARC ASPF': aspf, + 'DMARC SP': sp, + 'DMARC FORENSIC REPORT': fo, + 'DMARC AGGREGATE REPORT': rua, + 'BIMI_RECORD': bimi_record, + 'BIMI_VERSION': vbimi, + 'BIMI_LOCATION': location, + 'BIMI_AUTHORITY': authority, + 'SPOOFING POSSIBLE': spoofable}] report.write_to_excel(data) else: with print_lock: report.printer(domain, subdomain, dns_server, spf_record, spf_all, spf_includes, dmarc_record, p, pct, aspf, - sp, fo, rua, spoofable) - except: + sp, fo, rua, bimi_record, vbimi, location, authority, spoofable) + except Exception as e: + raise e with print_lock: report.output_error( f"Domain {domain} is offline or format cannot be interpreted.") diff --git a/test.py b/test.py old mode 100644 new mode 100755