Skip to content

Commit

Permalink
black formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
MattKeeley committed Aug 11, 2024
1 parent 5bf8a19 commit eeff00f
Show file tree
Hide file tree
Showing 8 changed files with 235 additions and 119 deletions.
2 changes: 1 addition & 1 deletion modules/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
# modules/__init__.py
# modules/__init__.py
15 changes: 9 additions & 6 deletions modules/bimi.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import dns.resolver


class BIMI:
def __init__(self, domain, dns_server=None):
self.domain = domain
Expand All @@ -22,9 +23,9 @@ def get_bimi_record(self):
resolver = dns.resolver.Resolver()
if self.dns_server:
resolver.nameservers = [self.dns_server]
bimi = resolver.resolve(f'default._bimi.{self.domain}', 'TXT')
bimi = resolver.resolve(f"default._bimi.{self.domain}", "TXT")
for record in bimi:
if 'v=BIMI' in str(record):
if "v=BIMI" in str(record):
return record
return None
except Exception:
Expand Down Expand Up @@ -53,7 +54,9 @@ def get_bimi_details(self):
return self.version, self.location, self.authority

def __str__(self):
return (f"BIMI Record: {self.bimi_record}\n"
f"Version: {self.version}\n"
f"Location: {self.location}\n"
f"Authority: {self.authority}")
return (
f"BIMI Record: {self.bimi_record}\n"
f"Version: {self.version}\n"
f"Location: {self.location}\n"
f"Authority: {self.authority}"
)
21 changes: 12 additions & 9 deletions modules/dmarc.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import dns.resolver
import tldextract


class DMARC:
def __init__(self, domain, dns_server=None):
self.domain = domain
Expand All @@ -28,7 +29,7 @@ def get_dmarc_record(self):
subdomain = tldextract.extract(self.domain).registered_domain
if subdomain != self.domain:
return self.get_dmarc_record_for_domain(subdomain)

return self.get_dmarc_record_for_domain(self.domain)

def get_dmarc_record_for_domain(self, domain):
Expand All @@ -42,7 +43,7 @@ def get_dmarc_record_for_domain(self, domain):

for dns_data in dmarc:
if "DMARC1" in str(dns_data):
return str(dns_data).replace('"', '')
return str(dns_data).replace('"', "")
return None

def get_dmarc_policy(self):
Expand Down Expand Up @@ -82,10 +83,12 @@ def get_dmarc_aggregate_reports(self):
return None

def __str__(self):
return (f"DMARC Record: {self.dmarc_record}\n"
f"Policy: {self.policy}\n"
f"Pct: {self.pct}\n"
f"ASPF: {self.aspf}\n"
f"Subdomain Policy: {self.sp}\n"
f"Forensic Report URI: {self.fo}\n"
f"Aggregate Report URI: {self.rua}")
return (
f"DMARC Record: {self.dmarc_record}\n"
f"Policy: {self.policy}\n"
f"Pct: {self.pct}\n"
f"ASPF: {self.aspf}\n"
f"Subdomain Policy: {self.sp}\n"
f"Forensic Report URI: {self.fo}\n"
f"Aggregate Report URI: {self.rua}"
)
23 changes: 13 additions & 10 deletions modules/dns.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .dmarc import DMARC
from .bimi import BIMI


class DNS:
def __init__(self, domain):
self.domain = domain
Expand All @@ -21,9 +22,9 @@ def __init__(self, domain):
def get_soa_record(self):
"""Sets the SOA record and DNS server of a given domain."""
resolver = dns.resolver.Resolver()
resolver.nameservers = ['1.1.1.1']
resolver.nameservers = ["1.1.1.1"]
try:
query = resolver.resolve(self.domain, 'SOA')
query = resolver.resolve(self.domain, "SOA")
except Exception:
return
if query:
Expand All @@ -44,15 +45,15 @@ def get_dns_server(self):
if self.spf_record.spf_record and self.dmarc_record.dmarc_record:
return

for ip_address in ['1.1.1.1', '8.8.8.8', '9.9.9.9']:
for ip_address in ["1.1.1.1", "8.8.8.8", "9.9.9.9"]:
self.spf_record = SPF(self.domain, ip_address)
self.dmarc_record = DMARC(self.domain, ip_address)
self.bimi_record = BIMI(self.domain, ip_address)
if self.spf_record.spf_record and self.dmarc_record.dmarc_record:
self.dns_server = ip_address
return

self.dns_server = '1.1.1.1'
self.dns_server = "1.1.1.1"

def get_txt_record(self, record_type):
"""Returns the TXT record of a given type for the domain."""
Expand All @@ -65,9 +66,11 @@ def get_txt_record(self, record_type):
return None

def __str__(self):
return (f"Domain: {self.domain}\n"
f"SOA Record: {self.soa_record}\n"
f"DNS Server: {self.dns_server}\n"
f"SPF Record: {self.spf_record.spf_record}\n"
f"DMARC Record: {self.dmarc_record.dmarc_record}\n"
f"BIMI Record: {self.bimi_record.bimi_record}")
return (
f"Domain: {self.domain}\n"
f"SOA Record: {self.soa_record}\n"
f"DNS Server: {self.dns_server}\n"
f"SPF Record: {self.spf_record.spf_record}\n"
f"DMARC Record: {self.dmarc_record.dmarc_record}\n"
f"BIMI Record: {self.bimi_record.bimi_record}"
)
93 changes: 65 additions & 28 deletions modules/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
# Initialize colorama
init()


