Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding BIMI record retrival #22

Merged
merged 3 commits into from
Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions libs/bimi.py
Original file line number Diff line number Diff line change
@@ -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
12 changes: 7 additions & 5 deletions libs/dns.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import dns.resolver
import socket
from . import spf, dmarc
from . import spf, dmarc, bimi


def get_soa_record(domain):
Expand All @@ -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):
Expand Down
Empty file modified libs/logic.py
100644 → 100755
Empty file.
21 changes: 17 additions & 4 deletions libs/report.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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
34 changes: 25 additions & 9 deletions spoofy.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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.")
Expand Down
Empty file modified test.py
100644 → 100755
Empty file.