1
0
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:
John Hammond 2021-05-07 22:29:32 -04:00
parent 4f5e792a49
commit 519c8910e1
8 changed files with 93 additions and 81 deletions

View File

@ -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):

View File

@ -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"""

View File

@ -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()))

View File

@ -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]

View 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

View File

@ -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

View File

@ -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

View File

@ -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()