mirror of
https://github.com/calebstewart/pwncat.git
synced 2024-11-27 19:04:15 +01:00
Fixed enumeration modules
Some modules weren't cleaning up their Popen objects. All modules at least execute now. Their results need to be fact-checked, though.
This commit is contained in:
parent
8c524bfa03
commit
691503a270
@ -38,6 +38,7 @@ class Link:
|
||||
if self.escalation.type == "escalate.replace":
|
||||
# Exit out of the subshell
|
||||
self.old_session.layers.pop()(self.old_session)
|
||||
self.old_session.platform.refresh_uid()
|
||||
|
||||
def __str__(self):
|
||||
return self.escalation.title(self.old_session)
|
||||
@ -155,6 +156,7 @@ class Command(CommandDefinition):
|
||||
try:
|
||||
# This direction failed. Go back up and try again.
|
||||
chain.pop().leave()
|
||||
|
||||
continue
|
||||
except IndexError:
|
||||
manager.target.log(
|
||||
@ -170,6 +172,8 @@ class Command(CommandDefinition):
|
||||
)
|
||||
result = escalation.escalate(manager.target)
|
||||
|
||||
manager.target.platform.refresh_uid()
|
||||
|
||||
# Construct the escalation link
|
||||
link = Link(manager.target, escalation, result)
|
||||
|
||||
@ -205,6 +209,7 @@ class Command(CommandDefinition):
|
||||
task, status=f"attempting {escalation.title(manager.target)}"
|
||||
)
|
||||
result = escalation.escalate(manager.target)
|
||||
manager.target.platform.refresh_uid()
|
||||
link = Link(manager.target, escalation, result)
|
||||
|
||||
if escalation.type == "escalate.replace":
|
||||
|
@ -33,5 +33,7 @@ class Command(CommandDefinition):
|
||||
|
||||
for i in range(args.count):
|
||||
manager.target.layers.pop()(manager.target)
|
||||
|
||||
manager.target.platform.refresh_uid()
|
||||
except IndexError:
|
||||
manager.target.log("[yellow]warning[/yellow]: no more layers to leave")
|
||||
|
@ -105,8 +105,7 @@ class PrivateKey(Fact):
|
||||
color = "green"
|
||||
return f"Potential private key for [{color}]{self.uid}[/{color}] at [cyan]{rich.markup.escape(self.path)}[/cyan]"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
def description(self, session) -> str:
|
||||
return self.content
|
||||
|
||||
|
||||
|
@ -89,7 +89,7 @@ class Module(pwncat.modules.BaseModule):
|
||||
if clear:
|
||||
for module in modules:
|
||||
yield pwncat.modules.Status(module.name)
|
||||
module.run(clear=True)
|
||||
module.run(session, clear=True)
|
||||
return
|
||||
|
||||
# Enumerate all facts
|
||||
|
@ -1,10 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from rich.prompt import Prompt
|
||||
|
||||
from pwncat.modules import BaseModule, Argument, Status, Bool, ModuleFailed
|
||||
from pwncat.facts import Implant
|
||||
from pwncat.util import console
|
||||
from rich.prompt import Prompt
|
||||
from pwncat.facts import Implant
|
||||
from pwncat.modules import Bool, Status, Argument, BaseModule, ModuleFailed
|
||||
|
||||
|
||||
class Module(BaseModule):
|
||||
@ -92,6 +91,7 @@ class Module(BaseModule):
|
||||
else:
|
||||
# Track the new shell layer in the current session
|
||||
session.layers.append(result)
|
||||
session.platform.refresh_uid()
|
||||
|
||||
session.log(
|
||||
f"escalation [green]succeeded[/green] with: {implant.title(session)}"
|
||||
|
@ -28,43 +28,36 @@ class Module(EnumerateModule):
|
||||
|
||||
def enumerate(self, session):
|
||||
|
||||
pam: InstalledModule = None
|
||||
# Check if we previously had PAM persistence... this isn't re-implemented yet
|
||||
# for module in session.run(
|
||||
# "persist.gather", progress=self.progress, module="persist.pam_backdoor"
|
||||
# ):
|
||||
# pam = module
|
||||
# break
|
||||
# Ensure the user database is already retrieved
|
||||
session.find_user(uid=0)
|
||||
|
||||
if pam is None:
|
||||
# The pam persistence module isn't installed.
|
||||
return
|
||||
for implant in session.run("enumerate", types=["implant.*"]):
|
||||
if implant.source != "linux.implant.pam":
|
||||
continue
|
||||
|
||||
# Grab the log path
|
||||
log_path = pam.persist.args["log"]
|
||||
# Just in case we have multiple of the same password logged
|
||||
observed = []
|
||||
# Just in case we have multiple of the same password logged
|
||||
observed = []
|
||||
|
||||
try:
|
||||
with session.platform.open(log_path, "r") as filp:
|
||||
for line in filp:
|
||||
line = line.rstrip("\n")
|
||||
if line in observed:
|
||||
continue
|
||||
try:
|
||||
with session.platform.open(implant.log, "r") as filp:
|
||||
for lineno, line in enumerate(filp):
|
||||
line = line.rstrip("\n")
|
||||
if line in observed:
|
||||
continue
|
||||
|
||||
user, *password = line.split(":")
|
||||
password = ":".join(password)
|
||||
user, *password = line.split(":")
|
||||
password = ":".join(password)
|
||||
|
||||
try:
|
||||
# Check for valid user name
|
||||
session.platform.find_user(name=user)
|
||||
except KeyError:
|
||||
continue
|
||||
try:
|
||||
# Check for valid user name
|
||||
user_info = session.find_user(name=user)
|
||||
except KeyError:
|
||||
continue
|
||||
|
||||
observed.append(line)
|
||||
observed.append(line)
|
||||
|
||||
yield "creds.password", PotentialPassword(
|
||||
password, log_path, None, uid=pwncat.victim.users[user].id
|
||||
)
|
||||
except (FileNotFoundError, PermissionError):
|
||||
pass
|
||||
yield PotentialPassword(
|
||||
self.name, password, implant.log, lineno, user_info.id
|
||||
)
|
||||
except (FileNotFoundError, PermissionError):
|
||||
pass
|
||||
|
@ -1,12 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
import time
|
||||
|
||||
import rich.markup
|
||||
from Crypto.PublicKey import RSA
|
||||
|
||||
import pwncat
|
||||
import rich.markup
|
||||
from pwncat.facts import PrivateKey
|
||||
from pwncat.modules import Status
|
||||
from Crypto.PublicKey import RSA
|
||||
from pwncat.platform.linux import Linux
|
||||
from pwncat.modules.enumerate import Schedule, EnumerateModule
|
||||
|
||||
@ -48,6 +47,9 @@ class Module(EnumerateModule):
|
||||
yield Status(f"found [cyan]{rich.markup.escape(path)}[/cyan]")
|
||||
facts.append(PrivateKey(self.name, path, uid, None, False))
|
||||
|
||||
# Ensure proc is cleaned up
|
||||
proc.wait()
|
||||
|
||||
for fact in facts:
|
||||
try:
|
||||
yield Status(f"reading [cyan]{rich.markup.escape(fact.path)}[/cyan]")
|
||||
|
@ -2,9 +2,8 @@
|
||||
import dataclasses
|
||||
from typing import List
|
||||
|
||||
import rich.markup
|
||||
|
||||
import pwncat
|
||||
import rich.markup
|
||||
from pwncat import util
|
||||
from pwncat.db import Fact
|
||||
from pwncat.platform.linux import Linux
|
||||
@ -67,3 +66,5 @@ class Module(EnumerateModule):
|
||||
fact = FileCapabilityData(self.name, path, caps)
|
||||
|
||||
yield fact
|
||||
|
||||
proc.wait()
|
||||
|
@ -4,9 +4,8 @@ import re
|
||||
import shlex
|
||||
import dataclasses
|
||||
|
||||
import rich.markup
|
||||
|
||||
import pwncat
|
||||
import rich.markup
|
||||
from pwncat.db import Fact
|
||||
from pwncat.subprocess import CalledProcessError
|
||||
from pwncat.platform.linux import Linux
|
||||
@ -82,6 +81,9 @@ class Module(EnumerateModule):
|
||||
# if this is executable
|
||||
screen_paths.append(path)
|
||||
|
||||
# Clean up the search
|
||||
proc.wait()
|
||||
|
||||
# Now, check each screen version to determine if it is vulnerable
|
||||
for screen_path in screen_paths:
|
||||
version_output = session.platform.Popen(
|
||||
@ -112,3 +114,6 @@ class Module(EnumerateModule):
|
||||
continue
|
||||
|
||||
yield ScreenVersion(self.name, path, perms, vulnerable=True)
|
||||
|
||||
# Clean up process
|
||||
version_output.wait()
|
||||
|
@ -195,6 +195,7 @@ class Module(EnumerateModule):
|
||||
try:
|
||||
etc_sudoers = session.platform.Path("/etc/sudoers")
|
||||
if etc_sudoers.readable():
|
||||
rules = []
|
||||
with etc_sudoers.open() as filp:
|
||||
for line in filp:
|
||||
line = line.strip()
|
||||
@ -207,37 +208,41 @@ class Module(EnumerateModule):
|
||||
# Yield the sudo rule
|
||||
yield rule
|
||||
|
||||
# We can't handle abilities which we didn't parse properly
|
||||
if not rule.matched:
|
||||
continue
|
||||
rules.append(rule)
|
||||
|
||||
user_name = rule.runas_user
|
||||
if user_name == "ALL":
|
||||
user_name = "root"
|
||||
for rule in rules:
|
||||
|
||||
# Grab the user by name so we can get the UID
|
||||
runas_user = session.find_user(name=user_name)
|
||||
if runas_user is None:
|
||||
# Not a valid user? :/
|
||||
continue
|
||||
# We can't handle abilities which we didn't parse properly
|
||||
if not rule.matched:
|
||||
continue
|
||||
|
||||
user = session.find_user(name=rule.user)
|
||||
if user is None:
|
||||
continue
|
||||
user_name = rule.runas_user
|
||||
if user_name == "ALL":
|
||||
user_name = "root"
|
||||
|
||||
# Yield escalation abilities
|
||||
for spec in rule.commands:
|
||||
yield from (
|
||||
build_gtfo_ability(
|
||||
self.name,
|
||||
runas_user.id,
|
||||
method,
|
||||
spec=spec,
|
||||
source_uid=user.id,
|
||||
user=runas_user.name,
|
||||
)
|
||||
for method in session.platform.gtfo.iter_sudo(spec)
|
||||
# Grab the user by name so we can get the UID
|
||||
runas_user = session.find_user(name=user_name)
|
||||
if runas_user is None:
|
||||
# Not a valid user? :/
|
||||
continue
|
||||
|
||||
user = session.find_user(name=rule.user)
|
||||
if user is None:
|
||||
continue
|
||||
|
||||
# Yield escalation abilities
|
||||
for spec in rule.commands:
|
||||
yield from (
|
||||
build_gtfo_ability(
|
||||
self.name,
|
||||
runas_user.id,
|
||||
method,
|
||||
spec=spec,
|
||||
source_uid=user.id,
|
||||
user=runas_user.name,
|
||||
)
|
||||
for method in session.platform.gtfo.iter_sudo(spec)
|
||||
)
|
||||
|
||||
return
|
||||
except (FileNotFoundError, PermissionError):
|
||||
|
@ -95,7 +95,7 @@ class PopenLinux(pwncat.subprocess.Popen):
|
||||
if self.stdin is not None:
|
||||
self.stdin.close()
|
||||
if self.stdout_raw is not None:
|
||||
self.stdout_raw.close()
|
||||
self.stdout_raw.close
|
||||
|
||||
# Hope they know what they're doing...
|
||||
self.platform.command_running = None
|
||||
@ -477,6 +477,8 @@ class Linux(Platform):
|
||||
self.name = "linux"
|
||||
self.command_running = None
|
||||
|
||||
self._uid = None
|
||||
|
||||
# This causes an stty to be sent.
|
||||
# If we aren't in a pty, it doesn't matter.
|
||||
# if we are, we need this stty to properly handle process IO
|
||||
@ -521,6 +523,8 @@ class Linux(Platform):
|
||||
else:
|
||||
self.has_pty = False
|
||||
|
||||
self.refresh_uid()
|
||||
|
||||
def disable_history(self):
|
||||
"""Disable shell history"""
|
||||
|
||||
@ -690,18 +694,29 @@ class Linux(Platform):
|
||||
except CalledProcessError:
|
||||
return None
|
||||
|
||||
def getuid(self):
|
||||
def refresh_uid(self):
|
||||
"""Retrieve the current user ID"""
|
||||
|
||||
try:
|
||||
# NOTE: this is probably not great... but sometimes it fails when transitioning
|
||||
# states, and I can't pin down why. The second time normally succeeds, and I've
|
||||
# never observed it hanging for any significant amount of time.
|
||||
proc = self.run(["id", "-ru"], capture_output=True, text=True, check=True)
|
||||
return int(proc.stdout.rstrip("\n"))
|
||||
while True:
|
||||
try:
|
||||
proc = self.run(
|
||||
["id", "-ru"], capture_output=True, text=True, check=True
|
||||
)
|
||||
self._uid = int(proc.stdout.rstrip("\n"))
|
||||
return self._uid
|
||||
except ValueError:
|
||||
continue
|
||||
except CalledProcessError as exc:
|
||||
raise PlatformError(str(exc)) from exc
|
||||
|
||||
def getuid(self):
|
||||
""" Retrieve the current cached uid """
|
||||
return self._uid
|
||||
|
||||
def getenv(self, name: str):
|
||||
|
||||
try:
|
||||
|
Loading…
Reference in New Issue
Block a user