Skip to content
Open
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,6 @@ dmypy.json

# Pyre type checker
.pyre/

# temporary input file for testing DKIM
dom.txt
133 changes: 133 additions & 0 deletions modules/dkim.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import re
import dns.resolver


class DKIM:
def __init__(self, domain, dns_server=None):
self.domain = domain
self.dns_server = dns_server
self.dkim_record = self.get_dkim_record()
# self.dkim_query = None
self.combined_txt_records = ''
self.dkim_result = None

def get_dkim_record(self):
"""Returns the DKIM record for a given domain."""
self.combined_txt_records = ''

selectors = [
'selector',
'selector1',
'selector2',
'default',
'dkim',
'mail',
's1',
's2',
's3',
'key1',
'key2',
'key3',
'k1',
'k2',
'k3',
'zoho',
'google',
'googlemail',
'protonmail',
'fm1',
'fm2',
'fm3',
'mandrill',
'sendgrid',
's1024',
'm1',
'm2',
'ms',
'amazonses',
'zendesk1',
'zendesk2',
'everlytickey1',
'everlytickey2',
'litesrv',
'sig1',
'sm',
'ctct1',
'ctct2',
'spop1024',
'dk',
'a1',
'aweber_key_a',
'aweber_key_b',
'aweber_key_c',
'cm',
'clab1',
'dkim1024',
'e2ma-k1',
'e2ma-k2',
'e2ma-k3',
'sable',
'hs1',
'hs2',
'kl',
'kl2',
'mailjet',
'mailpoet1',
'mailpoet2',
'm101',
'm102',
'ecm1',
'nce2048',
'smtp',
'class',
'smtpapi',
'domk',
'smtpout',
'authsmtp',
'proddkim',
'testdkim',
'primary',
'ses',
'yousendit',
'ed-dkim',
'publickey',
'sasl',
'qcdkim',
'x',
'm',
'mikd',
'private',
'ei',
'spop',
'spop1024',
'mxvault',
'krs',
'mailo',
'pic',
'mta',
'email',
'acdkim1'
]

for selector in selectors:
try:
resolver = dns.resolver.Resolver()

# set OpenDNS for TXT/CNAME query
resolver.nameservers = ["208.67.222.222"]
self.dkim_result = resolver.resolve(f"{selector}._domainkey.{self.domain}", "TXT")

if self.dkim_result != None:
if self.dkim_result.response.answer != None: # dkim_result.canonical_name not empty
for record in self.dkim_result.chaining_result.answer:
trim_record = str(record.strings[0][:128])+"...(trimmed)"
self.combined_txt_records += f"[*] {selector}._domainkey.{self.domain} -> {trim_record}" + "\r\n"
else:
continue
except:
continue

if self.combined_txt_records != '':
return self.combined_txt_records
else:
return None
4 changes: 4 additions & 0 deletions modules/dns.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from .spf import SPF
from .dmarc import DMARC
from .bimi import BIMI
from .dkim import DKIM


class DNS:
Expand All @@ -14,6 +15,7 @@ def __init__(self, domain):
self.dns_server = None
self.spf_record = None
self.dmarc_record = None
self.dkim_record = None
self.bimi_record = None

self.get_soa_record()
Expand Down Expand Up @@ -48,6 +50,7 @@ def get_dns_server(self):
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.dkim_record = DKIM(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
Expand All @@ -72,5 +75,6 @@ def __str__(self):
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"DKIM Record: {self.dkim_record.dkim_record}\n"
f"BIMI Record: {self.bimi_record.bimi_record}"
)
6 changes: 6 additions & 0 deletions modules/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def printer(**kwargs):
sp = kwargs.get("DMARC_SP")
fo = kwargs.get("DMARC_FORENSIC_REPORT")
rua = kwargs.get("DMARC_AGGREGATE_REPORT")
dkim_record = kwargs.get("DKIM")
bimi_record = kwargs.get("BIMI_RECORD")
vbimi = kwargs.get("BIMI_VERSION")
location = kwargs.get("BIMI_LOCATION")
Expand Down Expand Up @@ -116,6 +117,11 @@ def printer(**kwargs):
else:
output_message("[?]", "No DMARC record found.", "warning")

if dkim_record:
output_message("[*]", f"DKIM selectors: \r\n{dkim_record}", "info")
else:
output_message("[?]", f"No known DKIM selectors enumerated on {domain}.", "warning")

if bimi_record:
output_message("[*]", f"BIMI record: {bimi_record}", "info")
output_message("[*]", f"BIMI version: {vbimi}", "info")
Expand Down
9 changes: 7 additions & 2 deletions spoofy.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@
from modules.spf import SPF
from modules.dmarc import DMARC
from modules.bimi import BIMI
from modules.dkim import DKIM
from modules.spoofing import Spoofing
from modules import report

print_lock = threading.Lock()


def process_domain(domain):
"""Process a domain to gather DNS, SPF, DMARC, and BIMI records, and evaluate spoofing potential."""
"""Process a domain to gather DNS, SPF, DMARC, BIMI records, enumerate known DKIM selectors, and evaluate spoofing potential."""
dns_info = DNS(domain)
spf = SPF(domain, dns_info.dns_server)
dmarc = DMARC(domain, dns_info.dns_server)
dkim = DKIM(domain, dns_info.dns_server)
bimi_info = BIMI(domain, dns_info.dns_server)

spf_record = spf.spf_record
Expand All @@ -34,6 +36,8 @@ def process_domain(domain):
dmarc_fo = dmarc.fo
dmarc_rua = dmarc.rua

dkim_record = dkim.dkim_record

bimi_record = bimi_info.bimi_record
bimi_version = bimi_info.version
bimi_location = bimi_info.location
Expand Down Expand Up @@ -70,6 +74,7 @@ def process_domain(domain):
"DMARC_SP": dmarc_sp,
"DMARC_FORENSIC_REPORT": dmarc_fo,
"DMARC_AGGREGATE_REPORT": dmarc_rua,
"DKIM": dkim_record,
"BIMI_RECORD": bimi_record,
"BIMI_VERSION": bimi_version,
"BIMI_LOCATION": bimi_location,
Expand Down Expand Up @@ -97,7 +102,7 @@ def worker(domain_queue, print_lock, output, results):

def main():
parser = argparse.ArgumentParser(
description="Process domains to gather DNS, SPF, DMARC, and BIMI records."
description="Process domains to gather DNS, SPF, DMARC, BIMI records and known DKIM selectors."
)
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("-d", type=str, help="Single domain to process.")
Expand Down