From 4da1d803430af71ea6e9e556bbf1721c1347a625 Mon Sep 17 00:00:00 2001 From: m0xiaoxi Date: Wed, 15 Dec 2021 21:35:44 +0800 Subject: [PATCH] feat: fix some bugs --- config.py | 4 +- config/example.config.yaml | 40 ++++++++------- core/sender.py | 66 +++++++++++++------------ core/util.py | 6 +-- spoofing.py | 99 ++++++++++++++++++++++++++------------ 5 files changed, 130 insertions(+), 85 deletions(-) diff --git a/config.py b/config.py index 9123b1d..5b041c0 100755 --- a/config.py +++ b/config.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -import os,json +import os, json from core.util import init_log BASE_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -11,6 +11,4 @@ with open(RULE_PATH, 'r') as f: CONFIG_RULES = json.load(f) -DEFAULT_EMAIL = 'default@mail.spoofing.com' - logger = init_log(LOG_FILE) diff --git a/config/example.config.yaml b/config/example.config.yaml index ed21ed4..be8de3d 100644 --- a/config/example.config.yaml +++ b/config/example.config.yaml @@ -1,28 +1,34 @@ -share_mode: +default: # Set some Global default values + subject: "Template subject" + body: "test" + + +target: #target victim default: - username: xxxx@mails.tsinghua.edu.cn # username via SMTP login - password: xxxxxx # password via SMTP login - host: mails.tsinghua.edu.cn # SMTP server + username: 'nislemail@gmail.com' + host: gmail.com # Optional, Target email service domain, e.g. admin@163.com ==> 163.com port: 25 # Optional, default 25 use_tls: False # Optional, default False use_ssl: False # Optional, default False debug_level: False # Optional, default False - gmail.com: - username: xxx@gmail.com - password: xxxx - host: smtp.gmail.com - port: 25 - -direct_mode: +smtp_user: default: - host: 163.com # Target email service domain, e.g. admin@163.com ==> 163.com + username: hellworld@gmail.com # username via SMTP login + password: abcdefg # password via SMTP login + host: mails.tsinghua.edu.cn # SMTP server port: 25 # Optional, default 25 use_tls: False # Optional, default False use_ssl: False # Optional, default False debug_level: False # Optional, default False + gmail.com: + username: xxx@gmail.com + password: xxxx + host: smtp.gmail.com + port: 25 + attack: default: @@ -42,7 +48,7 @@ attack: helo: autoencode: False - # A1 attack + # A1 attack A1: mail_from: "test@mail.spoofing.com" subject: "[Warning] Maybe you are vulnerable to the A1 attack!" @@ -81,7 +87,7 @@ attack: # A4 attack A4: mime_from: "admin@mail.spoofing.com" - extra_headers: {"From": "nislemail@spoofing.com"} + extra_headers: { "From": "nislemail@spoofing.com" } subject: "[Warning] Maybe you are vulnerable to the A4 attack!" body: "A4: Multiple From Headers." description: "A4: Multiple From Headers." @@ -90,7 +96,7 @@ attack: # A5 attack A5: mime_from: ", " - extra_headers: {"Sender": "nislemail@163.com"} + extra_headers: { "Sender": "nislemail@163.com" } subject: "[Warning] Maybe you are vulnerable to the A5 attack!" body: "A5: Multiple From Headers." description: "A5: Multiple From Headers." @@ -169,7 +175,5 @@ attack: description: "A14: Right-to-left Override Attack in domain." defense: "You should reject emails which contain these special characters in the sender address or add a warning on UI." -global_parameters: - subject: "Template subject" - mail_to: "nislemail@163.com" # Change receiveUser to what you like to test. + diff --git a/core/sender.py b/core/sender.py index c3a4642..1a2d8bf 100644 --- a/core/sender.py +++ b/core/sender.py @@ -1,8 +1,8 @@ import smtplib import smtplib import time -from config import * -from core.util import * +from config import logger +from core.util import string_types, text_type, query_mx_record from email import charset from email.encoders import encode_base64 from email.mime.base import MIMEBase @@ -19,12 +19,12 @@ def prepare_message(message, sender): if sender.mode == 'share': message.mime_from = sender.username else: - message.mime_from = DEFAULT_EMAIL + message.mime_from = 'default@mail.spoofing.com' if message.mail_from is None: if sender.mode == 'share': message.mail_from = sender.username else: - message.mail_from = DEFAULT_EMAIL + message.mail_from = 'default@mail.spoofing.com' if message.mail_to is None: message.mail_to = message.to_addrs() assert message.mail_to is not None @@ -82,18 +82,17 @@ def connection(self): def show_status(self): status = """ ------------------------------------------------------------------- -Sender config: -Mode: {} -Host: {} -Port: {} -Username: {} -Password: {} -Use_tls: {} -Use_ssl: {} -Debug_level: {} ------------------------------------------------------------------- - """.format(self.mode,self.host,self.port,self.username, self.password,self.use_tls, self.use_ssl,self.debug_level) +Sender Config: + - mode: {} + - host: {} + - port: {} + - username: {} + - password: {} + - use_tls: {} + - use_ssl: {} + - debug_level: {} + """.format(self.mode, self.host, self.port, self.username, self.password, self.use_tls, self.use_ssl, + self.debug_level) logger.info(status) return status @@ -218,8 +217,8 @@ class Message(object): def __init__(self, subject=None, to=None, body=None, html=None, mime_from=None, cc=None, bcc=None, attachments=None, reply_to=None, date=None, charset='utf-8', - extra_headers=None, mail_options=None, rcpt_options=None, mail_to=None, mail_from=None, helo=None, - autoencode=None, defense=None, description=None): + extra_headers={}, mail_options=None, rcpt_options=None, mail_to=None, mail_from=None, helo=None, + autoencode=None, defense=None, description=None, sender=None): self.subject = subject self.body = body self.html = html @@ -241,6 +240,7 @@ def __init__(self, subject=None, to=None, body=None, html=None, self.mail_from = mail_from self.mail_to = mail_to or [] self.helo = helo + self.sender = sender # make message_id self.message_id = make_msgid(domain=self.msg_domain()) @@ -279,16 +279,13 @@ def validate(self): def show_status(self): status = """ ------------------------------------------------------------------- -Envelope: -Helo: {} -Mail From: {} -Mail To: {} - -Email Content: +Message Config: +- helo: {} +- mail from: {} +- mail to: {} +- email content: {} ------------------------------------------------------------------- - """.format(self.helo,self.mail_from,self.mail_to, self.as_string()) + """.format(self.helo, self.mail_from, self.mail_to, self.as_string()) logger.info(status) return status @@ -314,18 +311,20 @@ def as_string(self): msg.attach(alternative) msg['Subject'] = Header(self.subject, self.charset) - msg['From'] = self.mime_from if self.extra_headers: for key, value in self.extra_headers.items(): # msg[key] = value msg.add_header(key, value) - msg['To'] = ', '.join(self.to) msg['Date'] = formatdate(self.date, localtime=True) msg['Message-ID'] = self.message_id + msg['From'] = self.mime_from + msg['To'] = ', '.join(self.to) if self.cc: msg['Cc'] = ', '.join(self.cc) if self.reply_to: msg['Reply-To'] = self.reply_to + if self.sender: + msg['Sender'] = self.sender for attachment in self.attachments: f = MIMEBase(*attachment.content_type.split('/')) f.set_payload(attachment.data) @@ -344,13 +343,18 @@ def as_string(self): f.add_header(key, value) msg.attach(f) - # TODO: fix mime_from auto encoding + s = msg.as_string() - if not self.autoencode: + if not self.autoencode or 'raw' in self.extra_headers: headers = s.split('\n') for h in headers: + # fix mime_from auto encoding if h.startswith('From:'): s = s.replace(h, "From: {}".format(self.mime_from)) + # add raw data + elif h.startswith("raw:"): + # print(h) + s = s.replace(h, '{}'.format(self.extra_headers['raw'])) # # fix run fuzz_test # for k, v in iteritems(self.run_fuzz): diff --git a/core/util.py b/core/util.py index 8e59dc0..bb89850 100644 --- a/core/util.py +++ b/core/util.py @@ -57,10 +57,10 @@ def read_config(config_path): tmp = func_dict.get(function, functin_call_error)(value) data = data.replace(k, tmp) else: - data = data.replace(k, y['global_parameters'][t]) + data = data.replace(k, y['default'][t]) config = yaml.safe_load(data) attack = config['attack'] - tp = config['global_parameters'] + tp = config['default'] # Complement the default value in attack for i in iterkeys(attack): @@ -92,7 +92,7 @@ def init_log(filename): ) # formattler = '%(asctime)s %(pathname)-8s:%(lineno)d %(levelname)-8s %(message)s' # formattler = '%(levelname)-8s %(message)s' - formattler = '[%(levelname)-8s] [%(asctime)s] [%(filename)-8s:%(lineno)-3d] %(message)s' + formattler = '[%(levelname)-7s] [%(asctime)s] [%(filename)-8s:%(lineno)-3d] %(message)s' fmt = logging.Formatter(formattler) logger = logging.getLogger() coloredlogs.install( diff --git a/spoofing.py b/spoofing.py index 5ec5fce..9abee96 100644 --- a/spoofing.py +++ b/spoofing.py @@ -1,28 +1,28 @@ -from config import * -from core.util import * import sys import traceback -import re from optparse import OptionParser +from config import logger, CONFIG_PATH +from core.util import read_config, banner from core.sender import Sender, Message, prepare_message -#TODO add DKIM signer + +# TODO add DKIM signer def parse_options(): parser = OptionParser() - parser.add_option("-m", "--mode", dest="mode", default="s", choices=['s', 'd'], - help="The attack mode with spoofing email (s: Shared MTA, d: Direct MTA)") - parser.add_option("-t", "--target", dest="target", default="default", help="Select target under attack mode.") + parser.add_option("-m", "--mode", dest="mode", default='d', choices=['s', 'd'], + help="The attack mode (s: Shared MTA, d: Direct MTA)") + parser.add_option("-u", "--user", dest="user", default="default", + help="Select smtp_user, only used in Shared MTA mode.") + parser.add_option('-t', "--target", dest='target', default='default', help='Select target victim.') parser.add_option("-a", "--attack", dest='attack', default="default", - help="Select a specific attack method to send spoofing email.") - parser.add_option("--mail_from", dest='mail_from', default=None, - help='Set Mail From address manually. It will overwrite the settings in config.yaml') - parser.add_option("--mime_from", dest='mime_from', default=None, - help='Set Mime From address manually. It will overwrite the settings in config.yaml') - parser.add_option("--mail_to", dest='mail_to', default=None, - help='Set Mail to address manually. It will overwrite the settings in config.yaml') - parser.add_option("--mime_to", dest='mime_to', default=None, - help='Set Mime to address manually. It will overwrite the settings in config.yaml') - + help="Select one attack method to send spoofing emails.") + parser.add_option("-d", "--debug", dest="debug", action="store_true", help="Turn on debug mode.") + parser.add_option("--list", dest='list', action='store_true', help="list attack methods.") + # parser.add_option('-q', '--quite', action='store_false', dest='quite', help='don\'t print info log.') #TODO + parser.add_option("--mail_from", dest='mail_from', default=None, help='set mail_from address manually.') + parser.add_option("--mime_from", dest='mime_from', default=None, help='set mime_from address manually.') + parser.add_option("--mime_to", dest='mime_to', default=None, help='set mime_to address manually.') + parser.add_option("--mail_to", dest='mail_to', default=None, help='set mail_to address manually.') (options, args) = parser.parse_args() return options @@ -33,28 +33,47 @@ def run_error(errmsg): sys.exit() - def run(): options = parse_options() # config config = read_config(CONFIG_PATH) + check_configs(options, config) - if options.mode == "s": - target = config["share_mode"][options.target] - target["mode"] = "share" - mail = Sender(**target) - mail.show_status() + if options.list: + show_attacks(config['attack']) + return + demo = None + try: + victim = config['target'][options.target] + except Exception as e: + # Directly fill in the target address, i.e., test@qq.com + victim = config['target']['default'] + victim['username'] = options.target.split() + victim['host'] = options.target.split('@')[1].strip() + if options.mode == "s": + demo = config["smtp_user"][options.user] + demo["mode"] = "share" elif options.mode == 'd': - target = config['direct_mode'][options.target] - target["mode"] = "direct" - mail = Sender(**target) - mail.show_status() + demo = victim + demo["mode"] = "direct" else: - logger.error("Option.mode illegal!{}".format(options.mode)) - sys.exit() + errmsg = "Mode setting error! {}".format(options.mode) + run_error(errmsg) + + if options.debug: + demo['debug_level'] = options.debug + + mail = Sender(**demo) + mail.show_status() - m = config["attack"][options.attack] + try: + m = config["attack"][options.attack] + except Exception as e: + show_attacks(config["attack"]) + sys.exit() + if 'mail_to' not in m or not m['mail_to']: + m['mail_to'] = victim['username'] if options.mail_from: m['mail_from'] = options.mail_from if options.mail_to: @@ -70,6 +89,26 @@ def run(): logger.info("All Task Done! :)") +# TODO +def check_configs(options, config): + error = False + errmsg = 'config error' + if error: + run_error(errmsg) + + + + +def show_attacks(attacks): + logger.info("Name\t\t\tDescription\t") + logger.info("-" * 100) + for a in attacks: + description = attacks[a]['description'] if 'description' in attacks[a] else None + # defense = attacks[a]['defense'] if 'defense' in attacks[a] else None + logger.info("{}\t\t\t{}".format(a, description)) + logger.info("-" * 100) + + def main(): banner() try: