mirror of
https://github.com/calebstewart/pwncat.git
synced 2024-11-27 10:54:14 +01:00
Made enumerate.misc.writable_path functional. Added support to enumerate groups
This commit is contained in:
parent
4f5e792a49
commit
519c8910e1
@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
from typing import Optional
|
||||
|
||||
import rich.markup
|
||||
from persistent.list import PersistentList
|
||||
|
||||
from pwncat.db.fact import Fact
|
||||
@ -19,7 +20,10 @@ class Group(Fact):
|
||||
self.members: PersistentList = PersistentList(members)
|
||||
|
||||
def __repr__(self):
|
||||
return f"""Group(gid={self.id}, name={repr(self.name)}), members={repr(",".join(m for m in self.members))})"""
|
||||
return f"""Group(gid={self.id}, name={repr(self.name)}, members={repr(self.members)})"""
|
||||
|
||||
def __str__(self):
|
||||
return f"""{rich.markup.escape(self.name)}, gid={self.id}, members={rich.markup.escape(",".join((m for m in self.members)))}"""
|
||||
|
||||
|
||||
class User(Fact):
|
||||
|
@ -121,17 +121,26 @@ class Session:
|
||||
self.log("registered new host w/ db")
|
||||
|
||||
def current_user(self) -> pwncat.db.User:
|
||||
""" Retrieve the current user object """
|
||||
"""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 """
|
||||
"""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 find_group(self, gid=None, name=None):
|
||||
"""Locate a user object by name or ID"""
|
||||
|
||||
for group in self.run("enumerate.gather", types=["group"]):
|
||||
if (gid is None or group.id == gid) and (
|
||||
name is None or group.name == name
|
||||
):
|
||||
return group
|
||||
|
||||
def run(self, module: str, **kwargs):
|
||||
"""Run a module on this session"""
|
||||
|
||||
|
@ -2,12 +2,25 @@
|
||||
import os
|
||||
import stat
|
||||
|
||||
import rich.markup
|
||||
|
||||
import pwncat
|
||||
from pwncat.db import Fact
|
||||
from pwncat.util import Access
|
||||
from pwncat.platform.linux import Linux
|
||||
from pwncat.modules.agnostic.enumerate import EnumerateModule, Schedule
|
||||
|
||||
|
||||
class WritablePath(Fact):
|
||||
def __init__(self, source, path):
|
||||
super().__init__(source=source, types=["misc.writable_path"])
|
||||
|
||||
self.path: str = path
|
||||
|
||||
def __str__(self):
|
||||
return f"""{rich.markup.escape(self.path)}"""
|
||||
|
||||
|
||||
class Module(EnumerateModule):
|
||||
"""
|
||||
Locate any components of the current PATH that are writable
|
||||
@ -20,8 +33,6 @@ class Module(EnumerateModule):
|
||||
|
||||
def enumerate(self, session):
|
||||
|
||||
user = session.platform.current_user()
|
||||
|
||||
for path in session.platform.getenv("PATH").split(":"):
|
||||
|
||||
# Ignore empty components
|
||||
@ -35,4 +46,4 @@ class Module(EnumerateModule):
|
||||
|
||||
# See if we have write permission
|
||||
if path.is_dir() and path.writable():
|
||||
yield "misc.writable_path", str(path.resolve())
|
||||
yield WritablePath(self.name, str(path.resolve()))
|
||||
|
@ -6,7 +6,7 @@ from pwncat.platform.linux import Linux, LinuxUser
|
||||
|
||||
|
||||
class Module(EnumerateModule):
|
||||
""" Enumerate users from a linux target """
|
||||
"""Enumerate users from a linux target"""
|
||||
|
||||
PROVIDES = ["user"]
|
||||
PLATFORM = [Linux]
|
||||
|
43
pwncat/modules/linux/enumerate/user/group.py
Normal file
43
pwncat/modules/linux/enumerate/user/group.py
Normal file
@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from pwncat.modules import ModuleFailed, Status
|
||||
from pwncat.modules.agnostic.enumerate import EnumerateModule, Schedule
|
||||
from pwncat.platform.linux import Linux, LinuxGroup
|
||||
|
||||
|
||||
class Module(EnumerateModule):
|
||||
"""Enumerate users from a linux target"""
|
||||
|
||||
PROVIDES = ["group"]
|
||||
PLATFORM = [Linux]
|
||||
SCHEDULE = Schedule.ONCE
|
||||
|
||||
def enumerate(self, session: "pwncat.manager.Session"):
|
||||
|
||||
group_file = session.platform.Path("/etc/group")
|
||||
|
||||
try:
|
||||
with group_file.open("r") as filp:
|
||||
for group_line in filp:
|
||||
try:
|
||||
# Extract the group fields
|
||||
(group_name, hash, gid, members) = group_line.split(":")
|
||||
|
||||
# Build a group object
|
||||
group = LinuxGroup(
|
||||
self.name,
|
||||
group_name,
|
||||
hash,
|
||||
int(gid),
|
||||
(m.strip() for m in members.split(",") if m.strip()),
|
||||
)
|
||||
|
||||
yield group
|
||||
|
||||
except Exception as exc:
|
||||
raise ModuleFailed(f"something fucked {exc}")
|
||||
# Bad group line
|
||||
continue
|
||||
|
||||
except (FileNotFoundError, PermissionError) as exc:
|
||||
raise ModuleFailed(str(exc)) from exc
|
@ -54,19 +54,19 @@ class Path:
|
||||
def writable(self) -> bool:
|
||||
"""This is non-standard, but is useful"""
|
||||
|
||||
user = self._target.current_user()
|
||||
user = self._target.session.current_user()
|
||||
group = self._target.session.find_group(gid=user.gid)
|
||||
mode = self.stat().st_mode
|
||||
uid = self.stat().st_uid
|
||||
gid = self.stat().st_gid
|
||||
|
||||
if uid == user.id and (mode & stat.S_IWUSR):
|
||||
return True
|
||||
elif user.group.id == gid and (mode & stat.S_IWGRP):
|
||||
elif group.id == gid and (mode & stat.S_IWGRP):
|
||||
return True
|
||||
else:
|
||||
for group in user.groups:
|
||||
if group.id == gid and (mode & stat.S_IWGRP):
|
||||
return True
|
||||
if group.id == gid and (mode & stat.S_IWGRP):
|
||||
return True
|
||||
else:
|
||||
if mode & stat.S_IWOTH:
|
||||
return True
|
||||
|
@ -17,7 +17,7 @@ import pwncat.subprocess
|
||||
from pwncat import util
|
||||
from pwncat.gtfobins import GTFOBins, Capability, Stream, MissingBinary
|
||||
from pwncat.platform import Platform, PlatformError, Path
|
||||
from pwncat.db import User
|
||||
from pwncat.db import User, Group
|
||||
|
||||
|
||||
class PopenLinux(pwncat.subprocess.Popen):
|
||||
@ -471,6 +471,19 @@ class LinuxUser(User):
|
||||
self.shell = shell
|
||||
|
||||
|
||||
class LinuxGroup(Group):
|
||||
def __init__(self, source, group_name, hash, gid, members, password=None):
|
||||
|
||||
# We've never seen a group password hash, but those apparently exist????
|
||||
if hash == "x":
|
||||
hash = None
|
||||
|
||||
super().__init__(source, group_name, gid, members)
|
||||
|
||||
self.hash = hash
|
||||
self.password = password
|
||||
|
||||
|
||||
class Linux(Platform):
|
||||
"""
|
||||
Concrete platform class abstracting interaction with a GNU/Linux remote
|
||||
|
@ -1,68 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import threading
|
||||
from typing import Optional, Union
|
||||
|
||||
import pwncat
|
||||
import pwncat.channel
|
||||
import pwncat.platform
|
||||
|
||||
|
||||
class Session:
|
||||
"""
|
||||
Combine a platform and a C2 channel to track an abstract session
|
||||
with a victim.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
platform: Union[str, pwncat.platform.Platform],
|
||||
channel: Optional[pwncat.channel.Channel] = None,
|
||||
):
|
||||
|
||||
# Allow creation of a session from a platform identifier
|
||||
# or from an already initialized platform object.
|
||||
if not isinstance(platform, pwncat.platform.Platform):
|
||||
self.platform: pwncat.platform.Platform = pwncat.platform.create(
|
||||
platform, channel=channel
|
||||
)
|
||||
else:
|
||||
self.platform: pwncat.platform.Platform = platform
|
||||
|
||||
# Find the host hash identifying this unique victim
|
||||
host_hash = self.platform.get_host_hash()
|
||||
|
||||
# Lookup the host hash in the database
|
||||
self.host: pwncat.db.Host = pwncat.db.get_session().query(
|
||||
pwncat.db.Host
|
||||
).filter_by(hash=host_hash).first()
|
||||
|
||||
# A lock used to ensure that multiple actions aren't performed
|
||||
# at the same time on one session. This is mainly implemented with
|
||||
# the intension of implementing multi-session capabilities into
|
||||
# pwncat in the future
|
||||
self.lock: threading.Lock = threading.Lock()
|
||||
|
||||
# Bootstrap the new host object
|
||||
if self.host is None:
|
||||
self._bootstrap_new_host(host_hash)
|
||||
|
||||
def _bootstrap_new_host(self, host_hash):
|
||||
"""
|
||||
Utilize the enumerated host hash to build a new host object in the
|
||||
database. This tracks all data related to an individual host.
|
||||
"""
|
||||
|
||||
def __enter__(self) -> "Session":
|
||||
""" Acquire the session lock
|
||||
|
||||
:return: the locked session object
|
||||
:rtype: pwncat.session.Session
|
||||
"""
|
||||
|
||||
self.lock.acquire()
|
||||
return self
|
||||
|
||||
def __exit__(self):
|
||||
""" Release the session lock """
|
||||
|
||||
self.lock.release()
|
Loading…
Reference in New Issue
Block a user