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"> ")
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'> ')
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
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)
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)