class command_ftpget(HoneyPotCommand): """ """ 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.download_path = CONFIG.get('honeypot', 'download_path') self.url_log = 'ftp://' if self.username: self.url_log = '{}{}'.format(self.url_log, self.username) if self.password: self.url_log = '{}:{}'.format(self.url_log, self.password) self.url_log = '{}@'.format(self.url_log) self.url_log = '{}{}'.format(self.url_log, self.host) if self.port != 21: self.url_log = '{}:{}'.format(self.url_log, self.port) self.url_log = '{}/{}'.format(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 CONFIG.has_option('honeypot', 'out_addr'): out_addr = (CONFIG.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=%s, port=%s, err=%s' % (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=%s, passwd=%s, err=%s' % (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_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 Command_wget(HoneyPotCommand): """ wget command """ limit_size: int = CowrieConfig.getint("honeypot", "download_limit_size", fallback=0) quiet: bool = False 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 proglen: int = 0 url: bytes host: str started: float def start(self): url: str 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: str = 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 = f"http://{url}" urldata = compat.urllib_parse.urlparse(url) self.host = urldata.hostname # 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.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: {}: Cannot open: No such file or directory\n". format(self.outfile)) self.exit() return self.artifact = Artifact("curl-download") if not self.quiet: tm = time.strftime("%Y-%m-%d %H:%M:%S") self.errorWrite(f"--{tm}-- {url}\n") self.errorWrite( f"Connecting to {self.host}:{urldata.port}... connected.\n") self.errorWrite("HTTP request sent, awaiting response... ") self.deferred = self.wgetDownload(url) if self.deferred: self.deferred.addCallback(self.success) self.deferred.addErrback(self.error) def wgetDownload(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=True, headers=headers, timeout=10) return deferred def handle_CTRL_C(self) -> None: self.write("^C\n") self.exit() def success(self, response): """ successful treq get """ # TODO possible this is UNKNOWN_LENGTH if response.length != UNKNOWN_LENGTH: self.totallength = response.length else: self.totallength = 0 if self.limit_size > 0 and self.totallength > self.limit_size: log.msg( f"Not saving URL ({self.url.decode()}) (size: {self.totallength}) exceeds file size limit ({self.limit_size})" ) self.exit() return self.started = time.time() if not self.quiet: self.errorWrite("200 OK\n") if response.headers.hasHeader(b"content-type"): self.contenttype = response.headers.getRawHeaders( b"content-type")[0].decode() else: self.contenttype = "text/whatever" if not self.quiet: if response.length != UNKNOWN_LENGTH: self.errorWrite( f"Length: {self.totallength} ({sizeof_fmt(self.totallength)}) [{self.contenttype}]\n" ) else: self.errorWrite(f"Length: unspecified [{self.contenttype}]\n") if self.outfile is None: self.errorWrite("Saving to: `STDOUT'\n\n") else: self.errorWrite(f"Saving to: `{self.outfile}'\n\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) self.speed = self.currentlength / (time.time() - self.started) if self.totallength != 0: percent = int(self.currentlength / self.totallength * 100) spercent = f"{percent}%" eta = (self.totallength - self.currentlength) / self.speed else: spercent = f"{self.currentlength / 1000}K" percent = 0 eta = 0.0 s = "\r%s [%s] %s %dK/s eta %s" % ( spercent.rjust(3), ("%s>" % (int(39.0 / 100.0 * percent) * "=")).ljust(39), splitthousands(str(int(self.currentlength))).ljust(12), self.speed / 1000, tdiff(eta), ) if not self.quiet: self.errorWrite(s.ljust(self.proglen)) self.proglen = len(s) self.lastupdate = time.time() if not self.outfile: self.writeBytes(data) def collectioncomplete(self, data: None) -> None: """ this gets called once collection is complete """ self.artifact.close() self.totallength = self.currentlength if not self.quiet: self.errorWrite("\r100%%[%s] %s %dK/s" % ( "%s>" % (38 * "="), splitthousands(str(int(self.totallength))).ljust(12), self.speed / 1000, )) self.errorWrite("\n\n") self.errorWrite("%s (%d KB/s) - `%s' saved [%d/%d]\n\n" % ( time.strftime("%Y-%m-%d %H:%M:%S"), self.speed / 1000, self.outfile, self.currentlength, self.totallength, )) # 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 errors """ print(response) if response.check(error.DNSLookupError) is not None: self.write( f"Resolving no.such ({self.host})... failed: nodename nor servname provided, or not known.\n" ) self.write(f"wget: unable to resolve host address ‘{self.host}’\n") self.exit() return 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.decode(), ) self.exit()
class Command_ftpget(HoneyPotCommand): """ ftpget command """ download_path = CowrieConfig.get("honeypot", "download_path") verbose: bool host: str port: int username: str password: str remote_path: str remote_dir: str remote_file: str artifactFile: Artifact 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 '{}': No such file or directory".format( 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(f"Connecting to {self.host}\n" ) # 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(f"ftpget: cmd USER {self.username}\n") else: self.write("ftpget: cmd USER anonymous\n") if self.password: self.write(f"ftpget: cmd PASS {self.password}\n") 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( f"ftpget: unexpected server response to USER: {str(e)}\n") 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(f"ftpget: cmd SIZE {self.remote_path}\n") self.write(f"ftpget: cmd RETR {self.remote_path}\n") try: ftp.cwd(self.remote_dir) ftp.retrbinary(f"RETR {self.remote_file}", self.artifactFile.write) except Exception as e: log.msg(f"FTP retrieval failed: {str(e)}") self.write( f"ftpget: unexpected server response to USER: {str(e)}\n") 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_ftpget(HoneyPotCommand): 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.download_path = CONFIG.get('honeypot', 'download_path') self.url_log = 'ftp://' if self.username: self.url_log = '{}{}'.format(self.url_log, self.username) if self.password: self.url_log = '{}:{}'.format(self.url_log, self.password) self.url_log = '{}@'.format(self.url_log) self.url_log = '{}{}'.format(self.url_log, self.host) if self.port != 21: self.url_log = '{}:{}'.format(self.url_log, self.port) self.url_log = '{}/{}'.format(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 CONFIG.has_option('honeypot', 'out_addr'): out_addr = (CONFIG.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=%s, port=%s, err=%s' % (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=%s, passwd=%s, err=%s' % (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