From 6c9241351bb014abbad54a91c00056642a7459a5 Mon Sep 17 00:00:00 2001 From: William DeFreese Date: Tue, 21 Apr 2015 19:43:23 -0400 Subject: [PATCH] Migrated to using published emailprotectionslib --- .gitignore | 2 + SimpleEmailSpoofer.py | 91 +++++++++++------------ libs/EmailProtectionsLib.py | 139 ------------------------------------ libs/PrettyOutput.py | 10 +-- requirements.txt | 3 + spoofcheck.py | 56 +++++++-------- 6 files changed, 85 insertions(+), 216 deletions(-) delete mode 100644 libs/EmailProtectionsLib.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore index 6d0c588..5882d3f 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,5 @@ docs/_build/ # PyBuilder target/ + +.idea diff --git a/SimpleEmailSpoofer.py b/SimpleEmailSpoofer.py index d8ed47e..c3b28e9 100755 --- a/SimpleEmailSpoofer.py +++ b/SimpleEmailSpoofer.py @@ -1,14 +1,18 @@ #! /usr/bin/env python -from libs.EmailProtectionsLib import * -from libs.PrettyOutput import * import re import smtplib import argparse -from email.MIMEMultipart import MIMEMultipart -from email.MIMEText import MIMEText +import emailprotectionslib.dmarc as dmarclib +import emailprotectionslib.spf as spflib + +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText + +from libs.PrettyOutput import * + def get_args(): parser = argparse.ArgumentParser() @@ -38,8 +42,9 @@ def get_args(): return parser.parse_args() + def get_ack(force): - info("To continue: [yes/no]") + output_info("To continue: [yes/no]") if force is False: yn = raw_input() if yn != "yes": @@ -47,11 +52,12 @@ def get_ack(force): else: return True elif force is True: - meh( "Forced yes") + output_indifferent( "Forced yes") return True else: raise TypeError("Passed in non-boolean") + if __name__ == "__main__": args = get_args() @@ -61,125 +67,122 @@ def get_ack(force): # Read email text into email_text if args.interactive_email: - info("Enter HTML email line by line") - info("Press CTRL+D to finish") + output_info("Enter HTML email line by line") + output_info("Press CTRL+D to finish") while True: try: line = raw_input("| ") email_text += line + "\n" except EOFError: - info("Email captured.") + output_info("Email captured.") break else: try: with open(args.filename, "r") as infile: - info("Reading " + args.filename + " as email file") + output_info("Reading " + args.filename + " as email file") email_text = infile.read() except: - error("Could not open file " + args.filename) + output_error("Could not open file " + args.filename) exit(-1) email_re = re.compile(".*@(.*\...*)") - from_domain = email_re.match(args.from_address).group(1) to_domain = email_re.match(args.to_address).group(1) - - info("Checking if from domain " + Style.BRIGHT + from_domain + Style.NORMAL + " is spoofable") - + output_info("Checking if from domain " + Style.BRIGHT + from_domain + Style.NORMAL + " is spoofable") if from_domain == "gmail.com": if to_domain == "gmail.com": - bad("You are trying to spoof from a gmail address to a gmail address.") - bad("The Gmail web application will display a warning message on your email.") + output_bad("You are trying to spoof from a gmail address to a gmail address.") + output_bad("The Gmail web application will display a warning message on your email.") if not get_ack(args.force): - bad("Exiting") + output_bad("Exiting") exit(1) else: - meh("You are trying to spoof from a gmail address.") - meh("If the domain you are sending to is controlled by Google Apps the web application will display a warning message on your email.") + output_indifferent("You are trying to spoof from a gmail address.") + output_indifferent("If the domain you are sending to is controlled by Google Apps the web application will display a warning message on your email.") if not get_ack(args.force): - bad("Exiting") + output_bad("Exiting") exit(1) if args.spoof_check: spoofable = False try: - spf = get_spf(from_domain) + spf = spflib.SpfRecord.from_domain(from_domain) if spf.all_string is not None and not (spf.all_string == "~all" or spf.all_string == "-all"): spoofable = True - except NoSpfRecordException: + except spflib.NoSpfRecordException: spoofable = True try: - dmarc = get_dmarc(from_domain) - info(str(dmarc)) + dmarc = dmarclib.DmarcRecord.from_domain(from_domain) + output_info(str(dmarc)) if dmarc.policy is None or not (dmarc.policy == "reject" or dmarc.policy == "quarantine"): spoofable = True if dmarc.pct is not None and dmarc.pct != str(100): - meh("DMARC pct is set to " + dmarc.pct +"% - Spoofing might be possible") + output_indifferent("DMARC pct is set to " + dmarc.pct + "% - Spoofing might be possible") if dmarc.rua is not None: - meh("Aggregate reports will be sent: " + dmarc.rua) + output_indifferent("Aggregate reports will be sent: " + dmarc.rua) if not get_ack(args.force): - bad("Exiting") + output_bad("Exiting") exit(1) if dmarc.ruf is not None: - meh("Forensics reports will be sent: " + dmarc.ruf) + output_indifferent("Forensics reports will be sent: " + dmarc.ruf) if not get_ack(args.force): - bad("Exiting") + output_bad("Exiting") exit(1) - except NoDmarcRecordException: + except dmarclib.NoDmarcRecordException: spoofable = True if not spoofable: - bad("From domain " + Style.BRIGHT + from_domain + Style.NORMAL + " is not spoofable.") + output_bad("From domain " + Style.BRIGHT + from_domain + Style.NORMAL + " is not spoofable.") if not args.force: - bad("Exiting. (-f to override)") + output_bad("Exiting. (-f to override)") exit(2) else: - meh("Overriding...") + output_indifferent("Overriding...") else: - good("From domain " + Style.BRIGHT + from_domain + Style.NORMAL + " is spoofable!") + output_good("From domain " + Style.BRIGHT + from_domain + Style.NORMAL + " is spoofable!") - info("Sending to " + args.to_address) + output_info("Sending to " + args.to_address) try: - info("Connecting to SMTP server at " + args.smtp_server + ":" + str(args.smtp_port)) + output_info("Connecting to SMTP server at " + args.smtp_server + ":" + str(args.smtp_port)) server = smtplib.SMTP(args.smtp_server, args.smtp_port) msg = MIMEMultipart("alternative") msg.set_charset("utf-8") if args.from_name is not None: - info("Setting From header to: " + args.from_name + "<" + args.from_address + ">") + output_info("Setting From header to: " + args.from_name + "<" + args.from_address + ">") msg["From"] = args.from_name + "<" + args.from_address + ">" else: - info("Setting From header to: " + args.from_address) + output_info("Setting From header to: " + args.from_address) msg["From"] = args.from_address if args.subject is not None: - info("Setting Subject header to: " + args.subject) + output_info("Setting Subject header to: " + args.subject) msg["Subject"] = args.subject msg.attach(MIMEText(email_text, 'html', 'utf-8')) - info("Send email?") + output_info("Send email?") if not get_ack(args.force): - bad("Exiting") + output_bad("Exiting") exit(1) server.sendmail(args.from_address, args.to_address, msg.as_string()) - good("Email Sent") + output_good("Email Sent") except smtplib.SMTPException as e: - error("Error: Could not send email to " + args.to_address ) + output_error("Error: Could not send email to " + args.to_address ) raise e diff --git a/libs/EmailProtectionsLib.py b/libs/EmailProtectionsLib.py deleted file mode 100644 index 7de498e..0000000 --- a/libs/EmailProtectionsLib.py +++ /dev/null @@ -1,139 +0,0 @@ -import re -import dns.resolver -import logging - -class NoSpfRecordException(Exception): - def __init__(self, value): - self.value = value - def __str__(self): - return repr(self.value) - -class NoDmarcRecordException(Exception): - def __init__(self, value): - self.value = value - def __str__(self): - return repr(self.value) - - -class SpfRecord(object): - def __init__(self, spf_string): - spf_item_regex = "(?:((?:\+|-|~)?(?:a|mx|ptr|include|ip4|ip6|exists|redirect|exp|all)(?:(?::|/)?(?:\S*))?) ?)" - spf_version_r = "^v=(spf.)" - - self.spf_string = spf_string - self.version = re.match(spf_version_r, spf_string).group(1) - self.items = re.findall(spf_item_regex, spf_string) - - self.all_string = None - - for item in self.items: - if re.match(".all", item): - self.all_string = item - redirect = re.match("redirect=(.*)", item) - if redirect is not None: - try: - spf_string = get_spf_string(redirect.group(1)) - self._process_redirect(spf_string) - except NoSpfRecordException as ex: - logging.exception(ex) - - def _process_redirect(self, spf_string): - spf_item_regex = "(?:((?:\+|-|~)?(?:a|mx|ptr|include|ip4|ip6|exists|redirect|exp|all)(?:(?::|/)?(?:\S*))?) ?)" - spf_version_r = "^v=(spf.)" - self.spf_string += " | " + spf_string - self.version = re.match(spf_version_r, spf_string).group(1) - self.items = re.findall(spf_item_regex, spf_string) - - for item in self.items: - if re.match(".all", item): - self.all_string = item - redirect = re.match("redirect=(.*)", item) - if redirect is not None: - try: - spf_string = get_spf_string(redirect.group(1)) - self._process_redirect(spf_string) - except NoSpfRecordException as ex: - logging.exception(ex) - - def __str__(self): - return self.spf_string - -class DmarcRecord(object): - def __init__(self, dmarc_string): - dmarc_regex = "(\w+)=(.*?)(?:; ?|$)" - - self.dmarc_string = dmarc_string - - items = re.findall(dmarc_regex, dmarc_string) - - self.version = None - self.policy = None - self.pct = None - self.rua = None - self.ruf = None - self.subdomain_policy = None - self.dkim_alignment = None - self.spf_alignment = None - - for item in items: - prepend = item[0] - if prepend == "v": - self.version = item[1] - elif prepend == "p": - self.policy = item[1] - elif prepend == "pct": - self.pct = item[1] - elif prepend == "rua": - self.rua = item[1] - elif prepend == "ruf": - self.ruf = item[1] - elif prepend == "sp": - self.subdomain_policy = item[1] - elif prepend == "adkim": - self.dkim_alignment = item[1] - elif prepend == "aspf": - self.spf_alignment = item[1] - - def __str__(self): - return self.dmarc_string - - -def get_spf(domain): - try: - spf_string = get_spf_string(domain) - return SpfRecord(spf_string) - except NoSpfRecordException as e: - raise - -def get_dmarc(domain): - try: - dmarc_string = get_dmarc_string(domain) - return DmarcRecord(dmarc_string) - except NoDmarcRecordException as e: - raise - -def get_spf_string(domain): - spf_re = re.compile('^"(v=spf.*)"') - try: - for answer in dns.resolver.query(domain, "TXT"): - match = spf_re.match(str(answer)) - if match is not None: - return match.group(1) - except dns.resolver.NoAnswer: - raise NoSpfRecordException("Domain " + domain + " did not respond to TXT query") - except: - raise NoSpfRecordException("Domain " + domain + " had a strange error") - raise NoSpfRecordException("Domain " + domain + " had no SPF record") - -def get_dmarc_string(domain): - dmarc_re = re.compile('^"(v=DMARC.*)"') - try: - for answer in dns.resolver.query("_dmarc." + domain, "TXT"): - match = dmarc_re.match(str(answer)) - if match is not None: - return match.group(1) - except dns.resolver.NoAnswer: - raise NoDmarcRecordException("Domain " + domain + " did not respond to TXT query") - except: - raise NoDmarcRecordException("Domain " + domain + " had a strange error") - raise NoDmarcRecordException("Domain " + domain + " had no DMARC record") diff --git a/libs/PrettyOutput.py b/libs/PrettyOutput.py index 61a7676..5801056 100644 --- a/libs/PrettyOutput.py +++ b/libs/PrettyOutput.py @@ -1,17 +1,17 @@ from colorama import Fore, Back, Style from colorama import init as color_init -def good(line): +def output_good(line): print Fore.GREEN + Style.BRIGHT + "[+]" + Style.RESET_ALL, line -def meh(line): +def output_indifferent(line): print Fore.BLUE + Style.BRIGHT + "[*]" + Style.RESET_ALL, line -def error(line): +def output_error(line): print Fore.RED + Style.BRIGHT + "[-] !!! " + Style.NORMAL, line, Style.BRIGHT + "!!!" -def bad(line): +def output_bad(line): print Fore.RED + Style.BRIGHT + "[-]" + Style.RESET_ALL, line -def info(line): +def output_info(line): print Fore.WHITE + Style.BRIGHT + "[*]" + Style.RESET_ALL, line diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2844691 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +colorama +dnspython +emailprotectionslib \ No newline at end of file diff --git a/spoofcheck.py b/spoofcheck.py index 3451ec7..2c646f7 100755 --- a/spoofcheck.py +++ b/spoofcheck.py @@ -1,12 +1,14 @@ #! /usr/bin/env python -from libs.EmailProtectionsLib import * -from libs.PrettyOutput import * +import sys -from colorama import Fore, Back, Style from colorama import init as color_init -import sys +import emailprotectionslib.dmarc as dmarclib +import emailprotectionslib.spf as spflib + +from libs.PrettyOutput import output_good, output_bad, \ + output_info, output_error, output_indifferent if __name__ == "__main__": color_init() @@ -16,58 +18,56 @@ domain = sys.argv[1] try: - spf = get_spf(domain) - info("Found SPF record:") - info(str(spf)) + spf = spflib.SpfRecord.from_domain(domain) + output_info("Found SPF record:") + output_info(str(spf)) if spf.all_string is not None: if spf.all_string == "~all" or spf.all_string == "-all": - meh("SPF record contains an All item: " + spf.all_string) + output_indifferent("SPF record contains an All item: " + spf.all_string) else: - good("SPF record All item is too weak: " + spf.all_string) + output_good("SPF record All item is too weak: " + spf.all_string) spoofable = True else: - good("SPF record has no All string") + output_good("SPF record has no All string") - except NoSpfRecordException: - good( domain + " has no SPF record!") + except spflib.NoSpfRecordException: + output_good( domain + " has no SPF record!") spoofable = True try: - dmarc = get_dmarc(domain) - info("Found DMARC record:") - info(str(dmarc)) + dmarc = dmarclib.DmarcRecord.from_domain(domain) + output_info("Found DMARC record:") + output_info(str(dmarc)) if dmarc.policy is not None: if not dmarc.policy == "reject" and not dmarc.policy == "quarantine": spoofable = True - good("DMARC policy set to " + dmarc.policy) + output_good("DMARC policy set to " + dmarc.policy) else: - bad("DMARC policy set to " + dmarc.policy) + output_bad("DMARC policy set to " + dmarc.policy) else: - good("DMARC record has no Policy") + output_good("DMARC record has no Policy") spoofable = True if dmarc.pct is not None and dmarc.pct != str(100): - meh("DMARC pct is set to " + dmarc.pct +"% - might be possible") + output_indifferent("DMARC pct is set to " + dmarc.pct + "% - might be possible") if dmarc.rua is not None: - meh("Aggregate reports will be sent: " + dmarc.rua) + output_indifferent("Aggregate reports will be sent: " + dmarc.rua) if dmarc.ruf is not None: - meh("Forensics reports will be sent: " + dmarc.ruf) + output_indifferent("Forensics reports will be sent: " + dmarc.ruf) - - except NoDmarcRecordException: - good( domain + " has no DMARC record!") + except dmarclib.NoDmarcRecordException: + output_good(domain + " has no DMARC record!") spoofable = True if spoofable: - good( "Spoofing possible for " + domain + "!") + output_good("Spoofing possible for " + domain + "!") else: - bad( "Spoofing not possible for " + domain) - + output_bad("Spoofing not possible for " + domain) except IndexError: - error("Usage: " + sys.argv[0] + " [DOMAIN]") \ No newline at end of file + output_error("Usage: " + sys.argv[0] + " [DOMAIN]") \ No newline at end of file