1
0
mirror of https://github.com/calebstewart/pwncat.git synced 2024-11-23 17:15:38 +01:00

Merge branch 'platforms' of https://github.com/calebstewart/pwncat into platforms

This commit is contained in:
John Hammond 2021-05-02 14:59:01 -04:00
commit a60131edb8
8 changed files with 41 additions and 169 deletions

View File

@ -7,7 +7,7 @@ set -g privkey "data/pwncat"
# Set the pwncat backdoor user and password # Set the pwncat backdoor user and password
set -g backdoor_user "pwncat" set -g backdoor_user "pwncat"
set -g backdoor_pass "pwncat" set -g backdoor_pass "pwncat"
set -g db "memory://" set -g db "file://test.zodb"
set -g on_load { set -g on_load {
# Run a command upon a stable connection # Run a command upon a stable connection

View File

@ -108,7 +108,6 @@ def resolve_blocks(source: str):
class DatabaseHistory(History): class DatabaseHistory(History):
""" Yield history from the host entry in the database """ """ Yield history from the host entry in the database """
# NOTE - This is nerfed because of ZODB changes...
def __init__(self, manager): def __init__(self, manager):
super().__init__() super().__init__()
self.manager = manager self.manager = manager
@ -116,23 +115,14 @@ class DatabaseHistory(History):
def load_history_strings(self) -> Iterable[str]: def load_history_strings(self) -> Iterable[str]:
""" Load the history from the database """ """ Load the history from the database """
if False: with self.manager.db.transaction() as conn:
with self.manager.new_db_session() as session: yield from reversed(conn.root.history)
for history in (
session.query(pwncat.db.History)
.order_by(pwncat.db.History.id.desc())
.all()
):
yield history.command
def store_string(self, string: str) -> None: def store_string(self, string: str) -> None:
""" Store a command in the database """ """ Store a command in the database """
if False: with self.manager.db.transaction() as conn:
history = pwncat.db.History(command=string) conn.root.history.append(string)
with self.manager.new_db_session() as session:
session.add(history)
class CommandParser: class CommandParser:

View File

@ -2,7 +2,6 @@
import pwncat import pwncat
from pwncat.db.binary import Binary from pwncat.db.binary import Binary
from pwncat.db.history import History
from pwncat.db.persist import Persistence from pwncat.db.persist import Persistence
from pwncat.db.suid import SUID from pwncat.db.suid import SUID
from pwncat.db.tamper import Tamper from pwncat.db.tamper import Tamper

View File

@ -1,13 +0,0 @@
#!/usr/bin/env python3
import persistent
from typing import Optional
class History(persistent.Persistent):
"""Store history of ran commands on the target."""
def __init__(self, command):
# The command ran on the target (e.g., "whoami")
self.command: Optional[str] = command

View File

@ -120,6 +120,18 @@ class Session:
self.log("registered new host w/ db") self.log("registered new host w/ db")
def current_user(self) -> pwncat.db.User:
""" Retrieve the current user object """
return self.find_user(uid=self.platform.getuid())
def find_user(self, uid=None, name=None):
""" Locate a user object by name or ID """
for user in self.run("enumerate.gather", types=["user"]):
if (uid is None or user.id == uid) and (name is None or user.name == name):
return user
def run(self, module: str, **kwargs): def run(self, module: str, **kwargs):
"""Run a module on this session""" """Run a module on this session"""
@ -341,8 +353,12 @@ class Manager:
if not hasattr(conn.root, "targets"): if not hasattr(conn.root, "targets"):
conn.root.targets = persistent.list.PersistentList() conn.root.targets = persistent.list.PersistentList()
conn.transaction_manager.commit()
conn.close() if not hasattr(conn.root, "history"):
conn.root.history = persistent.list.PersistentList()
conn.transaction_manager.commit()
conn.close()
# Rebuild the command parser now that the database is available # Rebuild the command parser now that the database is available
self.parser = CommandParser(self) self.parser = CommandParser(self)

View File

@ -504,6 +504,10 @@ class Platform(ABC):
"""Retrieve a string describing the platform connection""" """Retrieve a string describing the platform connection"""
return str(self.channel) return str(self.channel)
@abstractmethod
def getuid(self):
"""Get the current user ID"""
@abstractmethod @abstractmethod
def getenv(self, name: str) -> str: def getenv(self, name: str) -> str:
"""Get the value of an environment variable. """Get the value of an environment variable.
@ -513,128 +517,6 @@ class Platform(ABC):
:rtype: str :rtype: str
""" """
@abstractmethod
def reload_users(self):
"""Reload the user and group cache. This is automatically called
if the cache hasn't been built yet, but may be called manually
if you know the users have changed. This method is also called
if a lookup for a specific user or group ID fails."""
def iter_users(self) -> Generator["pwncat.db.User", None, None]:
"""Iterate over all users on the remote system"""
with self.session.db as db:
users = db.query(pwncat.db.User).filter_by(host_id=self.session.host).all()
if users is None:
self.reload_users()
users = (
db.query(pwncat.db.User).filter_by(host_id=self.session.host).all()
)
if users is not None:
for user in users:
_ = user.groups
yield user
return
def find_user(
self,
name: Optional[str] = None,
id: Optional[int] = None,
_recurse: bool = True,
) -> "pwncat.db.User":
"""Locate a user by name or UID. If the user/group cache has not
been built, then reload_users is automatically called. If the
lookup fails, reload_users is called automatically to ensure that
there has not been a user/group update remotely. If the user
still cannot be found, a KeyError is raised."""
with self.session.db as db:
user = db.query(pwncat.db.User).filter_by(host_id=self.session.host)
if name is not None:
user = user.filter_by(name=name)
if id is not None:
user = user.filter_by(id=id)
user = user.first()
if user is None and _recurse:
self.reload_users()
return self.find_user(name=name, id=id, _recurse=False)
elif user is None:
raise KeyError
return user
def update_user(self):
"""Force an update of the current user the next time it is requested."""
self._current_user = None
def current_user(self):
"""Retrieve a user object for the current user"""
if self._current_user is not None:
return self._current_user
self._current_user = self.find_user(name=self.whoami())
return self._current_user
def iter_groups(self) -> Generator["pwncat.db.Group", None, None]:
"""Iterate over all groups on the remote system"""
with self.session.db as db:
groups = (
db.query(pwncat.db.Group).filter_by(host_id=self.session.host).all()
)
if groups is None:
self.reload_users()
groups = (
db.query(pwncat.db.Group).filter_by(host_id=self.session.host).all()
)
if groups is not None:
for group in groups:
_ = group.members
yield group
return
def find_group(
self,
name: Optional[str] = None,
id: Optional[int] = None,
_recurse: bool = True,
) -> "pwncat.db.Group":
"""Locate a group by name or GID. If the user/group cache has not
been built, then reload_users is automatically called. If the
lookup fails, reload_users is called automatically to ensure that
there has not been a user/group update remotely. If the group
still cannot be found, a KeyError is raised."""
with self.session.db as db:
group = db.query(pwncat.db.Group).filter_by(host_id=self.session.host)
if name is not None:
group = group.filter_by(name=name)
if id is not None:
group = group.filter_by(id=id)
group = group.first()
if group is None and _recurse:
self.reload_users()
return self.find_group(name=name, id=id, _recurse=False)
elif group is None:
raise KeyError
return group
@abstractmethod @abstractmethod
def stat(self, path: str) -> os.stat_result: def stat(self, path: str) -> os.stat_result:
"""Run stat on a path on the remote system and return a stat result """Run stat on a path on the remote system and return a stat result

View File

@ -547,11 +547,12 @@ class Linux(Platform):
return return
pty_command = None pty_command = None
shell = self.getenv("SHELL")
if pty_command is None: if pty_command is None:
script_path = self.which("script") script_path = self.which("script")
if script_path is not None: if script_path is not None:
pty_command = f""" exec {script_path} -qc /bin/sh /dev/null 2>&1\n""" pty_command = f""" exec {script_path} -qc {shell} /dev/null 2>&1\n"""
if pty_command is None: if pty_command is None:
python_path = self.which( python_path = self.which(
@ -566,7 +567,7 @@ class Linux(Platform):
] ]
) )
if python_path is not None: if python_path is not None:
pty_command = f"""exec {python_path} -c "import pty; pty.spawn('/bin/sh')" 2>&1\n""" pty_command = f"""exec {python_path} -c "import pty; pty.spawn('{shell} -i')" 2>&1\n"""
if pty_command is not None: if pty_command is not None:
self.logger.info(pty_command.rstrip("\n")) self.logger.info(pty_command.rstrip("\n"))
@ -708,6 +709,15 @@ class Linux(Platform):
except CalledProcessError: except CalledProcessError:
return None return None
def getuid(self):
""" Retrieve the current user ID """
try:
proc = self.run(["id", "-ru"], capture_output=True, text=True, check=True)
return int(proc.stdout.rstrip("\n"))
except CalledProcessError as exc:
raise PlatformError(str(exc)) from exc
def getenv(self, name: str): def getenv(self, name: str):
try: try:

16
test.py
View File

@ -12,7 +12,7 @@ manager = pwncat.manager.Manager("data/pwncatrc")
# Tell the manager to create verbose sessions that # Tell the manager to create verbose sessions that
# log all commands executed on the remote host # log all commands executed on the remote host
manager.config.set("verbose", True, glob=True) # manager.config.set("verbose", True, glob=True)
# Establish a session # Establish a session
# session = manager.create_session("windows", host="192.168.56.10", port=4444) # session = manager.create_session("windows", host="192.168.56.10", port=4444)
@ -20,16 +20,4 @@ manager.config.set("verbose", True, glob=True)
session = manager.create_session("linux", host="127.0.0.1", port=4444) session = manager.create_session("linux", host="127.0.0.1", port=4444)
# session = manager.create_session("windows", host="0.0.0.0", port=4444) # session = manager.create_session("windows", host="0.0.0.0", port=4444)
proc = session.platform.sudo( print(session.current_user())
["env"],
env={"TEST": "hello world"},
password="Super Secret Squirrel",
text=True,
stdout=subprocess.PIPE,
)
print(proc.stdout.readline())
proc.wait()
manager.interactive()