Skip to content

Commit a6b4b5a

Browse files
committed
fix(enumerate): fix CVE-2019-14287 detection and restore pickle compatibility
Fixed CVE-2019-14287 detection logic. Also reverted a previous accidental change that broke pickle serialization, preventing persistent storage from working properly.
1 parent 6ac5afd commit a6b4b5a

File tree

5 files changed

+37
-33
lines changed

5 files changed

+37
-33
lines changed

pwncat/manager.py

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -923,34 +923,37 @@ def create_db_session(self):
923923
return self.db.open()
924924

925925
def load_modules(self, *paths):
926-
"""Dynamically load modules from the specified paths
926+
"""
927+
Dynamically load modules from the specified paths.
927928
928929
If a module has the same name as an already loaded module, it will
929-
take it's place in the module list. This includes built-in modules.
930+
take its place in the module list. This includes built-in modules.
930931
"""
931932

932-
for loader, module_name, _ in pkgutil.walk_packages(
933-
paths, prefix="pwncat.modules."
934-
):
933+
for loader, module_name, is_pkg in pkgutil.walk_packages(paths, prefix="pwncat.modules."):
934+
# Locate the module spec
935+
spec = importlib.util.find_spec(module_name)
936+
if spec is None:
937+
continue
935938

936-
if module_name not in sys.modules:
937-
spec = importlib.util.find_spec(module_name)
938-
if spec is None:
939-
continue
939+
# Always check `sys.modules` under `spec.name`.
940+
# If it's already loaded, reuse that module; if not, load anew.
941+
if spec.name in sys.modules:
942+
module = sys.modules[spec.name]
943+
else:
940944
module = importlib.util.module_from_spec(spec)
945+
sys.modules[spec.name] = module
941946
spec.loader.exec_module(module)
942-
else:
943-
module = sys.modules[module_name]
944947

945-
if getattr(module, "Module", None) is None:
948+
# We only care about modules that actually define `Module`
949+
if not hasattr(module, "Module"):
946950
continue
947951

948952
# Create an instance of this module
949-
module_name = module_name.split("pwncat.modules.")[1]
950-
self.modules[module_name] = module.Module()
953+
short_name = module_name.split("pwncat.modules.", 1)[1]
954+
self.modules[short_name] = module.Module()
955+
self.modules[short_name].name = short_name
951956

952-
# Store it's name so we know it later
953-
setattr(self.modules[module_name], "name", module_name)
954957

955958
def log(self, *args, **kwargs):
956959
"""Output a log entry"""

pwncat/modules/linux/enumerate/file/suid.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,4 @@ def enumerate(self, session: "pwncat.manager.Session"):
7676
)
7777
)
7878
finally:
79-
proc.wait()
79+
proc.wait()

pwncat/modules/linux/enumerate/software/sudo/cve_2019_14287.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/usr/bin/env python3
2-
from packaging import version
2+
from packaging.version import parse, InvalidVersion
33

44
import pwncat
55
from pwncat.facts import build_gtfo_ability
@@ -26,7 +26,11 @@ def enumerate(self, session: "pwncat.manager.Session"):
2626
return
2727

2828
# This vulnerability was patched in 1.8.28
29-
if version.parse(sudo_info.version) >= version.parse("1.8.28"):
29+
try:
30+
parsed_version = parse(sudo_info.version)
31+
if parsed_version >= parse("1.8.28"):
32+
return
33+
except InvalidVersion:
3034
return
3135

3236
# Grab the current user/group
@@ -74,4 +78,4 @@ def enumerate(self, session: "pwncat.manager.Session"):
7478
source_uid=current_user.id,
7579
user="\\#-1",
7680
spec=command,
77-
)
81+
)

pwncat/platform/__init__.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -515,12 +515,11 @@ def __init__(
515515
target = self
516516

517517
class RemotePath(base_path, Path):
518-
519-
_target = target
520-
_stat = None
521-
522-
def __init__(self, *args):
523-
base_path.__init__(*args)
518+
def __new__(cls, *args, **kwargs):
519+
obj = super().__new__(cls, *args, **kwargs)
520+
obj._target = target
521+
obj._stat = None
522+
return obj
524523

525524
self.Path = RemotePath
526525
""" A concrete Path object for this platform conforming to pathlib.Path """

pwncat/platform/linux.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1635,14 +1635,12 @@ def context_changed(self):
16351635

16361636
# Update self.shell just in case the user changed shells
16371637
try:
1638-
# Get the PID of the running shell
1639-
pid = self.getenv("$")
1640-
# Grab the path to the executable representing the shell
1641-
self.shell = self.Path("/proc", pid, "exe").readlink()
1642-
except (FileNotFoundError, PermissionError, OSError):
1643-
# Fall back to SHELL even though it's not really trustworthy
1638+
pid = self.getenv("$").strip()
1639+
proc_exe_path = f"/proc/{pid}/exe"
1640+
self.shell = self.readlink(proc_exe_path)
1641+
except (FileNotFoundError, PermissionError, OSError, AttributeError):
16441642
self.shell = self.getenv("SHELL")
1645-
if self.shell is None or self.shell == "":
1643+
if not self.shell:
16461644
self.shell = "/bin/sh"
16471645

16481646
# Refresh the currently tracked user and group IDs

0 commit comments

Comments
 (0)