Ejemplo n.º 1
0
    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,
            )
Ejemplo n.º 2
0
    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)
Ejemplo n.º 3
0
    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
Ejemplo n.º 4
0
    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()
Ejemplo n.º 5
0
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))
Ejemplo n.º 6
0
def kernel_build_string():
    return CowrieConfig.get("shell",
                            "kernel_build_string",
                            fallback="#1 SMP Debian 3.2.68-1+deb7u1")
Ejemplo n.º 7
0
def kernel_name():
    return CowrieConfig.get("shell", "kernel_name", fallback="Linux")
Ejemplo n.º 8
0
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)
Ejemplo n.º 9
0
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
Ejemplo n.º 10
0
 def start(self):
     self.bot_token = CowrieConfig.get('output_telegram', 'bot_token')
     self.chat_id = CowrieConfig.get('output_telegram', 'chat_id')
Ejemplo n.º 11
0
 def start(self):
     self.format = CowrieConfig.get("output_textlog", "format")
     self.outfile = open(CowrieConfig.get("output_textlog", "logfile"), "a")
Ejemplo n.º 12
0
    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
Ejemplo n.º 13
0
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
Ejemplo n.º 14
0
    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.",
        )
Ejemplo n.º 15
0
 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
Ejemplo n.º 16
0
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()
Ejemplo n.º 17
0
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)
Ejemplo n.º 18
0
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)
Ejemplo n.º 19
0
    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
Ejemplo n.º 20
0
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()
Ejemplo n.º 21
0
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()
Ejemplo n.º 22
0
 def start(self):
     """
     Start output plugin
     """
     self.apiKey = CowrieConfig.get("output_malshare", "api_key")
Ejemplo n.º 23
0
def hardware_platform():
    return CowrieConfig.get("shell", "hardware_platform", fallback="x86_64")
Ejemplo n.º 24
0
    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
Ejemplo n.º 25
0
def kernel_version():
    return CowrieConfig.get("shell",
                            "kernel_version",
                            fallback="3.2.0-4-amd64")
Ejemplo n.º 26
0
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
Ejemplo n.º 27
0
def operating_system():
    return CowrieConfig.get("shell", "operating_system", fallback="GNU/Linux")
Ejemplo n.º 28
0
    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]
Ejemplo n.º 29
0
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
Ejemplo n.º 30
0
    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