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:
commit
a60131edb8
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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
|
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
16
test.py
@ -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()
|
|
||||||
|
Loading…
Reference in New Issue
Block a user