def start(self): """ Initialize pymisp module and ObjectWrapper (Abstract event and object creation) """ host: str = CowrieConfig.get("output_redis", "host") port: int = CowrieConfig.getint("output_redis", "port") try: db = CowrieConfig.getint("output_redis", "db") except NoOptionError: db = 0 try: password = CowrieConfig.get("output_redis", "password") except NoOptionError: password = None self.redis = redis.StrictRedis(host=host, port=port, db=db, password=password) self.keyname = CowrieConfig.get("output_redis", "keyname") try: self.send_method = SEND_METHODS[ CowrieConfig.get("output_redis", "send_method") ] except (NoOptionError, KeyError): self.send_method = SEND_METHODS["lpush"]
def send_initialisation(self): """ Used only by the PoolHandler on the first connection, to set the pool up. """ max_vms = CowrieConfig.getint("proxy", "pool_max_vms", fallback=2) vm_unused_timeout = CowrieConfig.getint( "proxy", "pool_vm_unused_timeout", fallback=600 ) share_guests = CowrieConfig.getboolean( "proxy", "pool_share_guests", fallback=True ) buf = struct.pack("!cII?", b"i", max_vms, vm_unused_timeout, share_guests) self.transport.write(buf)
def start(self): """ Start Output Plugin """ self.timeout = [ CowrieConfig.getint("output_reversedns", "timeout", fallback=3) ]
def connectionMade(self): pt = self.getProtoTransport() self.sessionno = pt.transport.sessionno self.realClientIP = pt.transport.getPeer().host self.realClientPort = pt.transport.getPeer().port self.logintime = time.time() log.msg(eventid="cowrie.session.params", arch=self.user.server.arch) timeout = CowrieConfig.getint("honeypot", "interactive_timeout", fallback=180) self.setTimeout(timeout) # Source IP of client in user visible reports (can be fake or real) try: self.clientIP = CowrieConfig.get("honeypot", "fake_addr") except Exception: self.clientIP = self.realClientIP # Source IP of server in user visible reports (can be fake or real) if CowrieConfig.has_option("honeypot", "internet_facing_ip"): self.kippoIP = CowrieConfig.get("honeypot", "internet_facing_ip") else: try: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect(("8.8.8.8", 80)) self.kippoIP = s.getsockname()[0] except Exception: self.kippoIP = "192.168.0.1" finally: s.close()
def backend_connection_success(self, backendTransport): log.msg("Connected to honeypot backend") self.startTime = time.time() self.setTimeout( CowrieConfig.getint("honeypot", "authentication_timeout", fallback=120) )
def start(self): log.msg( "WARNING: Beta version of new hpfeeds enabled. This will become hpfeeds in a future release." ) if CowrieConfig.has_option("output_hpfeeds3", "channel"): self.channel = CowrieConfig.get("output_hpfeeds3", "channel") if CowrieConfig.has_option("output_hpfeeds3", "endpoint"): endpoint = CowrieConfig.get("output_hpfeeds3", "endpoint") else: server = CowrieConfig.get("output_hpfeeds3", "server") port = CowrieConfig.getint("output_hpfeeds3", "port") if CowrieConfig.has_option("output_hpfeeds3", "tlscert"): with open(CowrieConfig.get("output_hpfeeds3", "tlscert")) as fp: authority = ssl.Certificate.loadPEM(fp.read()) options = ssl.optionsForClientTLS(server, authority) endpoint = endpoints.SSL4ClientEndpoint( reactor, server, port, options) else: endpoint = endpoints.HostnameEndpoint(reactor, server, port) ident = CowrieConfig.get("output_hpfeeds3", "identifier") secret = CowrieConfig.get("output_hpfeeds3", "secret") self.meta = {} self.client = ClientSessionService(endpoint, ident, secret) self.client.startService()
def __init__(self, nat_service): self.qemu = backend_pool.libvirt.backend_service.LibvirtBackendService( ) self.nat_service = nat_service self.guests = [] self.guest_id: int = 0 self.guest_lock = Lock() # time in seconds between each loop iteration self.loop_sleep_time: int = 5 self.loop_next_call = None # default configs; custom values will come from the client when they connect to the pool self.max_vm: int = 2 self.vm_unused_timeout: int = 600 self.share_guests: bool = True # file configs self.ssh_port: int = CowrieConfig.getint("backend_pool", "guest_ssh_port", fallback=-1) self.telnet_port: int = CowrieConfig.getint("backend_pool", "guest_telnet_port", fallback=-1) self.local_pool: str = (CowrieConfig.get("proxy", "pool", fallback="local") == "local") self.pool_only: bool = CowrieConfig.getboolean("backend_pool", "pool_only", fallback=False) self.use_nat: bool = CowrieConfig.getboolean("backend_pool", "use_nat", fallback=True) # detect invalid config if not self.ssh_port > 0 and not self.telnet_port > 0: log.msg( eventid="cowrie.backend_pool.service", format= "Invalid configuration: one of SSH or Telnet ports must be defined!", ) os._exit(1) self.any_vm_up: bool = False # TODO fix for no VM available
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 start(self): log.msg("Early version of hpfeeds-output, untested!") server = CowrieConfig.get("output_hpfeeds", "server") port = CowrieConfig.getint("output_hpfeeds", "port") ident = CowrieConfig.get("output_hpfeeds", "identifier") secret = CowrieConfig.get("output_hpfeeds", "secret") debug = CowrieConfig.getboolean("output_hpfeeds", "debug") self.client = hpclient(server, port, ident, secret, debug) self.meta = {}
def start(self): self.timeout = CowrieConfig.getint("output_socketlog", "timeout") addr = CowrieConfig.get("output_socketlog", "address") self.host = addr.split(":")[0] self.port = int(addr.split(":")[1]) self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.settimeout(self.timeout) self.sock.connect((self.host, self.port))
def backend_connection_success(self, backendTransport): log.msg("Connected to honeypot backend") self.startTime = time.time() # this timeout is replaced with `interactive_timeout` in ssh.py self.setTimeout( CowrieConfig.getint("honeypot", "authentication_timeout", fallback=120))
def start(self): self.host = CowrieConfig.get(RETHINK_DB_SEGMENT, "host") self.port = CowrieConfig.getint(RETHINK_DB_SEGMENT, "port") self.db = CowrieConfig.get(RETHINK_DB_SEGMENT, "db") self.table = CowrieConfig.get(RETHINK_DB_SEGMENT, "table") self.password = CowrieConfig.get(RETHINK_DB_SEGMENT, "password", raw=True) self.connection = r.connect( host=self.host, port=self.port, db=self.db, password=self.password ) try: r.db_create(self.db).run(self.connection) r.db(self.db).table_create(self.table).run(self.connection) except r.RqlRuntimeError: pass
def start(self): self.api_url = CowrieConfig.get( "output_threatjammer", "api_url", fallback=THREATJAMMER_REPORT_URL, ) self.default_ttl = CowrieConfig.getint( "output_threatjammer", "ttl", fallback=THREATJAMMER_DEFAULT_TTL) self.default_category = CowrieConfig.get( "output_threatjammer", "category", fallback=THREATJAMMER_DEFAULT_CATEGORY, ) self.track_login = CowrieConfig.getboolean( "output_threatjammer", "track_login", fallback=THREATJAMMER_DEFAULT_TRACK_LOGIN, ) self.track_session = CowrieConfig.getboolean( "output_threatjammer", "track_session", fallback=THREATJAMMER_DEFAULT_TRACK_SESSION, ) self.bearer_token = CowrieConfig.get("output_threatjammer", "bearer_token") self.tags = CowrieConfig.get("output_threatjammer", "tags").split(",") self.last_report: int = -1 self.report_bucket: int = BUFFER_FLUSH_MAX_SIZE self.ip_set: Set[str] = set() self.track_events = [] if self.track_login: self.track_events.append("cowrie.login") if self.track_session: self.track_events.append("cowrie.session") self.http_client = HTTPClient(self.api_url, self.bearer_token) log.msg( eventid="cowrie.threatjammer.reporterinitialized", format="ThreatJammer.com output plugin successfully initialized.\ Category=%(category)s. TTL=%(ttl)s. Session Tracking=%(session_tracking)s. Login Tracking=%(login_tracking)s", category=self.default_category, ttl=self.default_ttl, session_tracking=self.track_session, login_tracking=self.track_login, )
def __init__(self, tolerance_attempts, state_dump): self.sleeping = False self.sleep_until = 0 self.tolerance_attempts = tolerance_attempts self.tolerance_window = 60 * CowrieConfig.getint( "output_abuseipdb", "tolerance_window", fallback=120) self.rereport_after = 3600 * CowrieConfig.getfloat( "output_abuseipdb", "rereport_after", fallback=24) if self.rereport_after < REREPORT_MINIMUM: self.rereport_after = REREPORT_MINIMUM self.state_dump = state_dump # To write our dump to disk we have a method we call in a thread so we # don't block if we get slow io. This is a cheap hack to get a lock on # the file. See self.write_dump_file() self._writing = False super().__init__()
def connectionMade(self): """ Called when the connection is made to the other side. We sent our version and the MSG_KEXINIT packet. """ self.sshParse = ssh.SSH(self) self.transportId = uuid.uuid4().hex[:12] self.peer_ip = self.transport.getPeer().host self.peer_port = self.transport.getPeer().port + 1 self.local_ip = self.transport.getHost().host self.local_port = self.transport.getHost().port self.transport.write(f"{self.ourVersionString}\r\n".encode()) self.currentEncryptions = transport.SSHCiphers(b"none", b"none", b"none", b"none") self.currentEncryptions.setKeys(b"", b"", b"", b"", b"", b"") self.otherVersionString = "Unknown" log.msg( eventid="cowrie.session.connect", format= "New connection: %(src_ip)s:%(src_port)s (%(dst_ip)s:%(dst_port)s) [session: %(session)s]", src_ip=self.peer_ip, src_port=self.transport.getPeer().port, dst_ip=self.local_ip, dst_port=self.transport.getHost().port, session=self.transportId, sessionno=f"S{self.transport.sessionno}", protocol="ssh", ) # if we have a pool connect to it and later request a backend, else just connect to a simple backend # when pool is set we can just test self.pool_interface to the same effect of getting the CowrieConfig proxy_backend = CowrieConfig.get("proxy", "backend", fallback="simple") if proxy_backend == "pool": # request a backend d = self.factory.pool_handler.request_interface() d.addCallback(self.pool_connection_success) d.addErrback(self.pool_connection_error) else: # simply a proxy, no pool backend_ip = CowrieConfig.get("proxy", "backend_ssh_host") backend_port = CowrieConfig.getint("proxy", "backend_ssh_port") self.connect_to_backend(backend_ip, backend_port)
def processPasswordInput(self) -> None: self.sendData = False # withold data until input is complete if self.prePasswordData: self.sendBackend(self.currentData[:3]) self.prePasswordData = False # remove control characters control_chars = [b"\xff", b"\xfd", b"\x01", b"\r", b"\x00", b"\n"] self.passwordState += remove_all(self.currentData, control_chars) # check if done inputing if b"\r" in self.currentData: terminatingChar = chr( self.currentData[self.currentData.index(b"\r") + 1]).encode() # usually \n or \x00 # cleanup self.passwordState = process_backspaces(self.passwordState) log.msg( f"User input password: {self.passwordState.decode('unicode-escape')}" ) self.inputingPassword = False # having the password (and the username, either empy or set before), we can check the login # on the database, and if valid authenticate or else, if invalid send a fake password to get # the login failed prompt src_ip = self.server.transport.getPeer().host if HoneypotPasswordChecker().checkUserPass(self.usernameState, self.passwordState, src_ip): passwordToSend = self.backendPassword self.authDone = True self.server.setTimeout( CowrieConfig.getint("honeypot", "interactive_timeout", fallback=300)) else: log.msg("Sending invalid auth to backend") passwordToSend = self.backendPassword + b"fake" # actually send to backend self.currentData = passwordToSend + b"\r" + terminatingChar self.sendData = True
def _cbLogin(self, ial): """ Fired on a successful login """ interface, protocol, logout = ial protocol.windowSize = self.windowSize self.protocol = protocol self.logout = logout self.state = "Command" self.transport.write(b"\n") # Remove the short timeout of the login prompt. self.transport.setTimeout( CowrieConfig.getint("honeypot", "interactive_timeout", fallback=300) ) # replace myself with avatar protocol protocol.makeConnection(self.transport) self.transport.protocol = protocol
def start(self): self.debug = CowrieConfig.getboolean("output_mysql", "debug", fallback=False) port = CowrieConfig.getint("output_mysql", "port", fallback=3306) try: self.db = ReconnectingConnectionPool( "MySQLdb", host=CowrieConfig.get("output_mysql", "host"), db=CowrieConfig.get("output_mysql", "database"), user=CowrieConfig.get("output_mysql", "username"), passwd=CowrieConfig.get("output_mysql", "password", raw=True), port=port, cp_min=1, cp_max=1, charset="utf8mb4", cp_reconnect=True, use_unicode=True, ) except (MySQLdb.Error, MySQLdb._exceptions.Error) as e: log.msg("output_mysql: Error %d: %s" % (e.args[0], e.args[1]))
def connectionMade(self): self.transportId = uuid.uuid4().hex[:12] sessionno = self.transport.sessionno self.startTime = time.time() self.setTimeout( CowrieConfig.getint("honeypot", "authentication_timeout", fallback=120) ) log.msg( eventid="cowrie.session.connect", format="New connection: %(src_ip)s:%(src_port)s (%(dst_ip)s:%(dst_port)s) [session: %(session)s]", src_ip=self.transport.getPeer().host, src_port=self.transport.getPeer().port, dst_ip=self.transport.getHost().host, dst_port=self.transport.getHost().port, session=self.transportId, sessionno="T{}".format(str(sessionno)), protocol="telnet", ) TelnetTransport.connectionMade(self)
def start_pool(self): # cleanup older qemu objects self.qemu.destroy_all_cowrie() # start backend qemu environment self.qemu.start_backend() # cleanup references if restarting self.guests = [] self.guest_id = 0 self.any_vm_up = False # TODO fix for no VM available # start producer threads.deferToThread(self.producer_loop) # recycle myself after some time recycle_period = CowrieConfig.getint("backend_pool", "recycle_period", fallback=-1) if recycle_period > 0: reactor.callLater(recycle_period, self.restart_pool)
def connectionMade(self): self.transportId = uuid.uuid4().hex[:12] sessionno = self.transport.sessionno self.peer_ip = self.transport.getPeer().host self.peer_port = self.transport.getPeer().port + 1 self.local_ip = self.transport.getHost().host self.local_port = self.transport.getHost().port log.msg( eventid="cowrie.session.connect", format= "New connection: %(src_ip)s:%(src_port)s (%(dst_ip)s:%(dst_port)s) [session: %(session)s]", src_ip=self.transport.getPeer().host, src_port=self.transport.getPeer().port, dst_ip=self.transport.getHost().host, dst_port=self.transport.getHost().port, session=self.transportId, sessionno=f"T{str(sessionno)}", protocol="telnet", ) TelnetTransport.connectionMade(self) # if we have a pool connect to it and later request a backend, else just connect to a simple backend # when pool is set we can just test self.pool_interface to the same effect of getting the config proxy_backend = CowrieConfig.get("proxy", "backend", fallback="simple") if proxy_backend == "pool": # request a backend d = self.factory.pool_handler.request_interface() d.addCallback(self.pool_connection_success) d.addErrback(self.pool_connection_error) else: # simply a proxy, no pool backend_ip = CowrieConfig.get("proxy", "backend_telnet_host") backend_port = CowrieConfig.getint("proxy", "backend_telnet_port") self.connect_to_backend(backend_ip, backend_port)
def start(self): self.debug = CowrieConfig.getboolean("output_mysql", "debug", fallback=False) port = CowrieConfig.getint("output_mysql", "port", fallback=3306) try: self.db = ReconnectingConnectionPool( "mysql.connector", host=CowrieConfig.get("output_mysql", "host"), db=CowrieConfig.get("output_mysql", "database"), user=CowrieConfig.get("output_mysql", "username"), passwd=CowrieConfig.get("output_mysql", "password", raw=True), port=port, cp_min=1, cp_max=1, charset="utf8mb4", cp_reconnect=True, use_unicode=True, ) # except (MySQLdb.Error, MySQLdb._exceptions.Error) as e: except Exception as e: log.msg(f"output_mysql: Error {e.args[0]}: {e.args[1]}") self.lc = LoopingCall(self.check_wait) self.lc.start(30) self.versions = {}
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 CowrieSSHChannel(channel.SSHChannel): """ This is an SSH channel with built-in logging """ ttylogEnabled = True ttylogFile = "" bytesReceived = 0 bytesReceivedLimit = 0 bytesWritten = 0 name = b"cowrie-ssh-channel" startTime: float = 0.0 ttylogPath = CowrieConfig.get("honeypot", "log_path") downloadPath = CowrieConfig.get("honeypot", "download_path") ttylogEnabled = CowrieConfig.getboolean("honeypot", "ttylog", fallback=True) bytesReceivedLimit = CowrieConfig.getint("honeypot", "download_limit_size", fallback=0) def __repr__(self): """ Return a pretty representation of this object. @return Pretty representation of this object as a string @rtype: L{str} """ return f"Cowrie SSH Channel {self.name}" def __init__(self, *args, **kw): """ Initialize logging """ channel.SSHChannel.__init__(self, *args, **kw) def channelOpen(self, specificData): self.startTime = time.time() self.ttylogFile = "{}/tty/{}-{}-{}.log".format( self.ttylogPath, time.strftime("%Y%m%d-%H%M%S"), self.conn.transport.transportId, self.id, ) log.msg( eventid="cowrie.log.open", ttylog=self.ttylogFile, format="Opening TTY Log: %(ttylog)s", ) ttylog.ttylog_open(self.ttylogFile, time.time()) channel.SSHChannel.channelOpen(self, specificData) def closed(self): log.msg( eventid="cowrie.log.closed", format="Closing TTY Log: %(ttylog)s after %(duration)f seconds", ttylog=self.ttylogFile, size=self.bytesReceived + self.bytesWritten, duration=time.time() - self.startTime, ) ttylog.ttylog_close(self.ttylogFile, time.time()) channel.SSHChannel.closed(self) def dataReceived(self, data): """ Called when we receive data from the user @type data: L{bytes} @param data: Data sent to the server from the client """ self.bytesReceived += len(data) if self.bytesReceivedLimit and self.bytesReceived > self.bytesReceivedLimit: log.msg(f"Data upload limit reached for channel {self.id}") self.eofReceived() return if self.ttylogEnabled: ttylog.ttylog_write(self.ttylogFile, len(data), ttylog.TYPE_INPUT, time.time(), data) channel.SSHChannel.dataReceived(self, data) def write(self, data): """ Called when we send data to the user @type data: L{bytes} @param data: Data sent to the client from the server """ if self.ttylogEnabled: ttylog.ttylog_write(self.ttylogFile, len(data), ttylog.TYPE_OUTPUT, time.time(), data) self.bytesWritten += len(data) channel.SSHChannel.write(self, data)
class Command_tftp(HoneyPotCommand): port = 69 hostname = None file_to_get = None limit_size = CowrieConfig.getint("honeypot", "download_limit_size", fallback=0) def makeTftpRetrieval(self): progresshook = Progress(self).progresshook self.artifactFile = Artifact(self.file_to_get) tclient = None url = "" try: tclient = tftpy.TftpClient(self.hostname, int(self.port)) # tftpy can't handle unicode string as filename # so we have to convert unicode type to str type tclient.download(str(self.file_to_get), self.artifactFile, progresshook) url = "tftp://{}/{}".format(self.hostname, self.file_to_get.strip("/")) self.file_to_get = self.fs.resolve_path(self.file_to_get, self.protocol.cwd) if hasattr(tclient.context, "metrics"): self.fs.mkfile(self.file_to_get, 0, 0, tclient.context.metrics.bytes, 33188) else: self.fs.mkfile(self.file_to_get, 0, 0, 0, 33188) except tftpy.TftpException: if tclient and tclient.context and not tclient.context.fileobj.closed: tclient.context.fileobj.close() if url: # log to cowrie.log log.msg( format= "Downloaded URL (%(url)s) with SHA-256 %(shasum)s to %(outfile)s", url=url, outfile=self.artifactFile.shasumFilename, shasum=self.artifactFile.shasum, ) self.protocol.logDispatch( eventid="cowrie.session.file_download", format= "Downloaded URL (%(url)s) with SHA-256 %(shasum)s to %(outfile)s", url=url, outfile=self.artifactFile.shasumFilename, shasum=self.artifactFile.shasum, destfile=self.file_to_get, ) # Update the honeyfs to point to downloaded file self.fs.update_realfile(self.fs.getfile(self.file_to_get), self.artifactFile.shasumFilename) self.fs.chown(self.file_to_get, self.protocol.user.uid, self.protocol.user.gid) def start(self): parser = CustomParser(self) parser.prog = "tftp" parser.add_argument("hostname", nargs="?", default=None) parser.add_argument("-c", nargs=2) parser.add_argument("-l") parser.add_argument("-g") parser.add_argument("-p") parser.add_argument("-r") args = parser.parse_args(self.args) if args.c: if len(args.c) > 1: self.file_to_get = args.c[1] if args.hostname is None: self.exit() return self.hostname = args.hostname elif args.r: self.file_to_get = args.r self.hostname = args.g else: self.write( "usage: tftp [-h] [-c C C] [-l L] [-g G] [-p P] [-r R] [hostname]\n" ) self.exit() return if self.hostname is None: self.exit() return if self.hostname.find(":") != -1: host, port = self.hostname.split(":") self.hostname = host self.port = int(port) self.makeTftpRetrieval() self.exit()
class command_curl(HoneyPotCommand): """ curl command """ limit_size = CowrieConfig.getint("honeypot", "download_limit_size", fallback=0) download_path = CowrieConfig.get("honeypot", "download_path") def start(self): try: optlist, args = getopt.getopt( self.args, "sho:O", ["help", "manual", "silent"] ) except getopt.GetoptError as err: # TODO: should be 'unknown' instead of 'not recognized' self.write(f"curl: {err}\n") self.write( "curl: try 'curl --help' or 'curl --manual' for more information\n" ) self.exit() return for opt in optlist: if opt[0] == "-h" or opt[0] == "--help": self.write(CURL_HELP) self.exit() return elif opt[0] == "-s" or opt[0] == "--silent": self.silent = True if len(args): if args[0] is not None: url = str(args[0]).strip() else: self.write( "curl: try 'curl --help' or 'curl --manual' for more information\n" ) self.exit() return if "://" not in url: url = "http://" + url urldata = compat.urllib_parse.urlparse(url) outfile = None for opt in optlist: if opt[0] == "-o": outfile = opt[1] if opt[0] == "-O": outfile = urldata.path.split("/")[-1] if ( outfile is None or not len(outfile.strip()) or not urldata.path.count("/") ): self.write("curl: Remote file name has no length!\n") self.exit() return if outfile: outfile = self.fs.resolve_path(outfile, self.protocol.cwd) path = os.path.dirname(outfile) if not path or not self.fs.exists(path) or not self.fs.isdir(path): self.write( "curl: %s: Cannot open: No such file or directory\n" % outfile ) self.exit() return url = url.encode("ascii") self.url = url self.artifactFile = Artifact(outfile) # HTTPDownloader will close() the file object so need to preserve the name self.deferred = self.download(url, outfile, self.artifactFile) if self.deferred: self.deferred.addCallback(self.success, outfile) self.deferred.addErrback(self.error, url) def download(self, url, fakeoutfile, outputfile, *args, **kwargs): scheme: bytes try: parsed = compat.urllib_parse.urlparse(url) scheme = parsed.scheme host: str = parsed.hostname.decode("utf8") port: int = parsed.port or (443 if scheme == "https" else 80) if scheme != b"http" and scheme != b"https": raise NotImplementedError except Exception: self.errorWrite( f'curl: (1) Protocol "{scheme.encode("utf8")}" not supported or disabled in libcurl\n' ) self.exit() return None factory = HTTPProgressDownloader( self, fakeoutfile, url, outputfile, *args, **kwargs ) out_addr = None if CowrieConfig.has_option("honeypot", "out_addr"): out_addr = (CowrieConfig.get("honeypot", "out_addr"), 0) if scheme == "https": context_factory = ssl.optionsForClientTLS(hostname=host) self.connection = reactor.connectSSL( host, port, factory, context_factory, bindAddress=out_addr ) else: # Can only be http self.connection = reactor.connectTCP( host, port, factory, bindAddress=out_addr ) return factory.deferred def handle_CTRL_C(self): self.write("^C\n") self.connection.transport.loseConnection() def success(self, data, outfile): if not os.path.isfile(self.artifactFile.shasumFilename): log.msg("there's no file " + self.artifactFile.shasumFilename) self.exit() self.protocol.logDispatch( eventid="cowrie.session.file_download", format="Downloaded URL (%(url)s) with SHA-256 %(shasum)s to %(outfile)s", url=self.url, outfile=self.artifactFile.shasumFilename, shasum=self.artifactFile.shasum, ) # Update the honeyfs to point to downloaded file if output is a file if outfile: self.fs.update_realfile( self.fs.getfile(outfile), self.artifactFile.shasumFilename ) self.fs.chown(outfile, self.protocol.user.uid, self.protocol.user.gid) else: with open(self.artifactFile.shasumFilename, "rb") as f: self.writeBytes(f.read()) self.exit() def error(self, error, url): log.msg(error.printTraceback()) if hasattr(error, "getErrorMessage"): # Exceptions errormsg = error.getErrorMessage() log.msg(errormsg) self.write("\n") self.protocol.logDispatch( eventid="cowrie.session.file_download.failed", format="Attempt to download file(s) from URL (%(url)s) failed", url=self.url, ) self.exit()
def makeService(self, options: Dict) -> service.Service: """ Construct a TCPServer from a factory defined in Cowrie. """ if options["help"] is True: print("""Usage: twistd [options] cowrie [-h] Options: -h, --help print this help message. Makes a Cowrie SSH/Telnet honeypot. """) sys.exit(1) if os.name == "posix" and os.getuid() == 0: print("ERROR: You must not run cowrie as root!") sys.exit(1) tz: str = CowrieConfig.get("honeypot", "timezone", fallback="UTC") # `system` means use the system time zone if tz != "system": os.environ["TZ"] = tz log.msg("Python Version {}".format(str(sys.version).replace("\n", ""))) log.msg("Twisted Version {}.{}.{}".format( __twisted_version__.major, __twisted_version__.minor, __twisted_version__.micro, )) log.msg("Cowrie Version {}.{}.{}".format( __cowrie_version__.major, __cowrie_version__.minor, __cowrie_version__.micro, )) # check configurations if not self.enableTelnet and not self.enableSSH and not self.pool_only: print( "ERROR: You must at least enable SSH or Telnet, or run the backend pool" ) sys.exit(1) # Load output modules self.output_plugins = [] for x in CowrieConfig.sections(): if not x.startswith("output_"): continue if CowrieConfig.getboolean(x, "enabled") is False: continue engine: str = x.split("_")[1] try: output = __import__(f"cowrie.output.{engine}", globals(), locals(), ["output"]).Output() log.addObserver(output.emit) self.output_plugins.append(output) log.msg(f"Loaded output engine: {engine}") except ImportError as e: log.err( f"Failed to load output engine: {engine} due to ImportError: {e}" ) log.msg( f"Please install the dependencies for {engine} listed in requirements-output.txt" ) except Exception: log.err() log.msg(f"Failed to load output engine: {engine}") self.topService = service.MultiService() application = service.Application("cowrie") self.topService.setServiceParent(application) # initialise VM pool handling - only if proxy AND pool set to enabled, and pool is to be deployed here # or also enabled if pool_only is true backend_type: str = CowrieConfig.get("honeypot", "backend", fallback="shell") proxy_backend: str = CowrieConfig.get("proxy", "backend", fallback="simple") if (backend_type == "proxy" and proxy_backend == "pool") or self.pool_only: # in this case we need to set some kind of pool connection local_pool: bool = (CowrieConfig.get("proxy", "pool", fallback="local") == "local") pool_host: str = CowrieConfig.get("proxy", "pool_host", fallback="127.0.0.1") pool_port: int = CowrieConfig.getint("proxy", "pool_port", fallback=6415) if local_pool or self.pool_only: # start a pool locally f = PoolServerFactory() f.tac = self listen_endpoints = get_endpoints_from_section( CowrieConfig, "backend_pool", 6415) create_endpoint_services(reactor, self.topService, listen_endpoints, f) pool_host = "127.0.0.1" # force use of local interface # either way (local or remote) we set up a client to the pool # unless this instance has no SSH and Telnet (pool only) if (self.enableTelnet or self.enableSSH) and not self.pool_only: self.pool_handler = PoolHandler(pool_host, pool_port, self) # type: ignore else: # we initialise the services directly self.pool_ready() return self.topService
class command_wget(HoneyPotCommand): """ wget command """ limit_size = CowrieConfig.getint("honeypot", "download_limit_size", fallback=0) downloadPath = CowrieConfig.get("honeypot", "download_path") def start(self): try: optlist, args = getopt.getopt(self.args, "cqO:P:", ["header="]) except getopt.GetoptError: self.errorWrite("Unrecognized option\n") self.exit() return if len(args): url = args[0].strip() else: self.errorWrite("wget: missing URL\n") self.errorWrite("Usage: wget [OPTION]... [URL]...\n\n") self.errorWrite("Try `wget --help' for more options.\n") self.exit() return self.outfile = None self.quiet = False for opt in optlist: if opt[0] == "-O": self.outfile = opt[1] if opt[0] == "-q": self.quiet = True # for some reason getopt doesn't recognize "-O -" # use try..except for the case if passed command is malformed try: if not self.outfile: if "-O" in args: self.outfile = args[args.index("-O") + 1] except Exception: pass if "://" not in url: url = "http://%s" % url urldata = compat.urllib_parse.urlparse(url) self.url = url.encode("utf8") if self.outfile is None: self.outfile = urldata.path.split("/")[-1] if not len(self.outfile.strip()) or not urldata.path.count("/"): self.outfile = "index.html" if self.outfile != "-": self.outfile = self.fs.resolve_path(self.outfile, self.protocol.cwd) path = os.path.dirname(self.outfile) if not path or not self.fs.exists(path) or not self.fs.isdir(path): self.errorWrite( "wget: %s: Cannot open: No such file or directory\n" % self.outfile) self.exit() return self.deferred = self.download(self.url, self.outfile) if self.deferred: self.deferred.addCallback(self.success) self.deferred.addErrback(self.error, self.url) else: self.exit() def download(self, url, fakeoutfile, *args, **kwargs): """ url - URL to download fakeoutfile - file in guest's fs that attacker wants content to be downloaded to """ try: parsed = compat.urllib_parse.urlparse(url) scheme = parsed.scheme host = parsed.hostname.decode("utf8") port = parsed.port or (443 if scheme == b"https" else 80) if scheme != b"http" and scheme != b"https": raise NotImplementedError if not host: return None except Exception: self.errorWrite(f"{url}: Unsupported scheme.\n") return None # File in host's fs that will hold content of the downloaded file # HTTPDownloader will close() the file object so need to preserve the name self.artifactFile = Artifact(self.outfile) if not self.quiet: self.errorWrite("--{}-- {}\n".format( time.strftime("%Y-%m-%d %H:%M:%S"), url.decode("utf8"))) self.errorWrite("Connecting to %s:%d... connected.\n" % (host, port)) self.errorWrite("HTTP request sent, awaiting response... ") factory = HTTPProgressDownloader(self, fakeoutfile, url, self.artifactFile, *args, **kwargs) out_addr = None if CowrieConfig.has_option("honeypot", "out_addr"): out_addr = (CowrieConfig.get("honeypot", "out_addr"), 0) if scheme == b"https": context_factory = ssl.optionsForClientTLS(hostname=host) self.connection = reactor.connectSSL(host, port, factory, context_factory, bindAddress=out_addr) elif scheme == b"http": self.connection = reactor.connectTCP(host, port, factory, bindAddress=out_addr) else: raise NotImplementedError return factory.deferred def handle_CTRL_C(self): self.errorWrite("^C\n") self.connection.transport.loseConnection() def success(self, data): if not os.path.isfile(self.artifactFile.shasumFilename): log.msg("there's no file " + self.artifactFile.shasumFilename) self.exit() # log to cowrie.log log.msg( format= "Downloaded URL (%(url)s) with SHA-256 %(shasum)s to %(outfile)s", url=self.url, outfile=self.artifactFile.shasumFilename, shasum=self.artifactFile.shasum, ) # log to output modules self.protocol.logDispatch( eventid="cowrie.session.file_download", format= "Downloaded URL (%(url)s) with SHA-256 %(shasum)s to %(outfile)s", url=self.url, outfile=self.artifactFile.shasumFilename, shasum=self.artifactFile.shasum, ) # Update honeyfs to point to downloaded file or write to screen if self.outfile != "-": self.fs.update_realfile(self.fs.getfile(self.outfile), self.artifactFile.shasumFilename) self.fs.chown(self.outfile, self.protocol.user.uid, self.protocol.user.gid) else: with open(self.artifactFile.shasumFilename, "rb") as f: self.writeBytes(f.read()) self.exit() def error(self, error, url): # we need to handle 301 redirects separately if hasattr(error, "webStatus") and error.webStatus.decode() == "301": self.errorWrite( f"{error.webStatus.decode()} {error.webMessage.decode()}\n") https_url = error.getErrorMessage().replace( "301 Moved Permanently to ", "") self.errorWrite(f"Location {https_url} [following]\n") # do the download again with the https URL self.deferred = self.download(https_url.encode("utf8"), self.outfile) if self.deferred: self.deferred.addCallback(self.success) self.deferred.addErrback(self.error, https_url) else: self.exit() else: if hasattr(error, "getErrorMessage"): # exceptions errorMessage = error.getErrorMessage() self.errorWrite(errorMessage + "\n") # Real wget also adds this: if (hasattr(error, "webStatus") and error.webStatus and hasattr(error, "webMessage")): # exceptions self.errorWrite("{} ERROR {}: {}\n".format( time.strftime("%Y-%m-%d %T"), error.webStatus.decode(), error.webMessage.decode("utf8"), )) else: self.errorWrite("{} ERROR 404: Not Found.\n".format( time.strftime("%Y-%m-%d %T"))) # prevent cowrie from crashing if the terminal have been already destroyed try: self.protocol.logDispatch( eventid="cowrie.session.file_download.failed", format= "Attempt to download file(s) from URL (%(url)s) failed", url=self.url, ) except Exception: pass self.exit()
class Command_curl(HoneyPotCommand): """ curl command """ limit_size: int = CowrieConfig.getint("honeypot", "download_limit_size", fallback=0) outfile: Optional[str] = None # outfile is the file saved inside the honeypot artifact: Artifact # artifact is the file saved for forensics in the real file system currentlength: int = 0 # partial size during download totallength: int = 0 # total length silent: bool = False url: bytes host: str def start(self) -> None: try: optlist, args = getopt.getopt( self.args, "sho:O", ["help", "manual", "silent"] ) except getopt.GetoptError as err: # TODO: should be 'unknown' instead of 'not recognized' self.write(f"curl: {err}\n") self.write( "curl: try 'curl --help' or 'curl --manual' for more information\n" ) self.exit() return for opt in optlist: if opt[0] == "-h" or opt[0] == "--help": self.write(CURL_HELP) self.exit() return elif opt[0] == "-s" or opt[0] == "--silent": self.silent = True if len(args): if args[0] is not None: url = str(args[0]).strip() else: self.write( "curl: try 'curl --help' or 'curl --manual' for more information\n" ) self.exit() return if "://" not in url: url = "http://" + url urldata = compat.urllib_parse.urlparse(url) for opt in optlist: if opt[0] == "-o": self.outfile = opt[1] if opt[0] == "-O": self.outfile = urldata.path.split("/")[-1] if ( self.outfile is None or not len(self.outfile.strip()) or not urldata.path.count("/") ): self.write("curl: Remote file name has no length!\n") self.exit() return if self.outfile: self.outfile = self.fs.resolve_path(self.outfile, self.protocol.cwd) if self.outfile: path = os.path.dirname(self.outfile) if not path or not self.fs.exists(path) or not self.fs.isdir(path): self.write( f"curl: {self.outfile}: Cannot open: No such file or directory\n" ) self.exit() return self.url = url.encode("ascii") parsed = compat.urllib_parse.urlparse(url) if parsed.hostname: self.host = parsed.hostname if parsed.scheme: scheme = parsed.scheme # port: int = parsed.port or (443 if scheme == "https" else 80) if scheme != "http" and scheme != "https": self.errorWrite( f'curl: (1) Protocol "{scheme}" not supported or disabled in libcurl\n' ) self.exit() return # TODO: need to do full name resolution in case someon passes DNS name pointing to local address try: if ipaddress.ip_address(self.host).is_private: self.errorWrite(f"curl: (6) Could not resolve host: {self.host}\n") self.exit() return None except ValueError: pass self.artifact = Artifact("curl-download") self.deferred = self.treqDownload(url) if self.deferred: self.deferred.addCallback(self.success) self.deferred.addErrback(self.error) def treqDownload(self, url): """ Download `url` """ headers = {"User-Agent": ["curl/7.38.0"]} # TODO: use designated outbound interface # out_addr = None # if CowrieConfig.has_option("honeypot", "out_addr"): # out_addr = (CowrieConfig.get("honeypot", "out_addr"), 0) deferred = treq.get(url=url, allow_redirects=False, headers=headers, timeout=10) return deferred def handle_CTRL_C(self): self.write("^C\n") self.exit() def success(self, response): """ successful treq get """ self.totallength = response.length # TODO possible this is UNKNOWN_LENGTH if self.limit_size > 0 and self.totallength > self.limit_size: log.msg( f"Not saving URL ({self.url}) (size: {self.totallength}) exceeds file size limit ({self.limit_size})" ) self.exit() return if self.outfile and not self.silent: self.write( " % Total % Received % Xferd Average Speed Time Time Time Current\n" ) self.write( " Dload Upload Total Spent Left Speed\n" ) deferred = treq.collect(response, self.collect) deferred.addCallback(self.collectioncomplete) return deferred def collect(self, data: bytes) -> None: """ partial collect """ self.currentlength += len(data) if self.limit_size > 0 and self.currentlength > self.limit_size: log.msg( f"Not saving URL ({self.url.decode()}) (size: {self.currentlength}) exceeds file size limit ({self.limit_size})" ) self.exit() return self.artifact.write(data) if self.outfile and not self.silent: self.write( "\r100 {} 100 {} 0 0 {} 0 --:--:-- --:--:-- --:--:-- {}".format( self.currentlength, self.currentlength, 63673, 65181 ) ) if not self.outfile: self.writeBytes(data) def collectioncomplete(self, data: None) -> None: """ this gets called once collection is complete """ self.artifact.close() if self.outfile and not self.silent: self.write("\n") # Update the honeyfs to point to artifact file if output is to file if self.outfile: self.fs.mkfile(self.outfile, 0, 0, self.currentlength, 33188) self.fs.chown(self.outfile, self.protocol.user.uid, self.protocol.user.gid) self.fs.update_realfile( self.fs.getfile(self.outfile), self.artifact.shasumFilename ) self.protocol.logDispatch( eventid="cowrie.session.file_download", format="Downloaded URL (%(url)s) with SHA-256 %(shasum)s to %(filename)s", url=self.url, filename=self.artifact.shasumFilename, shasum=self.artifact.shasum, ) log.msg( eventid="cowrie.session.file_download", format="Downloaded URL (%(url)s) with SHA-256 %(shasum)s to %(filename)s", url=self.url, filename=self.artifact.shasumFilename, shasum=self.artifact.shasum, ) self.exit() def error(self, response): """ handle any exceptions """ if response.check(error.DNSLookupError) is not None: self.write(f"curl: (6) Could not resolve host: {self.host}\n") self.exit() return # possible errors: # defer.CancelledError, # error.ConnectingCancelledError, log.msg(response.printTraceback()) if hasattr(response, "getErrorMessage"): # Exceptions errormsg = response.getErrorMessage() log.msg(errormsg) self.write("\n") self.protocol.logDispatch( eventid="cowrie.session.file_download.failed", format="Attempt to download file(s) from URL (%(url)s) failed", url=self.url, ) self.exit()
class LoggingServerProtocol(insults.ServerProtocol): """ Wrapper for ServerProtocol that implements TTY logging """ ttylogPath: str = CowrieConfig.get("honeypot", "ttylog_path") downloadPath: str = CowrieConfig.get("honeypot", "download_path") ttylogEnabled: bool = CowrieConfig.getboolean("honeypot", "ttylog", fallback=True) bytesReceivedLimit: int = CowrieConfig.getint("honeypot", "download_limit_size", fallback=0) def __init__(self, prot=None, *a, **kw): self.type: str self.ttylogSize: int = 0 self.bytesReceived: int = 0 self.redirFiles: Set[List[str]] = set() self.redirlogOpen: bool = False # it will be set at core/protocol.py self.stdinlogOpen: bool = False self.ttylogOpen: bool = False self.terminalProtocol: Any self.transport: Any insults.ServerProtocol.__init__(self, prot, *a, **kw) if prot is protocol.HoneyPotExecProtocol: self.type = "e" # Execcmd else: self.type = "i" # Interactive def getSessionId(self): transportId = self.transport.session.conn.transport.transportId channelId = self.transport.session.id return (transportId, channelId) def connectionMade(self) -> None: transportId, channelId = self.getSessionId() self.startTime: float = time.time() if self.ttylogEnabled: self.ttylogFile = "{}/{}-{}-{}{}.log".format( self.ttylogPath, time.strftime("%Y%m%d-%H%M%S"), transportId, channelId, self.type, ) ttylog.ttylog_open(self.ttylogFile, self.startTime) self.ttylogOpen = True self.ttylogSize = 0 self.stdinlogFile = "{}/{}-{}-{}-stdin.log".format( self.downloadPath, time.strftime("%Y%m%d-%H%M%S"), transportId, channelId, ) if self.type == "e": self.stdinlogOpen = True # log the command into ttylog if self.ttylogEnabled: (sess, cmd) = self.protocolArgs ttylog.ttylog_write(self.ttylogFile, len(cmd), ttylog.TYPE_INTERACT, time.time(), cmd) else: self.stdinlogOpen = False insults.ServerProtocol.connectionMade(self) if self.type == "e": self.terminalProtocol.execcmd.encode("utf8") def write(self, data: bytes) -> None: if self.ttylogEnabled and self.ttylogOpen: ttylog.ttylog_write(self.ttylogFile, len(data), ttylog.TYPE_OUTPUT, time.time(), data) self.ttylogSize += len(data) insults.ServerProtocol.write(self, data) def dataReceived(self, data: bytes) -> None: """ Input received from user """ self.bytesReceived += len(data) if self.bytesReceivedLimit and self.bytesReceived > self.bytesReceivedLimit: log.msg(format="Data upload limit reached") self.eofReceived() return if self.stdinlogOpen: with open(self.stdinlogFile, "ab") as f: f.write(data) elif self.ttylogEnabled and self.ttylogOpen: ttylog.ttylog_write(self.ttylogFile, len(data), ttylog.TYPE_INPUT, time.time(), data) # prevent crash if something like this was passed: # echo cmd ; exit; \n\n if self.terminalProtocol: insults.ServerProtocol.dataReceived(self, data) def eofReceived(self) -> None: """ Receive channel close and pass on to terminal """ if self.terminalProtocol: self.terminalProtocol.eofReceived() def loseConnection(self) -> None: """ Override super to remove the terminal reset on logout """ self.transport.loseConnection() def connectionLost(self, reason): """ FIXME: this method is called 4 times on logout.... it's called once from Avatar.closed() if disconnected """ if self.stdinlogOpen: try: with open(self.stdinlogFile, "rb") as f: shasum = hashlib.sha256(f.read()).hexdigest() shasumfile = os.path.join(self.downloadPath, shasum) if os.path.exists(shasumfile): os.remove(self.stdinlogFile) duplicate = True else: os.rename(self.stdinlogFile, shasumfile) duplicate = False log.msg( eventid="cowrie.session.file_download", format= "Saved stdin contents with SHA-256 %(shasum)s to %(outfile)s", duplicate=duplicate, outfile=shasumfile, shasum=shasum, destfile="", ) except OSError: pass finally: self.stdinlogOpen = False if self.redirFiles: for rp in self.redirFiles: rf = rp[0] if rp[1]: url = rp[1] else: url = rf[rf.find("redir_") + len("redir_"):] try: if not os.path.exists(rf): continue if os.path.getsize(rf) == 0: os.remove(rf) continue with open(rf, "rb") as f: shasum = hashlib.sha256(f.read()).hexdigest() shasumfile = os.path.join(self.downloadPath, shasum) if os.path.exists(shasumfile): os.remove(rf) duplicate = True else: os.rename(rf, shasumfile) duplicate = False log.msg( eventid="cowrie.session.file_download", format= "Saved redir contents with SHA-256 %(shasum)s to %(outfile)s", duplicate=duplicate, outfile=shasumfile, shasum=shasum, destfile=url, ) except OSError: pass self.redirFiles.clear() if self.ttylogEnabled and self.ttylogOpen: ttylog.ttylog_close(self.ttylogFile, time.time()) self.ttylogOpen = False shasum = ttylog.ttylog_inputhash(self.ttylogFile) shasumfile = os.path.join(self.ttylogPath, shasum) if os.path.exists(shasumfile): duplicate = True os.remove(self.ttylogFile) else: duplicate = False os.rename(self.ttylogFile, shasumfile) umask = os.umask(0) os.umask(umask) os.chmod(shasumfile, 0o666 & ~umask) log.msg( eventid="cowrie.log.closed", format="Closing TTY Log: %(ttylog)s after %(duration)d seconds", ttylog=shasumfile, size=self.ttylogSize, shasum=shasum, duplicate=duplicate, duration=time.time() - self.startTime, ) insults.ServerProtocol.connectionLost(self, reason)