def destroy_guest(self, domain, snapshot): if not self.ready: return try: # destroy the domain in qemu domain.destroy() # we want to remove the snapshot if either: # - explicitely set save_snapshots to False # - no snapshot dir was defined (using cowrie's root dir) - should not happen but prevent it if ((not CowrieConfig.getboolean( "backend_pool", "save_snapshots", fallback=True) or CowrieConfig.get( "backend_pool", "snapshot_path", fallback=None) is None) and os.path.exists(snapshot) and os.path.isfile(snapshot)): os.remove(snapshot) # destroy its disk snapshot except Exception as error: log.err( eventid="cowrie.backend_pool.qemu", format="Error destroying guest: %(error)s", error=error, )
def load(self): """ load the user db """ try: with open( "{}/userdb.txt".format(CowrieConfig.get("honeypot", "etc_path")) ) as db: userdb = db.readlines() except OSError: log.msg("Could not read etc/userdb.txt, default database activated") userdb = _USERDB_DEFAULTS for user in userdb: if not user.startswith("#"): try: login = user.split(":")[0].encode("utf8") password = user.split(":")[2].strip().encode("utf8") except IndexError: continue else: self.adduser(login, password)
def open(self, filename, openFlags, mode): """ #log.msg("fs.open %s" % filename) #if (openFlags & os.O_APPEND == os.O_APPEND): # log.msg("fs.open append") #if (openFlags & os.O_CREAT == os.O_CREAT): # log.msg("fs.open creat") #if (openFlags & os.O_TRUNC == os.O_TRUNC): # log.msg("fs.open trunc") #if (openFlags & os.O_EXCL == os.O_EXCL): # log.msg("fs.open excl") # treat O_RDWR same as O_WRONLY """ if openFlags & os.O_WRONLY == os.O_WRONLY or openFlags & os.O_RDWR == os.O_RDWR: # strip executable bit hostmode = mode & ~(111) hostfile = "{}/{}_sftp_{}".format( CowrieConfig.get("honeypot", "download_path"), time.strftime("%Y%m%d-%H%M%S"), re.sub("[^A-Za-z0-9]", "_", filename), ) self.mkfile(filename, 0, 0, 0, stat.S_IFREG | mode) fd = os.open(hostfile, openFlags, hostmode) self.update_realfile(self.getfile(filename), hostfile) self.tempfiles[fd] = hostfile self.filenames[fd] = filename return fd elif openFlags & os.O_RDONLY == os.O_RDONLY: return None return None
def __init__(self) -> None: self.sessions: dict[str, str] = {} self.ips: dict[str, str] = {} # Need these for each individual transport, or else the session numbers overlap self.sshRegex: Pattern[str] = re.compile(".*SSHTransport,([0-9]+),[0-9a-f:.]+$") self.telnetRegex: Pattern[str] = re.compile( ".*TelnetTransport,([0-9]+),[0-9a-f:.]+$" ) self.sensor: str = CowrieConfig.get( "honeypot", "sensor_name", fallback=socket.gethostname() ) self.timeFormat: str # use Z for UTC (Zulu) time, it's shorter. if "TZ" in environ and environ["TZ"] == "UTC": self.timeFormat = "%Y-%m-%dT%H:%M:%S.%fZ" else: self.timeFormat = "%Y-%m-%dT%H:%M:%S.%f%z" # Event trigger so that stop() is called by the reactor when stopping reactor.addSystemEventTrigger("before", "shutdown", self.stop) # type: ignore self.start()
class Group: """ This class contains code to handle the groups and their properties in /etc/group. """ group_file = "{}/etc/group".format( CowrieConfig.get("honeypot", "contents_path")) group: List[Dict[str, Any]] def __init__(self): self.load() def load(self) -> None: """ Load /etc/group """ self.group = [] with open(self.group_file) as f: while True: rawline = f.readline() if not rawline: break line = rawline.strip() if not line: continue if line.startswith("#"): continue (gr_name, gr_passwd, gr_gid, gr_mem) = line.split(":") e: Dict[str, Union[str, int]] = {} e["gr_name"] = gr_name try: e["gr_gid"] = int(gr_gid) except ValueError: e["gr_gid"] = 1001 e["gr_mem"] = gr_mem self.group.append(e) def save(self) -> None: """ Save the group db Note: this is subject to races between cowrie instances, but hey ... """ # with open(self.group_file, 'w') as f: # for (login, uid, passwd) in self.userdb: # f.write('%s:%d:%s\n' % (login, uid, passwd)) raise NotImplementedError def getgrnam(self, name: str) -> Dict[str, Any]: """ Get group entry for groupname """ for e in self.group: if name == e["gr_name"]: return e raise KeyError("getgrnam(): name not found in group file: " + name) def getgrgid(self, uid: int) -> Dict[str, Any]: """ Get group entry for gid """ for e in self.group: if uid == e["gr_gid"]: return e raise KeyError("getgruid(): uid not found in group file: " + str(uid))
def kernel_build_string(): return CowrieConfig.get("shell", "kernel_build_string", fallback="#1 SMP Debian 3.2.68-1+deb7u1")
def kernel_name(): return CowrieConfig.get("shell", "kernel_name", fallback="Linux")
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_ftpget(HoneyPotCommand): """ ftpget command """ download_path = CowrieConfig.get("honeypot", "download_path") def help(self): self.write( """BusyBox v1.20.2 (2016-06-22 15:12:53 EDT) multi-call binary. Usage: ftpget [OPTIONS] HOST [LOCAL_FILE] REMOTE_FILE Download a file via FTP -c Continue previous transfer -v Verbose -u USER Username -p PASS Password -P NUM Port\n\n""") def start(self): try: optlist, args = getopt.getopt(self.args, "cvu:p:P:") except getopt.GetoptError: self.help() self.exit() return if len(args) < 2: self.help() self.exit() return self.verbose = False self.username = "" self.password = "" self.port = 21 self.host = "" self.local_file = "" self.remote_path = "" for opt in optlist: if opt[0] == "-v": self.verbose = True elif opt[0] == "-u": self.username = opt[1] elif opt[0] == "-p": self.password = opt[1] elif opt[0] == "-P": try: self.port = int(opt[1]) except ValueError: pass if len(args) == 2: self.host, self.remote_path = args elif len(args) >= 3: self.host, self.local_file, self.remote_path = args[:3] self.remote_dir = os.path.dirname(self.remote_path) self.remote_file = os.path.basename(self.remote_path) if not self.local_file: self.local_file = self.remote_file fakeoutfile = self.fs.resolve_path(self.local_file, self.protocol.cwd) path = os.path.dirname(fakeoutfile) if not path or not self.fs.exists(path) or not self.fs.isdir(path): self.write("ftpget: can't open '%s': No such file or directory" % self.local_file) self.exit() return self.url_log = "ftp://" if self.username: self.url_log = f"{self.url_log}{self.username}" if self.password: self.url_log = f"{self.url_log}:{self.password}" self.url_log = f"{self.url_log}@" self.url_log = f"{self.url_log}{self.host}" if self.port != 21: self.url_log = f"{self.url_log}:{self.port}" self.url_log = f"{self.url_log}/{self.remote_path}" self.artifactFile = Artifact(self.local_file) result = self.ftp_download() self.artifactFile.close() if not result: # log to cowrie.log log.msg( format="Attempt to download file(s) from URL (%(url)s) failed", url=self.url_log, ) self.protocol.logDispatch( eventid="cowrie.session.file_download.failed", format="Attempt to download file(s) from URL (%(url)s) failed", url=self.url_log, ) self.exit() return # log to cowrie.log log.msg( format= "Downloaded URL (%(url)s) with SHA-256 %(shasum)s to %(outfile)s", url=self.url_log, 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=self.url_log, outfile=self.artifactFile.shasumFilename, shasum=self.artifactFile.shasum, destfile=self.local_file, ) # Update the honeyfs to point to downloaded file self.fs.mkfile(fakeoutfile, 0, 0, os.path.getsize(self.artifactFile.shasumFilename), 33188) self.fs.update_realfile(self.fs.getfile(fakeoutfile), self.artifactFile.shasumFilename) self.fs.chown(fakeoutfile, self.protocol.user.uid, self.protocol.user.gid) self.exit() def ftp_download(self): out_addr = ("", 0) if CowrieConfig.has_option("honeypot", "out_addr"): out_addr = (CowrieConfig.get("honeypot", "out_addr"), 0) ftp = FTP(source_address=out_addr) # connect if self.verbose: self.write("Connecting to %s\n" % self.host) # TODO: add its IP address after the host try: ftp.connect(host=self.host, port=self.port, timeout=30) except Exception as e: log.msg("FTP connect failed: host={}, port={}, err={}".format( self.host, self.port, str(e))) self.write( "ftpget: can't connect to remote host: Connection refused\n") return False # login if self.verbose: self.write("ftpget: cmd (null) (null)\n") if self.username: self.write("ftpget: cmd USER %s\n" % self.username) else: self.write("ftpget: cmd USER anonymous\n") if self.password: self.write("ftpget: cmd PASS %s\n" % self.password) else: self.write("ftpget: cmd PASS busybox@\n") try: ftp.login(user=self.username, passwd=self.password) except Exception as e: log.msg("FTP login failed: user={}, passwd={}, err={}".format( self.username, self.password, str(e))) self.write("ftpget: unexpected server response to USER: %s\n" % str(e)) try: ftp.quit() except socket.timeout: pass return False # download if self.verbose: self.write("ftpget: cmd TYPE I (null)\n") self.write("ftpget: cmd PASV (null)\n") self.write("ftpget: cmd SIZE %s\n" % self.remote_path) self.write("ftpget: cmd RETR %s\n" % self.remote_path) try: ftp.cwd(self.remote_dir) ftp.retrbinary("RETR %s" % self.remote_file, self.artifactFile.write) except Exception as e: log.msg("FTP retrieval failed: %s" % str(e)) self.write("ftpget: unexpected server response to USER: %s\n" % str(e)) try: ftp.quit() except socket.timeout: pass return False # quit if self.verbose: self.write("ftpget: cmd (null) (null)\n") self.write("ftpget: cmd QUIT (null)\n") try: ftp.quit() except socket.timeout: pass return True
def start(self): self.bot_token = CowrieConfig.get('output_telegram', 'bot_token') self.chat_id = CowrieConfig.get('output_telegram', 'chat_id')
def start(self): self.format = CowrieConfig.get("output_textlog", "format") self.outfile = open(CowrieConfig.get("output_textlog", "logfile"), "a")
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 if not self.quiet: self.errorWrite( "--{}-- {}\n".format( time.strftime("%Y-%m-%d %H:%M:%S"), url.decode("utf8") ) ) self.errorWrite(f"Connecting to {host}:{port}... connected.\n") self.errorWrite("HTTP request sent, awaiting response... ") # TODO: need to do full name resolution. try: if ipaddress.ip_address(host).is_private: self.errorWrite( "Resolving {} ({})... failed: nodename nor servname provided, or not known.\n".format( host, host ) ) self.errorWrite(f"wget: unable to resolve host address ‘{host}’\n") return None except ValueError: pass # 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) 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 create_guest(connection, mac_address, guest_unique_id): # lazy import to avoid exception if not using the backend_pool and libvirt not installed (#1185) import libvirt # get guest configurations configuration_file = os.path.join( CowrieConfig.get("backend_pool", "config_files_path", fallback="share/pool_configs"), CowrieConfig.get("backend_pool", "guest_config", fallback="default_guest.xml"), ) version_tag = CowrieConfig.get("backend_pool", "guest_tag", fallback="guest") base_image = CowrieConfig.get("backend_pool", "guest_image_path") hypervisor = CowrieConfig.get("backend_pool", "guest_hypervisor", fallback="qemu") memory = CowrieConfig.getint("backend_pool", "guest_memory", fallback=128) qemu_machine = CowrieConfig.get("backend_pool", "guest_qemu_machine", fallback="pc-q35-3.1") # check if base image exists if not os.path.isfile(base_image): log.msg( eventid="cowrie.backend_pool.guest_handler", format="Base image provided was not found: %(base_image)s", base_image=base_image, ) os._exit(1) # only in some cases, like wrt kernel_image = CowrieConfig.get("backend_pool", "guest_kernel_image", fallback="") # get a directory to save snapshots, even if temporary try: # guest configuration, to be read by qemu, needs an absolute path snapshot_path = backend_pool.util.to_absolute_path( CowrieConfig.get("backend_pool", "snapshot_path")) except NoOptionError: snapshot_path = os.getcwd() # create a disk snapshot to be used by the guest disk_img = os.path.join(snapshot_path, f"snapshot-{version_tag}-{guest_unique_id}.qcow2") if not backend_pool.libvirt.snapshot_handler.create_disk_snapshot( base_image, disk_img): log.msg( eventid="cowrie.backend_pool.guest_handler", format="There was a problem creating the disk snapshot.", ) raise QemuGuestError() guest_xml = backend_pool.util.read_file(configuration_file) guest_config = guest_xml.format( guest_name="cowrie-" + version_tag + "_" + guest_unique_id, disk_image=disk_img, base_image=base_image, kernel_image=kernel_image, hypervisor=hypervisor, memory=memory, qemu_machine=qemu_machine, mac_address=mac_address, network_name="cowrie", ) try: dom = connection.createXML(guest_config, 0) if dom is None: log.err( eventid="cowrie.backend_pool.guest_handler", format="Failed to create a domain from an XML definition.", ) exit(1) log.msg( eventid="cowrie.backend_pool.guest_handler", format="Guest %(name)s has booted", name=dom.name(), ) return dom, disk_img except libvirt.libvirtError as e: log.err( eventid="cowrie.backend_pool.guest_handler", format="Error booting guest: %(error)s", error=e, ) raise e
def start(self): self.tolerance_attempts = CowrieConfig.getint("output_abuseipdb", "tolerance_attempts", fallback=10) self.state_path = CowrieConfig.get("output_abuseipdb", "dump_path") self.state_path = Path(*(d for d in self.state_path.split("/"))) self.state_dump = self.state_path / DUMP_FILE self.logbook = LogBook(self.tolerance_attempts, self.state_dump) # Pass our instance of LogBook() to Reporter() so we don't end up # working with different records. self.reporter = Reporter(self.logbook, self.tolerance_attempts) # We store the LogBook state any time a shutdown occurs. The rest of # our start-up is just for loading and cleaning the previous state try: with open(self.state_dump, "rb") as f: self.logbook.update(pickle.load(f)) # Check to see if we're still asleep after receiving a Retry-After # header in a previous response if self.logbook["sleeping"]: t_wake = self.logbook["sleep_until"] t_now = time() if t_wake > t_now: # If we're meant to be asleep, we'll set logbook.sleep to # true and logbook.sleep_until to the time we can wake-up self.logbook.sleeping = True self.logbook.sleep_until = t_wake # and we set an alarm so the reactor knows when he can drag # us back out of bed reactor.callLater(t_wake - t_now, self.logbook.wakeup) del self.logbook["sleeping"] del self.logbook["sleep_until"] tolerated = self.logbook.pop("tolerated") except (pickle.UnpicklingError, FileNotFoundError, KeyError): if self.state_path.exists(): pass else: # If we don't already have an abuseipdb directory, let's make # one with the necessary permissions now. Path(self.state_path).mkdir(mode=0o700, parents=False, exist_ok=False) # And we do a clean-up to make sure that we're not carrying any expired # entries. The clean-up task ends by calling itself in a callLater, # thus running every CLEAN_DUMP_SCHED seconds until the end of time. self.logbook.cleanup_and_dump_state() # If tolerance_attempts > the previous setting, we need to change the # maximum length of the deque for any previously seen IP that we're # loading, otherwise we'd potentially have IPs that may never trigger # a report try: if tolerated != self.tolerance_attempts: for k in self.logbook: if self.logbook[k].__class__() == deque(): self.logbook[k] = deque([*self.logbook[k]], maxlen=self.tolerance_attempts) except UnboundLocalError: pass log.msg( eventid="cowrie.abuseipdb.started", format= f"AbuseIPDB Plugin version {__version__} started. Currently in beta.", )
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
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 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)
import os from datetime import datetime from twisted.python import log import cowrie.core.output from cowrie.core.config import CowrieConfig token = CowrieConfig.get("output_csirtg", "token", fallback="a1b2c3d4") if token == "a1b2c3d4": log.msg("output_csirtg: token not found in configuration file") exit(1) os.environ["CSIRTG_TOKEN"] = token import csirtgsdk # noqa: E402 class Output(cowrie.core.output.Output): """ CSIRTG output """ def start(self): """ Start the output module. Note that csirtsdk is imported here because it reads CSIRTG_TOKEN on import Cowrie sets this environment variable. """ self.user = CowrieConfig.get("output_csirtg", "username") self.feed = CowrieConfig.get("output_csirtg", "feed") self.debug = CowrieConfig.getboolean("output_csirtg", "debug", fallback=False)
def ftp_download(self): out_addr = ("", 0) if CowrieConfig.has_option("honeypot", "out_addr"): out_addr = (CowrieConfig.get("honeypot", "out_addr"), 0) ftp = FTP(source_address=out_addr) # connect if self.verbose: self.write("Connecting to %s\n" % self.host) # TODO: add its IP address after the host try: ftp.connect(host=self.host, port=self.port, timeout=30) except Exception as e: log.msg("FTP connect failed: host={}, port={}, err={}".format( self.host, self.port, str(e))) self.write( "ftpget: can't connect to remote host: Connection refused\n") return False # login if self.verbose: self.write("ftpget: cmd (null) (null)\n") if self.username: self.write("ftpget: cmd USER %s\n" % self.username) else: self.write("ftpget: cmd USER anonymous\n") if self.password: self.write("ftpget: cmd PASS %s\n" % self.password) else: self.write("ftpget: cmd PASS busybox@\n") try: ftp.login(user=self.username, passwd=self.password) except Exception as e: log.msg("FTP login failed: user={}, passwd={}, err={}".format( self.username, self.password, str(e))) self.write("ftpget: unexpected server response to USER: %s\n" % str(e)) try: ftp.quit() except socket.timeout: pass return False # download if self.verbose: self.write("ftpget: cmd TYPE I (null)\n") self.write("ftpget: cmd PASV (null)\n") self.write("ftpget: cmd SIZE %s\n" % self.remote_path) self.write("ftpget: cmd RETR %s\n" % self.remote_path) try: ftp.cwd(self.remote_dir) ftp.retrbinary("RETR %s" % self.remote_file, self.artifactFile.write) except Exception as e: log.msg("FTP retrieval failed: %s" % str(e)) self.write("ftpget: unexpected server response to USER: %s\n" % str(e)) try: ftp.quit() except socket.timeout: pass return False # quit if self.verbose: self.write("ftpget: cmd (null) (null)\n") self.write("ftpget: cmd QUIT (null)\n") try: ftp.quit() except socket.timeout: pass return True
class command_scp(HoneyPotCommand): """ scp command """ download_path = CowrieConfig.get("honeypot", "download_path") download_path_uniq = CowrieConfig.get( "honeypot", "download_path_uniq", fallback=download_path ) out_dir: str = "" def help(self): self.write( """usage: scp [-12346BCpqrv] [-c cipher] [-F ssh_config] [-i identity_file] [-l limit] [-o ssh_option] [-P port] [-S program] [[user@]host1:]file1 ... [[user@]host2:]file2\n""" ) def start(self): try: optlist, args = getopt.getopt(self.args, "12346BCpqrvfstdv:cFiloPS:") except getopt.GetoptError: self.help() self.exit() return self.out_dir = "" for opt in optlist: if opt[0] == "-d": self.out_dir = args[0] break if self.out_dir: outdir = self.fs.resolve_path(self.out_dir, self.protocol.cwd) if not self.fs.exists(outdir): self.errorWrite(f"-scp: {self.out_dir}: No such file or directory\n") self.exit() self.write("\x00") self.write("\x00") self.write("\x00") self.write("\x00") self.write("\x00") self.write("\x00") self.write("\x00") self.write("\x00") self.write("\x00") self.write("\x00") def lineReceived(self, line): log.msg( eventid="cowrie.session.file_download", realm="scp", input=line, format="INPUT (%(realm)s): %(input)s", ) self.protocol.terminal.write("\x00") def drop_tmp_file(self, data, name): tmp_fname = "{}-{}-{}-scp_{}".format( time.strftime("%Y%m%d-%H%M%S"), self.protocol.getProtoTransport().transportId, self.protocol.terminal.transport.session.id, re.sub("[^A-Za-z0-9]", "_", name), ) self.safeoutfile = os.path.join(self.download_path, tmp_fname) with open(self.safeoutfile, "wb+") as f: f.write(data) def save_file(self, data, fname): self.drop_tmp_file(data, fname) if os.path.exists(self.safeoutfile): with open(self.safeoutfile, "rb"): shasum = hashlib.sha256(data).hexdigest() hash_path = os.path.join(self.download_path_uniq, shasum) # If we have content already, delete temp file if not os.path.exists(hash_path): os.rename(self.safeoutfile, hash_path) duplicate = False else: os.remove(self.safeoutfile) duplicate = True log.msg( format='SCP Uploaded file "%(filename)s" to %(outfile)s', eventid="cowrie.session.file_upload", filename=os.path.basename(fname), duplicate=duplicate, url=fname, outfile=shasum, shasum=shasum, destfile=fname, ) self.safeoutfile = None # Update the honeyfs to point to downloaded file self.fs.update_realfile(self.fs.getfile(fname), hash_path) self.fs.chown(fname, self.protocol.user.uid, self.protocol.user.gid) def parse_scp_data(self, data): # scp data format: # C0XXX filesize filename\nfile_data\x00 # 0XXX - file permissions # filesize - size of file in bytes in decimal notation pos = data.find("\n") if pos != -1: header = data[:pos] pos += 1 if re.match(r"^C0[\d]{3} [\d]+ [^\s]+$", header): r = re.search(r"C(0[\d]{3}) ([\d]+) ([^\s]+)", header) if r and r.group(1) and r.group(2) and r.group(3): dend = pos + int(r.group(2)) if dend > len(data): dend = len(data) d = data[pos:dend] if self.out_dir: fname = os.path.join(self.out_dir, r.group(3)) else: fname = r.group(3) outfile = self.fs.resolve_path(fname, self.protocol.cwd) try: self.fs.mkfile(outfile, 0, 0, r.group(2), r.group(1)) except fs.FileNotFound: # The outfile locates at a non-existing directory. self.errorWrite(f"-scp: {outfile}: No such file or directory\n") self.safeoutfile = None return "" self.save_file(d, outfile) data = data[dend + 1 :] # cut saved data + \x00 else: data = "" else: data = "" return data def handle_CTRL_D(self): if ( self.protocol.terminal.stdinlogOpen and self.protocol.terminal.stdinlogFile and os.path.exists(self.protocol.terminal.stdinlogFile) ): with open(self.protocol.terminal.stdinlogFile, "rb") as f: data = f.read() header = data[: data.find(b"\n")] if re.match(r"C0[\d]{3} [\d]+ [^\s]+", header.decode()): data = data[data.find(b"\n") + 1 :] else: data = "" if data: with open(self.protocol.terminal.stdinlogFile, "wb") as f: f.write(data) 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 start(self): """ Start output plugin """ self.apiKey = CowrieConfig.get("output_malshare", "api_key")
def hardware_platform(): return CowrieConfig.get("shell", "hardware_platform", fallback="x86_64")
def buildProtocol(self, addr): """ Create an instance of the server side of the SSH protocol. @type addr: L{twisted.internet.interfaces.IAddress} provider @param addr: The address at which the server will listen. @rtype: L{cowrie.ssh.transport.HoneyPotSSHTransport} @return: The built transport. """ t: transport.SSHServerTransport if self.backend == "proxy": t = proxyTransport.FrontendSSHTransport() else: t = shellTransport.HoneyPotSSHTransport() t.ourVersionString = self.ourVersionString t.supportedPublicKeys = list(self.privateKeys.keys()) if not self.primes: ske = t.supportedKeyExchanges[:] if b"diffie-hellman-group-exchange-sha1" in ske: ske.remove(b"diffie-hellman-group-exchange-sha1") log.msg("No moduli, no diffie-hellman-group-exchange-sha1") if b"diffie-hellman-group-exchange-sha256" in ske: ske.remove(b"diffie-hellman-group-exchange-sha256") log.msg("No moduli, no diffie-hellman-group-exchange-sha256") t.supportedKeyExchanges = ske try: t.supportedCiphers = [ i.encode("utf-8") for i in CowrieConfig.get("ssh", "ciphers").split(",") ] except NoOptionError: # Reorder supported ciphers to resemble current openssh more t.supportedCiphers = [ b"aes128-ctr", b"aes192-ctr", b"aes256-ctr", b"aes256-cbc", b"aes192-cbc", b"aes128-cbc", b"3des-cbc", b"blowfish-cbc", b"cast128-cbc", ] try: t.supportedMACs = [ i.encode("utf-8") for i in CowrieConfig.get("ssh", "macs").split(",") ] except NoOptionError: # SHA1 and MD5 are considered insecure now. Use better algos # like SHA-256 and SHA-384 t.supportedMACs = [ b"hmac-sha2-512", b"hmac-sha2-384", b"hmac-sha2-256", b"hmac-sha1", b"hmac-md5", ] try: t.supportedCompressions = [ i.encode("utf-8") for i in CowrieConfig.get("ssh", "compression").split(",") ] except NoOptionError: t.supportedCompressions = [b"*****@*****.**", b"zlib", b"none"] t.factory = self return t
def kernel_version(): return CowrieConfig.get("shell", "kernel_version", fallback="3.2.0-4-amd64")
class CowrieSSHFactory(factory.SSHFactory): """ This factory creates HoneyPotSSHTransport instances They listen directly to the TCP port """ starttime: Optional[float] = None privateKeys: Dict[bytes, bytes] = {} publicKeys: Dict[bytes, bytes] = {} primes = None portal: Optional[tp.Portal] = None # gets set by plugin ourVersionString: str = CowrieConfig.get( "ssh", "version", fallback="SSH-2.0-OpenSSH_6.0p1 Debian-4+deb7u2") def __init__(self, backend, pool_handler): self.pool_handler = pool_handler self.backend: str = backend self.services = { b"ssh-userauth": ProxySSHAuthServer if self.backend == "proxy" else HoneyPotSSHUserAuthServer, b"ssh-connection": connection.CowrieSSHConnection, } super().__init__() def logDispatch(self, **args): """ Special delivery to the loggers to avoid scope problems """ args["sessionno"] = "S{}".format(args["sessionno"]) for output in self.tac.output_plugins: # type: ignore[attr-defined] output.logDispatch(**args) def startFactory(self): # For use by the uptime command self.starttime = time.time() # Load/create keys try: public_key_auth = [ i.encode("utf-8") for i in CowrieConfig.get("ssh", "public_key_auth").split(",") ] except NoOptionError: # no keys defined, use the three most common pub keys of OpenSSH public_key_auth = [ b"ssh-rsa", b"ecdsa-sha2-nistp256", b"ssh-ed25519" ] for key in public_key_auth: if key == b"ssh-rsa": rsaPubKeyString, rsaPrivKeyString = cowriekeys.getRSAKeys() self.publicKeys[key] = keys.Key.fromString( data=rsaPubKeyString) self.privateKeys[key] = keys.Key.fromString( data=rsaPrivKeyString) elif key == b"ssh-dss": dsaaPubKeyString, dsaPrivKeyString = cowriekeys.getDSAKeys() self.publicKeys[key] = keys.Key.fromString( data=dsaaPubKeyString) self.privateKeys[key] = keys.Key.fromString( data=dsaPrivKeyString) elif key == b"ecdsa-sha2-nistp256": ecdsaPuKeyString, ecdsaPrivKeyString = cowriekeys.getECDSAKeys( ) self.publicKeys[key] = keys.Key.fromString( data=ecdsaPuKeyString) self.privateKeys[key] = keys.Key.fromString( data=ecdsaPrivKeyString) elif key == b"ssh-ed25519": ed25519PubKeyString, ed25519PrivKeyString = cowriekeys.geted25519Keys( ) self.publicKeys[key] = keys.Key.fromString( data=ed25519PubKeyString) self.privateKeys[key] = keys.Key.fromString( data=ed25519PrivKeyString) _modulis = "/etc/ssh/moduli", "/private/etc/moduli" for _moduli in _modulis: try: self.primes = primes.parseModuliFile(_moduli) break except OSError: pass # this can come from backend in the future, check HonSSH's slim client self.ourVersionString = CowrieConfig.get( "ssh", "version", fallback="SSH-2.0-OpenSSH_6.0p1 Debian-4+deb7u2") factory.SSHFactory.startFactory(self) log.msg("Ready to accept SSH connections") def stopFactory(self): factory.SSHFactory.stopFactory(self) def buildProtocol(self, addr): """ Create an instance of the server side of the SSH protocol. @type addr: L{twisted.internet.interfaces.IAddress} provider @param addr: The address at which the server will listen. @rtype: L{cowrie.ssh.transport.HoneyPotSSHTransport} @return: The built transport. """ t: transport.SSHServerTransport if self.backend == "proxy": t = proxyTransport.FrontendSSHTransport() else: t = shellTransport.HoneyPotSSHTransport() t.ourVersionString = self.ourVersionString t.supportedPublicKeys = list(self.privateKeys.keys()) if not self.primes: ske = t.supportedKeyExchanges[:] if b"diffie-hellman-group-exchange-sha1" in ske: ske.remove(b"diffie-hellman-group-exchange-sha1") log.msg("No moduli, no diffie-hellman-group-exchange-sha1") if b"diffie-hellman-group-exchange-sha256" in ske: ske.remove(b"diffie-hellman-group-exchange-sha256") log.msg("No moduli, no diffie-hellman-group-exchange-sha256") t.supportedKeyExchanges = ske try: t.supportedCiphers = [ i.encode("utf-8") for i in CowrieConfig.get("ssh", "ciphers").split(",") ] except NoOptionError: # Reorder supported ciphers to resemble current openssh more t.supportedCiphers = [ b"aes128-ctr", b"aes192-ctr", b"aes256-ctr", b"aes256-cbc", b"aes192-cbc", b"aes128-cbc", b"3des-cbc", b"blowfish-cbc", b"cast128-cbc", ] try: t.supportedMACs = [ i.encode("utf-8") for i in CowrieConfig.get("ssh", "macs").split(",") ] except NoOptionError: # SHA1 and MD5 are considered insecure now. Use better algos # like SHA-256 and SHA-384 t.supportedMACs = [ b"hmac-sha2-512", b"hmac-sha2-384", b"hmac-sha2-256", b"hmac-sha1", b"hmac-md5", ] try: t.supportedCompressions = [ i.encode("utf-8") for i in CowrieConfig.get("ssh", "compression").split(",") ] except NoOptionError: t.supportedCompressions = [b"*****@*****.**", b"zlib", b"none"] t.factory = self return t
def operating_system(): return CowrieConfig.get("shell", "operating_system", fallback="GNU/Linux")
def __init__(self, protocol, *args): self.protocol = protocol self.args = list(args) self.environ = self.protocol.cmdstack[0].environ self.fs = self.protocol.fs self.data: bytes = None # output data self.input_data: Optional[ bytes] = None # used to store STDIN data passed via PIPE self.writefn: Callable[[bytes], None] = self.protocol.pp.outReceived self.errorWritefn: Callable[[bytes], None] = self.protocol.pp.errReceived # MS-DOS style redirect handling, inside the command # TODO: handle >>, 2>, etc if ">" in self.args or ">>" in self.args: if self.args[-1] in [">", ">>"]: self.errorWrite("-bash: parse error near '\\n' \n") return self.writtenBytes = 0 self.writefn = self.write_to_file if ">>" in self.args: index = self.args.index(">>") b_append = True else: index = self.args.index(">") b_append = False self.outfile = self.fs.resolve_path(str(self.args[(index + 1)]), self.protocol.cwd) del self.args[index:] p = self.fs.getfile(self.outfile) if (not p or not p[fs.A_REALFILE] or p[fs.A_REALFILE].startswith("honeyfs") or not b_append): tmp_fname = "{}-{}-{}-redir_{}".format( time.strftime("%Y%m%d-%H%M%S"), self.protocol.getProtoTransport().transportId, self.protocol.terminal.transport.session.id, re.sub("[^A-Za-z0-9]", "_", self.outfile), ) self.safeoutfile = os.path.join( CowrieConfig.get("honeypot", "download_path"), tmp_fname) perm = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH try: self.fs.mkfile(self.outfile, 0, 0, 0, stat.S_IFREG | perm) except fs.FileNotFound: # The outfile locates at a non-existing directory. self.errorWrite( f"-bash: {self.outfile}: No such file or directory\n") self.writefn = self.write_to_failed self.outfile = None self.safeoutfile = "" except fs.PermissionDenied: # The outfile locates in a file-system that doesn't allow file creation self.errorWrite( f"-bash: {self.outfile}: Permission denied\n") self.writefn = self.write_to_failed self.outfile = None self.safeoutfile = "" else: with open(self.safeoutfile, "ab"): self.fs.update_realfile(self.fs.getfile(self.outfile), self.safeoutfile) else: self.safeoutfile = p[fs.A_REALFILE]
class Passwd: """ This class contains code to handle the users and their properties in /etc/passwd. Note that contrary to the name, it does not handle any passwords. """ passwd_file = "{}/etc/passwd".format( CowrieConfig.get("honeypot", "contents_path")) passwd: List[Dict[str, Any]] = [] def __init__(self) -> None: self.load() def load(self) -> None: """ Load /etc/passwd """ self.passwd = [] with open(self.passwd_file) as f: while True: rawline = f.readline() if not rawline: break line = rawline.strip() if not line: continue if line.startswith("#"): continue if len(line.split(":")) != 7: log.msg("Error parsing line `" + line + "` in <honeyfs>/etc/passwd") continue ( pw_name, pw_passwd, pw_uid, pw_gid, pw_gecos, pw_dir, pw_shell, ) = line.split(":") e: Dict[str, Union[str, int]] = {} e["pw_name"] = pw_name e["pw_passwd"] = pw_passwd e["pw_gecos"] = pw_gecos e["pw_dir"] = pw_dir e["pw_shell"] = pw_shell try: e["pw_uid"] = int(pw_uid) except ValueError: e["pw_uid"] = 1001 try: e["pw_gid"] = int(pw_gid) except ValueError: e["pw_gid"] = 1001 self.passwd.append(e) def save(self): """ Save the user db Note: this is subject to races between cowrie instances, but hey ... """ # with open(self.passwd_file, 'w') as f: # for (login, uid, passwd) in self.userdb: # f.write('%s:%d:%s\n' % (login, uid, passwd)) raise NotImplementedError def getpwnam(self, name: str) -> Dict[str, Any]: """ Get passwd entry for username """ for e in self.passwd: if e["pw_name"] == name: return e raise KeyError("getpwnam(): name not found in passwd file: " + name) def getpwuid(self, uid: int) -> Dict[str, Any]: """ Get passwd entry for uid """ for e in self.passwd: if uid == e["pw_uid"]: return e raise KeyError("getpwuid(): uid not found in passwd file: " + str(uid)) def setpwentry(self, name: str) -> Dict[str, Any]: """ If the user is not in /etc/passwd, creates a new user entry for the session """ # ensure consistent uid and gid seed_id = crc32(name.encode("utf-8")) seed(seed_id) e: Dict[str, Any] = {} e["pw_name"] = name e["pw_passwd"] = "x" e["pw_gecos"] = 0 e["pw_dir"] = "/home/" + name e["pw_shell"] = "/bin/bash" e["pw_uid"] = randint(1500, 10000) e["pw_gid"] = e["pw_uid"] self.passwd.append(e) return e
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