diff --git a/pwncat/enumerate/writable_path.py b/pwncat/enumerate/writable_path.py new file mode 100644 index 0000000..31775e3 --- /dev/null +++ b/pwncat/enumerate/writable_path.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +""" +Enumerate directories in your PATH which are writable. All paths which are generated +from this enumerator are either directly writable, or do not exist but are under a +path which you have write access to. If the directory returned from this enumerator +does not exist, a call to `mkdir -p {directory}` should succeed. +""" +import os +from typing import Generator + +from pwncat.enumerate import FactData +import pwncat +from pwncat.util import Access + +name = "pwncat.enumerate.writable_path" +provides = "writable_path" +per_user = False +always_run = False + + +def enumerate() -> Generator[FactData, None, None]: + """ + Enumerate directories in our PATH which are writable + :return: + """ + + for path in pwncat.victim.getenv("PATH").split(":"): + access = pwncat.victim.access(path) + if (Access.DIRECTORY | Access.WRITE) in access: + yield path + elif ( + Access.EXISTS not in access + and (Access.PARENT_EXIST | Access.PARENT_WRITE) in access + ): + yield path + elif access == Access.NONE: + # This means the parent directory doesn't exist. Check up the chain to see if + # We can create this chain of directories + dirpath = os.path.dirname(path) + access = pwncat.victim.access(dirpath) + # Find the first item that either exists or it's parent does + while access == Access.NONE: + dirpath = os.path.dirname(dirpath) + access = pwncat.victim.access(dirpath) + # This item exists. Is it a directory and can we write to it? + if (Access.DIRECTORY | Access.WRITE) in access: + yield path + elif ( + Access.PARENT_EXIST | Access.PARENT_WRITE + ) in access and Access.EXISTS not in access: + yield path diff --git a/pwncat/remote/victim.py b/pwncat/remote/victim.py index 55bbafb..750816d 100644 --- a/pwncat/remote/victim.py +++ b/pwncat/remote/victim.py @@ -152,6 +152,8 @@ class Victim: # The current user. This is cached while at the `pwncat` prompt # and reloaded whenever returning from RAW mode. self.cached_user: str = None + # The original value of the PATH environment variable + self.original_path: List[str] = None def reconnect( self, hostid: str, requested_method: str = None, requested_user: str = None @@ -353,6 +355,9 @@ class Victim: # Disable automatic margins, which fuck up the prompt self.run("tput rmam") + # Store the original path + self.original_path = self.getenv("PATH").split(":") + # Now that we have a stable connection, we can create our # privesc finder object. self.privesc = privesc.Finder()