def showPrompt(self):
        if not self.interactive:
            return

        prompt = ""
        if CowrieConfig().has_option("honeypot", "prompt"):
            prompt = CowrieConfig().get("honeypot", "prompt")
            prompt += " "
        else:
            cwd = self.protocol.cwd
            homelen = len(self.protocol.user.avatar.home)
            if cwd == self.protocol.user.avatar.home:
                cwd = "~"
            elif (len(cwd) > (homelen + 1) and cwd[:(homelen + 1)]
                  == self.protocol.user.avatar.home + "/"):
                cwd = "~" + cwd[homelen:]

            # Example: [root@svr03 ~]#   (More of a "CentOS" feel)
            # Example: root@svr03:~#     (More of a "Debian" feel)
            prompt = f"{self.protocol.user.username}@{self.protocol.hostname}:{cwd}"
            if not self.protocol.user.uid:
                prompt += "# "  # "Root" user
            else:
                prompt += "$ "  # "Non-Root" user

        self.protocol.terminal.write(prompt.encode("ascii"))
        self.protocol.ps = (prompt.encode("ascii"), b"> ")
Example #2
0
    def showPrompt(self):
        if not self.interactive:
            return

        prompt = ''
        if CowrieConfig().has_option('honeypot', 'prompt'):
            prompt = CowrieConfig().get('honeypot', 'prompt')
            prompt += ' '
        else:
            cwd = self.protocol.cwd
            homelen = len(self.protocol.user.avatar.home)
            if cwd == self.protocol.user.avatar.home:
                cwd = '~'
            elif len(cwd) > (homelen + 1) and \
                    cwd[:(homelen + 1)] == self.protocol.user.avatar.home + '/':
                cwd = '~' + cwd[homelen:]

            # Example: [root@svr03 ~]#   (More of a "CentOS" feel)
            # Example: root@svr03:~#     (More of a "Debian" feel)
            prompt = '{0}@{1}:{2}'.format(self.protocol.user.username, self.protocol.hostname, cwd)
            if not self.protocol.user.uid:
                prompt += '# '  # "Root" user
            else:
                prompt += '$ '  # "Non-Root" user

        self.protocol.terminal.write(prompt.encode('ascii'))
        self.protocol.ps = (prompt.encode('ascii'), b'> ')
Example #3
0
class Output(cowrie.core.output.Output):
    """
    Splunk HEC output
    """
    def start(self):
        self.token = CowrieConfig().get('output_splunk', 'token')
        self.url = CowrieConfig().get('output_splunk', 'url').encode('utf8')
        self.index = CowrieConfig().get('output_splunk',
                                        'index',
                                        fallback=None)
        self.source = CowrieConfig().get('output_splunk',
                                         'source',
                                         fallback=None)
        self.sourcetype = CowrieConfig().get('output_splunk',
                                             'sourcetype',
                                             fallback=None)
        self.host = CowrieConfig().get('output_splunk', 'host', fallback=None)
        contextFactory = WebClientContextFactory()
        # contextFactory.method = TLSv1_METHOD
        self.agent = client.Agent(reactor, contextFactory)

    def stop(self):
        pass

    def write(self, logentry):
        for i in list(logentry.keys()):
            # Remove twisted 15 legacy keys
            if i.startswith('log_'):
                del logentry[i]

        splunkentry = {}
        if self.index:
            splunkentry["index"] = self.index
        if self.source:
            splunkentry["source"] = self.source
        if self.sourcetype:
            splunkentry["sourcetype"] = self.sourcetype
        if self.host:
            splunkentry["host"] = self.host
        else:
            splunkentry["host"] = logentry["sensor"]
        splunkentry["event"] = logentry
        self.postentry(splunkentry)

    def postentry(self, entry):
        """
        Send a JSON log entry to Splunk with Twisted
        """
        headers = http_headers.Headers({
            b'User-Agent': [b'Cowrie SSH Honeypot'],
            b'Authorization': [b"Splunk " + self.token.encode('utf8')],
            b'Content-Type': [b'application/json']
        })
        body = FileBodyProducer(BytesIO(json.dumps(entry).encode('utf8')))
        d = self.agent.request(b'POST', self.url, headers, body)

        def cbBody(body):
            return processResult(body)

        def cbPartial(failure):
            """
            Google HTTP Server does not set Content-Length. Twisted marks it as partial
            """
            failure.printTraceback()
            return processResult(failure.value.response)

        def cbResponse(response):
            if response.code == 200:
                return
            else:
                log.msg(
                    f"SplunkHEC response: {response.code} {response.phrase}")
                d = client.readBody(response)
                d.addCallback(cbBody)
                d.addErrback(cbPartial)
                return d

        def cbError(failure):
            failure.printTraceback()

        def processResult(result):
            j = json.loads(result)
            log.msg("SplunkHEC response: {}".format(j["text"]))

        d.addCallback(cbResponse)
        d.addErrback(cbError)
        return d
