Skip to content
Snippets Groups Projects
Commit 41dd4381 authored by Tobias Dussa's avatar Tobias Dussa
Browse files

Added support for attachments and for no-webhook operation. Also minor bugfixes.

parent 8688d3ed
No related branches found
No related tags found
No related merge requests found
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from email.generator import Generator
from email.message import EmailMessage from email.message import EmailMessage
from email.utils import formatdate from email.utils import formatdate
import argparse import argparse
...@@ -27,6 +28,7 @@ import getpass ...@@ -27,6 +28,7 @@ import getpass
import hashlib import hashlib
import jinja2 import jinja2
import os import os
import magic
import requests import requests
import secrets import secrets
import smtplib import smtplib
...@@ -43,17 +45,19 @@ if sys.version_info < (3, 8): ...@@ -43,17 +45,19 @@ if sys.version_info < (3, 8):
parser.register('action', 'extend', ExtendAction) parser.register('action', 'extend', ExtendAction)
# Parse args # Parse args
parser.add_argument('-a', '--attach', dest='attach', default=[], nargs=1, action='extend', help='add attachment(s) to mail (default: None)')
parser.add_argument('-b', '--basedir', dest='basedir', default='Mails', help='base directory for all output (default: "Mails")') parser.add_argument('-b', '--basedir', dest='basedir', default='Mails', help='base directory for all output (default: "Mails")')
parser.add_argument('-B', '--bcc', dest='bcc', default=[], nargs=1, action='extend', help='additional mail recipient to bcc (default: None)') parser.add_argument('-B', '--bcc', dest='bcc', default=[], nargs=1, action='extend', help='additional mail recipient(s) to bcc (default: None)')
parser.add_argument('-c', '--campaign', dest='campaign', default='Test', help='campaign name (default: "Test")') parser.add_argument('-c', '--campaign', dest='campaign', default='Test', help='campaign name (default: "Test")')
parser.add_argument('-C', '--cc', dest='cc', default=[], nargs=1, action='extend', help='additional mail recipient(s) to cc (default: None)')
parser.add_argument('-d', '--dry-run', dest='dryrun', default=False, action='store_true', help='dry run -- do not actually send mails or create targets (default: False)') parser.add_argument('-d', '--dry-run', dest='dryrun', default=False, action='store_true', help='dry run -- do not actually send mails or create targets (default: False)')
parser.add_argument('-f', '--from', dest='sender', default='Nobody <nobody@example.com>', help='sender mail address (default: "Nobody <nobody@example.com>"; implies dry-run if not set)') parser.add_argument('-f', '--from', dest='sender', default='Nobody <nobody@example.com>', help='sender mail address (default: "Nobody <nobody@example.com>"; implies dry-run if not set)')
parser.add_argument('-F', '--force', dest='force', default=False, action='store_true', help='force insecure login without TLS/SSL (default: False)') parser.add_argument('-F', '--force', dest='force', default=False, action='store_true', help='force insecure login without TLS/SSL (default: False)')
parser.add_argument('-H', '--hashstring', dest='hashstring', default='{salt}{campaign}{infix}-{site}', help='string to be hashed for the URL (default: "{salt}{campaign}{infix}-{site}" where "{salt}" is a random string)') parser.add_argument('-H', '--hashstring', dest='hashstring', default='{salt}{campaign}{infix}-{site}', help='string to be hashed for the URL (default: "{salt}{campaign}{infix}-{site}" where "{salt}" is a random string)')
parser.add_argument('-i', '--input', dest='input', default='{basedir}/{campaign}/Input{infix}.lst', help='input file (default: "{basedir}/{campaign}/Input{infix}.lst")') parser.add_argument('-i', '--input', dest='input', default='{basedir}/{campaign}/Input{infix}.lst', help='input file (default: "{basedir}/{campaign}/Input{infix}.lst")')
parser.add_argument('-o', '--output', dest='output', default='{basedir}/{campaign}/{site}/Mail{infix}.txt', help='output file name template (default: "{basedir}/{campaign}/{site}/Mail{infix}.txt")') parser.add_argument('-o', '--output', dest='output', default='{basedir}/{campaign}/{site}/Mail{infix}.txt', help='output file name template (default: "{basedir}/{campaign}/{site}/Mail{infix}.txt")')
parser.add_argument( '--salt', dest='salt', default=None, help='salt to use for hashing (default: random 8-byte hex string)')
parser.add_argument('-R', '--reply-to', dest='replyto', default=None, help='reply-to mail address (default: None)') parser.add_argument('-R', '--reply-to', dest='replyto', default=None, help='reply-to mail address (default: None)')
parser.add_argument( '--salt', dest='salt', default=None, help='salt to use for hashing (default: random 8-byte hex string)')
parser.add_argument('-s', '--subject', dest='subject', default='Security Challenge for {site} -- {campaign}{infix}', help='mail subject (default: "Security Challenge Message -- {campaign}{infix}")') parser.add_argument('-s', '--subject', dest='subject', default='Security Challenge for {site} -- {campaign}{infix}', help='mail subject (default: "Security Challenge Message -- {campaign}{infix}")')
parser.add_argument('-S', '--smtpserver', dest='smtpserver', default='localhost', help='SMTP server to use (default: "localhost"); port can be specified with "<host>:<port>" notation and takes precedence over implied ports and port specification') parser.add_argument('-S', '--smtpserver', dest='smtpserver', default='localhost', help='SMTP server to use (default: "localhost"); port can be specified with "<host>:<port>" notation and takes precedence over implied ports and port specification')
parser.add_argument( '--smtpport', dest='smtpport', default=0, type=int, help='SMTP port to use (default: 25); takes precedence over implied ports') parser.add_argument( '--smtpport', dest='smtpport', default=0, type=int, help='SMTP port to use (default: 25); takes precedence over implied ports')
...@@ -63,8 +67,9 @@ parser.add_argument( '--starttls', dest='starttls', default=False, acti ...@@ -63,8 +67,9 @@ parser.add_argument( '--starttls', dest='starttls', default=False, acti
parser.add_argument('-t', '--template', dest='template', default='{basedir}/{campaign}/Mail.template', help='mail template file (default: "{basedir}/{campaign}/Mail.template")') parser.add_argument('-t', '--template', dest='template', default='{basedir}/{campaign}/Mail.template', help='mail template file (default: "{basedir}/{campaign}/Mail.template")')
parser.add_argument('-T', '--to', dest='to', default='{firstname} {lastname} <{email}>', help='recipient mail address (default: "{firstname} {lastname} <{email}>")') parser.add_argument('-T', '--to', dest='to', default='{firstname} {lastname} <{email}>', help='recipient mail address (default: "{firstname} {lastname} <{email}>")')
parser.add_argument('-u', '--url', dest='url', default='{webserver}/{campaign}{infix}-{hash}', help='URL template to use (default: "{webserver}/{campaign}{infix}-{hash}"') parser.add_argument('-u', '--url', dest='url', default='{webserver}/{campaign}{infix}-{hash}', help='URL template to use (default: "{webserver}/{campaign}{infix}-{hash}"')
parser.add_argument('-U', '--createurl', dest='createurl', default='{webserver}/{campaign}{infix}-{hash}/create', help='URL template to use for creation URL (default: "{webserver}/{campaign}{infix}-{hash}/create"')
parser.add_argument('-v', '--verbose', dest='verbose', default=False, action='store_true', help='increase verbosity') parser.add_argument('-v', '--verbose', dest='verbose', default=False, action='store_true', help='increase verbosity')
parser.add_argument('-w', '--webserver', dest='webserver', default='https://challenge.example.com', help='web server to use (default: "https://challenge.example.com"; implies dry-run if not set') parser.add_argument('-w', '--webserver', dest='webserver', default='https://challenge.example.com', help='web server to use (default: "https://challenge.example.com"; implies dry-run if not set; to suppress web-hook calling, set to empty string ""')
parser.add_argument('infix', default='', nargs='?', help='infix for ID purposes, default empty') parser.add_argument('infix', default='', nargs='?', help='infix for ID purposes, default empty')
args = parser.parse_args() args = parser.parse_args()
...@@ -110,10 +115,14 @@ if not args.salt: ...@@ -110,10 +115,14 @@ if not args.salt:
args.salt = secrets.token_hex(8) args.salt = secrets.token_hex(8)
if args.verbose: if args.verbose:
if args.attach:
print(f'Using "{", ".join(args.attach)}" as mail attachment(s).')
print(f'Using "{args.basedir}" as base directory.') print(f'Using "{args.basedir}" as base directory.')
if args.bcc: if args.bcc:
print(f'Using "{", ".join(args.bcc)}" as silent mail recipients.') print(f'Using "{", ".join(args.bcc)}" as bcc mail recipient(s).')
print(f'Using "{args.campaign}" as campaign.') print(f'Using "{args.campaign}" as campaign.')
if args.cc:
print(f'Using "{", ".join(args.cc)}" as cc mail recipient(s).')
print(f'Using "{args.sender}" as sender mail address.') print(f'Using "{args.sender}" as sender mail address.')
if args.replyto: if args.replyto:
print(f'Using "{args.replyto}" as reply-to mail address.') print(f'Using "{args.replyto}" as reply-to mail address.')
...@@ -125,6 +134,7 @@ if args.verbose: ...@@ -125,6 +134,7 @@ if args.verbose:
print(f'Using "{args.template.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt))}" as template file.') print(f'Using "{args.template.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt))}" as template file.')
print(f'Using "{args.to.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt))}" as recipient mail address.') print(f'Using "{args.to.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt))}" as recipient mail address.')
print(f'Using "{args.url.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt))}" as URL template.') print(f'Using "{args.url.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt))}" as URL template.')
print(f'Using "{args.createurl.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt))}" as URL template.')
print(f'Using "{args.webserver}" as web server.') print(f'Using "{args.webserver}" as web server.')
print(f'Using "{args.infix}" as infix.') print(f'Using "{args.infix}" as infix.')
print(f'Using "{args.smtpserver}" as SMTP server.') print(f'Using "{args.smtpserver}" as SMTP server.')
...@@ -158,44 +168,65 @@ def toHex(serial): ...@@ -158,44 +168,65 @@ def toHex(serial):
def createMail(data): def createMail(data):
output = args.output.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash'], URL=data['URL'])) output = args.output.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash'], URL=data['URL']))
directory = os.path.dirname(output) directory = os.path.dirname(output)
print(f'Writing mail to {output}')
if not os.path.exists(directory): # Generate email body and attachments
os.makedirs(directory)
with open(output, 'wb') as mailfile:
template.stream(data).dump(mailfile, encoding='utf-8')
message = EmailMessage() message = EmailMessage()
with open(output, 'r') as mailfile: message.set_content(template.render(data))
message.set_content(mailfile.read()) for attach in args.attach:
with open(attach, 'rb') as attachment:
message.add_attachment(attachment.read(), *magic.from_file(attach, mime=True).split('/'))
message['Date'] = formatdate(localtime=True) message['Date'] = formatdate(localtime=True)
message['Subject'] = args.subject.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash'], URL=data['URL'])) message['Subject'] = args.subject.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash'], URL=data['URL']))
message['From'] = args.sender.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash'], URL=data['URL'])) message['From'] = args.sender.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash'], URL=data['URL']))
message['Reply-To'] = args.replyto.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash'], URL=data['URL'])) if args.replyto:
message['Reply-To'] = args.replyto.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash'], URL=data['URL']))
message['To'] = args.to.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash'], URL=data['URL'])) message['To'] = args.to.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash'], URL=data['URL']))
message['Bcc'] = ', '.join(args.bcc).format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash'], URL=data['URL'])) if args.cc:
message['Cc'] = ', '.join(args.cc).format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash'], URL=data['URL']))
if args.bcc:
message['Bcc'] = ', '.join(args.bcc).format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash'], URL=data['URL']))
# Record mail
print(f'Writing mail to {output}')
if not os.path.exists(directory):
os.makedirs(directory)
with open(output, 'w') as mailfile:
Generator(mailfile).flatten(message)
if not args.dryrun: if not args.dryrun:
# Actually send the mail
print(f'Sending mail to {message["To"]}')
if message['Cc']:
print(f'... and cc to {message["Cc"]}')
if message['Bcc']: if message['Bcc']:
print(f'Sending mail to {message["To"]} (and {message["Bcc"]})') print(f'... and bcc to {message["Bcc"]}')
else: try:
print(f'Sending mail to {message["To"]}') if args.starttls:
if args.starttls: with smtplib.SMTP(args.smtpserver, args.smtpport) as smtp:
with smtplib.SMTP(args.smtpserver, args.smtpport) as smtp: smtp.starttls()
smtp.starttls() smtp.login(args.smtpuser, args.smtppass)
smtp.login(args.smtpuser, args.smtppass) smtp.send_message(message)
smtp.send_message(message) elif args.smtpuser or args.smtppass:
elif args.smtpuser or args.smtppass: with smtplib.SMTP_SSL(args.smtpserver, args.smtpport) as smtp:
with smtplib.SMTP_SSL(args.smtpserver, args.smtpport) as smtp: smtp.login(args.smtpuser, args.smtppass)
smtp.login(args.smtpuser, args.smtppass) smtp.send_message(message)
smtp.send_message(message) else:
else: with smtplib.SMTP(args.smtpserver, args.smtpport) as smtp:
with smtplib.SMTP(args.smtpserver, args.smtpport) as smtp: smtp.send_message(message)
smtp.send_message(message) except:
print(f'Creating target at {data["URL"]}/create') print(f'ERROR sending the mail!')
requests.get(f'{data["URL"]}/create')
# Call the webhook if webserver is specified
if args.webserver:
print(f'Creating target at {data["CreateURL"]}')
requests.get(f'{data["CreateURL"]}')
def generateHashAndURL(data): def generateHashAndURL(data):
data['hash'] = hashlib.sha256(args.hashstring.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'])).encode('utf-8')).hexdigest() data['hash'] = hashlib.sha256(args.hashstring.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'])).encode('utf-8')).hexdigest()
data['URL'] = args.url.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash'])) data['URL'] = args.url.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash']))
data['CreateURL'] = args.createurl.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash']))
with open(args.input.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt)), 'r') as inputfile: with open(args.input.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt)), 'r') as inputfile:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment