1
0
mirror of https://github.com/calebstewart/pwncat.git synced 2024-11-30 20:34: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:
Caleb Stewart 2021-05-22 14:46:07 -04:00
parent 8c524bfa03
commit 691503a270
11 changed files with 104 additions and 77 deletions

View File

@ -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":

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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)}"

View File

@ -28,26 +28,19 @@ 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 = []
try:
with session.platform.open(log_path, "r") as filp:
for line in filp:
with session.platform.open(implant.log, "r") as filp:
for lineno, line in enumerate(filp):
line = line.rstrip("\n")
if line in observed:
continue
@ -57,14 +50,14 @@ class Module(EnumerateModule):
try:
# Check for valid user name
session.platform.find_user(name=user)
user_info = session.find_user(name=user)
except KeyError:
continue
observed.append(line)
yield "creds.password", PotentialPassword(
password, log_path, None, uid=pwncat.victim.users[user].id
yield PotentialPassword(
self.name, password, implant.log, lineno, user_info.id
)
except (FileNotFoundError, PermissionError):
pass

View File

@ -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]")

View File

@ -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()

View File

@ -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()

View File

@ -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,6 +208,10 @@ class Module(EnumerateModule):
# Yield the sudo rule
yield rule
rules.append(rule)
for rule in rules:
# We can't handle abilities which we didn't parse properly
if not rule.matched:
continue

View File

@ -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: