From 1e14402a92d044be2dfb07c91f9792d243bebc4c Mon Sep 17 00:00:00 2001 From: Marc van der Wal Date: Wed, 25 Oct 2023 15:50:24 +0200 Subject: [PATCH] =?UTF-8?q?send=5Femail.py=E2=80=AF:=20ajouter=20une=20sor?= =?UTF-8?q?tie=20JSON?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Faire un sorte que le script puisse fonctionner en mode non interactif : dans ce cas, sa sortie sur la console est du JSON. Cela permet ensuite à une éventuelle API REST de facilement se pluguer dessus. --- attacker/scripts/send_email.py | 110 +++++++++++++++++++++++++-------- 1 file changed, 83 insertions(+), 27 deletions(-) diff --git a/attacker/scripts/send_email.py b/attacker/scripts/send_email.py index 5bf0c23..275feed 100755 --- a/attacker/scripts/send_email.py +++ b/attacker/scripts/send_email.py @@ -1,8 +1,10 @@ #!/usr/bin/env python3 +from argparse import ArgumentParser from email.message import EmailMessage import email.utils import importlib.resources +import json import pathlib import re import smtplib @@ -61,15 +63,18 @@ custom_theme = Theme({ "data_display_name": "medium_purple1", "comment": "#777777", "failure": "bold red", - "success": "green" + "success": "green", + "text": "default" }) console = Console(highlighter=None, theme=custom_theme) class SMTP(smtplib.SMTP): - def __init__(self, **kwargs): + def __init__(self, output, **kwargs): super().__init__(**kwargs) self.highlighter = SMTPHighlighter() + self.log = []; + self.output = output def trace(self, s, direction): if direction not in ['in', 'out']: @@ -80,26 +85,32 @@ class SMTP(smtplib.SMTP): if isinstance(s, bytes): s = s.decode('utf-8') - lines = [self.highlighter(rich.text.Text(line)) for line in s.splitlines()] + + lines = [{'class': 'text', 'text': text} for text in s.splitlines()] + + # lines = [self.highlighter(rich.text.Text(line)) for line in s.splitlines()] if len(lines) > max_lines: suppressed_count = len(lines[max_lines:-1]) - suppressed_text_obj = rich.text.Text(f"({suppressed_count} ") if suppressed_count > 1: - suppressed_text_obj.append("lignes omises") + suppressed_text = f"{suppressed_count} lignes omises" else: - suppressed_text_obj.append("ligne omise") - suppressed_text_obj.append(")") - suppressed_text_obj.stylize("comment") + suppressed_text = f"{suppressed_count} ligne omise" + suppressed_obj = {'class': 'comment', 'text': suppressed_text} - lines = lines[:max_lines] + [suppressed_text_obj] + lines[-1:] + lines = lines[:max_lines] + [suppressed_obj] + lines[-1:] - for i, l in enumerate(lines): - prefix = arrows[direction] if i == 0 else " " - console.print(f" {prefix} ", end=None) - console.print(l) + # Print the text on the console + if self.output: + for i, l in enumerate(lines): + prefix = arrows[direction] if i == 0 else " " + console.print(f" {prefix} ", end=None) + console.print(l['text'], style=l['class']) - if direction == 'in': - console.print() + if direction == 'in': + console.print() + + # Save it internally too + self.log.append({'direction': direction, 'lines': lines}) def putcmd(self, cmd, args=''): super().putcmd(cmd.upper(), args) @@ -119,7 +130,9 @@ class SMTP(smtplib.SMTP): return errcode, errmsg def connect(self, *args, **kwargs): - console.print(" * Connexion établie au serveur", style="comment") + self.log.append({'direction': 'comment', + 'lines': [{'class': 'comment', + 'text': 'Connexion établie au serveur'}]}) return super().connect(*args, **kwargs) def ehlo(self, *args, **kwargs): @@ -218,21 +231,20 @@ def ask_template(): -def send_email(helo, envelope_from, email): +def send_email(helo, envelope_from, email, output=True): data = email.as_string().encode('utf-8') - try: - with SMTP(local_hostname=helo) as smtp: + with SMTP(local_hostname=helo, output=output) as smtp: + try: smtp.connect(VICTIM_MX) smtp.sendmail(envelope_from, VICTIM_ADDRESS, data) - except smtplib.SMTPException: - console.print(" [failure]✘[/] Message [failure]rejeté[/] par le serveur SMTP") - except: - raise - else: - console.print(" [success]✔[/] Message [success]accepté[/] par le serveur SMTP") + return {'outcome': 'success', 'log': smtp.log} + except smtplib.SMTPException: + return {'outcome': 'failure', 'log': smtp.log} + except: + raise -def main(): +def interactive_main(): try: console.print( rich.panel.Panel( @@ -256,7 +268,12 @@ def main(): email = generate_email(template) signed_email = add_dkim_signature(email) - send_email(helo, envelope_from, signed_email) + results = send_email(helo, envelope_from, signed_email, output=True) + if results['outcome'] == 'success': + console.print(" [success]✔[/] Message [success]accepté[/] par le serveur SMTP") + elif results['outcome'] == 'failure': + console.print(" [failure]✘[/] Message [failure]rejeté[/] par le serveur SMTP") + except KeyboardInterrupt: console.print() pass @@ -264,5 +281,44 @@ def main(): raise + +def main(): + parser = ArgumentParser() + parser.add_argument('--non-interactive', default=False, action='store_true') + parser.add_argument('--get-config', default=False, action='store_true') + parser.add_argument('--template') + parser.add_argument('--replace-rfc5321-mail-from', default=False, action='store_true') + parser.add_argument('--helo', default=DEFAULT_HELO) + + args = parser.parse_args() + + if args.non_interactive: + if args.get_config: + config = { + 'default_helo': DEFAULT_HELO, + 'my_mail_from': ATTACKER_MAIL_FROM, + 'templates': [{'id': t.name, + 'name': t.nice_name, + 'from_name': re.match('(.*) <(.*)>', t.msg['From']).group(1), + 'from_address': re.match('(.*) <(.*)>', t.msg['From']).group(2)} + for t in spoof_templates.spoof_templates()] + } + + print(json.dumps(config)) + else: + templates = {t.name: t for t in spoof_templates.spoof_templates()} + template = templates[args.template] + rfc5322from, = re.match('.* <(.*)>', template.msg['From']).groups() + if args.replace_rfc5321_mail_from: + envelope_from = ATTACKER_MAIL_FROM + else: + envelope_from = rfc5322from + + email = generate_email(template) + results = send_email(args.helo, envelope_from, email, output=False) + print(json.dumps(results)) + else: + interactive_main() + if __name__ == '__main__': main()