def output_message(symbol, message, level="info"):
"""Generic function to print messages with different colors and symbols based on the level."""
colors = {
Expand All @@ -15,11 +16,12 @@ def output_message(symbol, message, level="info"):
"bad": Fore.RED + Style.BRIGHT,
"indifferent": Fore.BLUE + Style.BRIGHT,
"error": Fore.RED + Style.BRIGHT + "!!! ",
"info": Fore.WHITE + Style.BRIGHT
"info": Fore.WHITE + Style.BRIGHT,
}
color = colors.get(level, Fore.WHITE + Style.BRIGHT)
print(color + f"{symbol} {message}" + Style.RESET_ALL)


def write_to_excel(data, file_name="output.xlsx"):
"""Writes a DataFrame of data to an Excel file, appending if the file exists."""
if os.path.exists(file_name) and os.path.getsize(file_name) > 0:
Expand All @@ -30,27 +32,28 @@ def write_to_excel(data, file_name="output.xlsx"):
else:
pd.DataFrame(data).to_excel(file_name, index=False)


def printer(**kwargs):
"""Utility function to print the results of DMARC, SPF, and BIMI checks in the original format."""
domain = kwargs.get('DOMAIN')
subdomain = kwargs.get('DOMAIN_TYPE') == 'subdomain'
dns_server = kwargs.get('DNS_SERVER')
spf_record = kwargs.get('SPF')
spf_all = kwargs.get('SPF_MULTIPLE_ALLS')
spf_dns_query_count = kwargs.get('SPF_NUM_DNS_QUERIES')
dmarc_record = kwargs.get('DMARC')
p = kwargs.get('DMARC_POLICY')
pct = kwargs.get('DMARC_PCT')
aspf = kwargs.get('DMARC_ASPF')
sp = kwargs.get('DMARC_SP')
fo = kwargs.get('DMARC_FORENSIC_REPORT')
rua = kwargs.get('DMARC_AGGREGATE_REPORT')
bimi_record = kwargs.get('BIMI_RECORD')
vbimi = kwargs.get('BIMI_VERSION')
location = kwargs.get('BIMI_LOCATION')
authority = kwargs.get('BIMI_AUTHORITY')
spoofable = kwargs.get('SPOOFING_POSSIBLE')
spoofing_type = kwargs.get('SPOOFING_TYPE')
domain = kwargs.get("DOMAIN")
subdomain = kwargs.get("DOMAIN_TYPE") == "subdomain"
dns_server = kwargs.get("DNS_SERVER")
spf_record = kwargs.get("SPF")
spf_all = kwargs.get("SPF_MULTIPLE_ALLS")
spf_dns_query_count = kwargs.get("SPF_NUM_DNS_QUERIES")
dmarc_record = kwargs.get("DMARC")
p = kwargs.get("DMARC_POLICY")
pct = kwargs.get("DMARC_PCT")
aspf = kwargs.get("DMARC_ASPF")
sp = kwargs.get("DMARC_SP")
fo = kwargs.get("DMARC_FORENSIC_REPORT")
rua = kwargs.get("DMARC_AGGREGATE_REPORT")
bimi_record = kwargs.get("BIMI_RECORD")
vbimi = kwargs.get("BIMI_VERSION")
location = kwargs.get("BIMI_LOCATION")
authority = kwargs.get("BIMI_AUTHORITY")
spoofable = kwargs.get("SPOOFING_POSSIBLE")
spoofing_type = kwargs.get("SPOOFING_TYPE")

output_message("[*]", f"Domain: {domain}", "indifferent")
output_message("[*]", f"Is subdomain: {subdomain}", "indifferent")
Expand All @@ -61,21 +64,55 @@ def printer(**kwargs):
if spf_all is None:
output_message("[*]", "SPF does not contain an `All` item.", "info")
elif spf_all == "2many":
output_message("[?]", "SPF record contains multiple `All` items.", "warning")
output_message(
"[?]", "SPF record contains multiple `All` items.", "warning"
)
else:
output_message("[*]", f"SPF all record: {spf_all}", "info")
output_message("[*]", f"SPF DNS query count: {spf_dns_query_count}" if spf_dns_query_count <= 10 else f"Too many SPF DNS query lookups {spf_dns_query_count}.", "info")
output_message(
"[*]",
f"SPF DNS query count: {spf_dns_query_count}"
if spf_dns_query_count <= 10
else f"Too many SPF DNS query lookups {spf_dns_query_count}.",
"info",
)
else:
output_message("[?]", "No SPF record found.", "warning")

if dmarc_record:
output_message("[*]", f"DMARC record: {dmarc_record}", "info")
output_message("[*]", f"Found DMARC policy: {p}" if p else "No DMARC policy found.", "info")
output_message("[*]", f"Found DMARC pct: {pct}" if pct else "No DMARC pct found.", "info")
output_message("[*]", f"Found DMARC aspf: {aspf}" if aspf else "No DMARC aspf found.", "info")
output_message("[*]", f"Found DMARC subdomain policy: {sp}" if sp else "No DMARC subdomain policy found.", "info")
output_message("[*]", f"Forensics reports will be sent: {fo}" if fo else "No DMARC forensics report location found.", "indifferent")
output_message("[*]", f"Aggregate reports will be sent to: {rua}" if rua else "No DMARC aggregate report location found.", "indifferent")
output_message(
"[*]", f"Found DMARC policy: {p}" if p else "No DMARC policy found.", "info"
)
output_message(
"[*]", f"Found DMARC pct: {pct}" if pct else "No DMARC pct found.", "info"
)
output_message(
"[*]",
f"Found DMARC aspf: {aspf}" if aspf else "No DMARC aspf found.",
"info",
)
output_message(
"[*]",
f"Found DMARC subdomain policy: {sp}"
if sp
else "No DMARC subdomain policy found.",
"info",
)
output_message(
"[*]",
f"Forensics reports will be sent: {fo}"
if fo
else "No DMARC forensics report location found.",
"indifferent",
)
output_message(
"[*]",
f"Aggregate reports will be sent to: {rua}"
if rua
else "No DMARC aggregate report location found.",
"indifferent",
)
else:
output_message("[?]", "No DMARC record found.", "warning")

Expand Down
48 changes: 26 additions & 22 deletions modules/spf.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import dns.resolver
import re


class SPF:
def __init__(self, domain, dns_server=None):
self.domain = domain
Expand All @@ -23,30 +24,30 @@ def get_spf_record(self, domain=None):
if not domain:
domain = self.domain
resolver = dns.resolver.Resolver()
resolver.nameservers = [self.dns_server, '1.1.1.1', '8.8.8.8']
query_result = resolver.resolve(domain, 'TXT')
resolver.nameservers = [self.dns_server, "1.1.1.1", "8.8.8.8"]
query_result = resolver.resolve(domain, "TXT")
for record in query_result:
if 'spf1' in str(record):
spf_record = str(record).replace('"', '')
if "spf1" in str(record):
spf_record = str(record).replace('"', "")
return spf_record
return None
except Exception:
return None

def get_spf_all_string(self):
"""Returns the string value of the 'all' mechanism in the SPF record."""

spf_record = self.spf_record
visited_domains = set()

while spf_record:
all_matches = re.findall(r'[-~?+]all', spf_record)
all_matches = re.findall(r"[-~?+]all", spf_record)
if len(all_matches) == 1:
return all_matches[0]
elif len(all_matches) > 1:
return '2many'
return "2many"

redirect_match = re.search(r'redirect=([\w.-]+)', spf_record)
redirect_match = re.search(r"redirect=([\w.-]+)", spf_record)
if redirect_match:
redirect_domain = redirect_match.group(1)
if redirect_domain in visited_domains:
Expand All @@ -57,42 +58,45 @@ def get_spf_all_string(self):
break

return None

def get_spf_dns_queries(self):
"""Returns the number of dns queries, redirects, and other mechanisms in the SPF record for a given domain."""

def count_dns_queries(spf_record):
count = 0
for item in spf_record.split():
if item.startswith("include:") or item.startswith("redirect="):
if item.startswith("include:"):
url = item.replace('include:', '')
url = item.replace("include:", "")
elif item.startswith("redirect="):
url = item.replace('redirect=', '')
url = item.replace("redirect=", "")

count += 1
try:
# Recursively fetch and count dns queries or redirects in the SPF record of the referenced domain
answers = dns.resolver.resolve(url, 'TXT')
answers = dns.resolver.resolve(url, "TXT")
for rdata in answers:
for txt_string in rdata.strings:
txt_record = txt_string.decode('utf-8')
if txt_record.startswith('v=spf1'):
txt_record = txt_string.decode("utf-8")
if txt_record.startswith("v=spf1"):
count += count_dns_queries(txt_record)
except Exception:
pass

# Count occurrences of 'a', 'mx', 'ptr', and 'exists' mechanisms
count += len(re.findall(r"[ ,+]a[ ,:]", spf_record))
count += len(re.findall(r"[ ,+]mx[ ,:]", spf_record))
count += len(re.findall(r"[ ]ptr[ ]", spf_record))
count += len(re.findall(r"exists[:]", spf_record))

return count

return count_dns_queries(self.spf_record)

def __str__(self):
return (f"SPF Record: {self.spf_record}\n"
f"All Mechanism: {self.all_mechanism}\n"
f"DNS Query Count: {self.spf_dns_query_count}\n"
f"Too Many DNS Queries: {self.too_many_dns_queries}")
return (
f"SPF Record: {self.spf_record}\n"
f"All Mechanism: {self.all_mechanism}\n"
f"DNS Query Count: {self.spf_dns_query_count}\n"
f"Too Many DNS Queries: {self.too_many_dns_queries}"
)
Loading

0 comments on commit eeff00f

Please sign in to comment.