Example #4
0
class PoolServer(Protocol):
    def __init__(self, factory):
        self.factory = factory
        self.local_pool = CowrieConfig().get('proxy', 'pool',
                                             fallback='local') == 'local'
        self.pool_only = CowrieConfig().getboolean('backend_pool',
                                                   'pool_only',
                                                   fallback=False)
        self.use_nat = CowrieConfig().getboolean('backend_pool',
                                                 'use_nat',
                                                 fallback=True)

        if self.use_nat:
            self.nat_public_ip = CowrieConfig().get('backend_pool',
                                                    'nat_public_ip')

    def dataReceived(self, data):
        res_op = struct.unpack('!c', bytes([
            data[0]
        ]))[0]  # yes, this needs to be done to extract the op code correctly
        response = None

        if res_op == b'i':
            recv = struct.unpack('!II?', data[1:])

            # set the pool service thread configs
            max_vm = recv[0]
            vm_unused_timeout = recv[1]
            share_guests = recv[2]
            self.factory.pool_service.set_configs(max_vm, vm_unused_timeout,
                                                  share_guests)

            # respond with ok
            self.factory.initialised = True
            response = struct.pack('!cI', b'i', 0)

        elif res_op == b'r':
            # receives: attacker ip (used to serve same VM to same attacker)
            # sends: status code, guest_id, guest_ip, guest's ssh and telnet port

            recv = struct.unpack('!H', data[1:3])
            ip_len = recv[0]

            recv = struct.unpack(f'!{ip_len}s', data[3:])
            attacker_ip = recv[0].decode()

            log.msg(eventid='cowrie.backend_pool.server',
                    format='Requesting a VM for attacker @ %(attacker_ip)s',
                    attacker_ip=attacker_ip)

            try:
                guest_id, guest_ip, guest_snapshot = self.factory.pool_service.request_vm(
                    attacker_ip)
                log.msg(eventid='cowrie.backend_pool.server',
                        format='Providing VM id %(guest_id)s',
                        guest_id=guest_id)

                ssh_port = CowrieConfig().getint('backend_pool',
                                                 'guest_ssh_port',
                                                 fallback=22)
                telnet_port = CowrieConfig().getint('backend_pool',
                                                    'guest_telnet_port',
                                                    fallback=23)

                # after we receive ip and ports, expose ports in the pool's public interface
                # we use NAT if this pool is being run remotely, and if users choose so
                if not self.local_pool and self.use_nat or self.pool_only:
                    nat_ssh_port, nat_telnet_port = self.factory.nat.request_binding(
                        guest_id, guest_ip, ssh_port, telnet_port)

                    fmt = '!cIIH{}sHHH{}s'.format(len(self.nat_public_ip),
                                                  len(guest_snapshot))
                    response = struct.pack(fmt, b'r', 0, guest_id,
                                           len(self.nat_public_ip),
                                           self.nat_public_ip.encode(),
                                           nat_ssh_port, nat_telnet_port,
                                           len(guest_snapshot),
                                           guest_snapshot.encode())
                else:
                    fmt = '!cIIH{}sHHH{}s'.format(len(guest_ip),
                                                  len(guest_snapshot))
                    response = struct.pack(fmt, b'r', 0,
                                           guest_id, len(guest_ip),
                                           guest_ip.encode(), ssh_port,
                                           telnet_port, len(guest_snapshot),
                                           guest_snapshot.encode())
            except NoAvailableVMs:
                log.msg(eventid='cowrie.backend_pool.server',
                        format='No VM available, returning error code')
                response = struct.pack('!cI', b'r', 1)

        elif res_op == b'f':
            # receives: guest_id
            recv = struct.unpack('!I', data[1:])
            guest_id = recv[0]

            log.msg(eventid='cowrie.backend_pool.server',
                    format='Freeing VM %(guest_id)s',
                    guest_id=guest_id)

            # free the NAT
            if not self.local_pool and self.use_nat or self.pool_only:
                self.factory.nat.free_binding(guest_id)

            # free the vm
            self.factory.pool_service.free_vm(guest_id)

        elif res_op == b'u':
            # receives: guest_id
            recv = struct.unpack('!I', data[1:])
            guest_id = recv[0]

            log.msg(eventid='cowrie.backend_pool.server',
                    format='Re-using VM %(guest_id)s (not used by attacker)',
                    guest_id=guest_id)

            # free the NAT
            if not self.local_pool and self.use_nat or self.pool_only:
                self.factory.nat.free_binding(guest_id)

            # free this connection and allow VM to be re-used
            self.factory.pool_service.reuse_vm(guest_id)

        if response:
            self.transport.write(response)
