コード例 #1
0
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()
コード例 #2
0
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()