Example #5
0
class Output(cowrie.core.output.Output):
    """
    dshield output
    """

    def start(self):
        self.auth_key = CowrieConfig().get('output_dshield', 'auth_key')
        self.userid = CowrieConfig().get('output_dshield', 'userid')
        self.batch_size = CowrieConfig().getint('output_dshield', 'batch_size')
        self.debug = CowrieConfig().getboolean('output_dshield', 'debug', fallback=False)
        self.batch = []  # This is used to store login attempts in batches

    def stop(self):
        pass

    def write(self, entry):
        if entry["eventid"] == 'cowrie.login.success' or entry["eventid"] == 'cowrie.login.failed':
            date = dateutil.parser.parse(entry["timestamp"])
            self.batch.append({
                'date': date.date().__str__(),
                'time': date.time().strftime("%H:%M:%S"),
                'timezone': time.strftime("%z"),
                'source_ip': entry['src_ip'],
                'user': entry['username'],
                'password': entry['password'],
            })

            if len(self.batch) >= self.batch_size:
                batch_to_send = self.batch
                self.submit_entries(batch_to_send)
                self.batch = []

    def transmission_error(self, batch):
        self.batch.extend(batch)
        if len(self.batch) > self.batch_size * 2:
            self.batch = self.batch[-self.batch_size:]

    def submit_entries(self, batch):
        """
        Large parts of this method are adapted from kippo-pyshield by jkakavas
        Many thanks to their efforts. https://github.com/jkakavas/kippo-pyshield
        """
        # The nonce is predefined as explained in the original script :
        # trying to avoid sending the authentication key in the "clear" but
        # not wanting to deal with a full digest like exchange. Using a
        # fixed nonce to mix up the limited userid.
        _nonceb64 = 'ElWO1arph+Jifqme6eXD8Uj+QTAmijAWxX1msbJzXDM='

        log_output = ''
        for attempt in self.batch:
            log_output += '{}\t{}\t{}\t{}\t{}\t{}\n'.format(
                attempt['date'],
                attempt['time'],
                attempt['timezone'],
                attempt['source_ip'],
                attempt['user'],
                attempt['password']
            )

        nonce = base64.b64decode(_nonceb64)
        digest = base64.b64encode(
            hmac.new(
                nonce+self.userid.encode('ascii'),
                base64.b64decode(self.auth_key),
                hashlib.sha256).digest()
        )
        auth_header = 'credentials={} nonce={} userid={}'.format(digest.decode('ascii'), _nonceb64, self.userid)
        headers = {
            'X-ISC-Authorization': auth_header,
            'Content-Type': 'text/plain'
        }

        if self.debug:
            log.msg('dshield: posting: {}'.format(repr(headers)))
            log.msg(f'dshield: posting: {log_output}')

        req = threads.deferToThread(
            requests.request,
            method='PUT',
            url='https://secure.dshield.org/api/file/sshlog',
            headers=headers,
            timeout=10,
            data=log_output
        )

        def check_response(resp):
            failed = False
            response = resp.content.decode('utf8')

            if self.debug:
                log.msg(f"dshield: status code {resp.status_code}")
                log.msg(f"dshield: response {resp.content}")

            if resp.status_code == requests.codes.ok:
                sha1_regex = re.compile(r'<sha1checksum>([^<]+)<\/sha1checksum>')
                sha1_match = sha1_regex.search(response)
                if sha1_match is None:
                    log.msg('dshield: ERROR: Could not find sha1checksum in response: {}'.format(repr(response)))
                    failed = True
                sha1_local = hashlib.sha1()
                sha1_local.update(log_output.encode('utf8'))
                if sha1_match.group(1) != sha1_local.hexdigest():
                    log.msg(
                        'dshield: ERROR: SHA1 Mismatch {} {} .'.format(sha1_match.group(1), sha1_local.hexdigest()))
                    failed = True
                md5_regex = re.compile(r'<md5checksum>([^<]+)<\/md5checksum>')
                md5_match = md5_regex.search(response)
                if md5_match is None:
                    log.msg('dshield: ERROR: Could not find md5checksum in response')
                    failed = True
                md5_local = hashlib.md5()
                md5_local.update(log_output.encode('utf8'))
                if md5_match.group(1) != md5_local.hexdigest():
                    log.msg('dshield: ERROR: MD5 Mismatch {} {} .'.format(md5_match.group(1), md5_local.hexdigest()))
                    failed = True
                log.msg('dshield: SUCCESS: Sent {} bytes worth of data to secure.dshield.org'.format(len(log_output)))
            else:
                log.msg(f'dshield ERROR: error {resp.status_code}.')
                log.msg(f'dshield response was {response}')
                failed = True

            if failed:
                # Something went wrong, we need to add them to batch.
                reactor.callFromThread(self.transmission_error, batch)

        req.addCallback(check_response)