1
0
mirror of https://github.com/calebstewart/pwncat.git synced 2024-11-27 19:04:15 +01:00

Merge branch 'platforms' of github.com:calebstewart/pwncat into platforms

This commit is contained in:
Caleb Stewart 2021-05-08 00:50:04 -04:00
commit b6f2ae78a5
6 changed files with 174 additions and 86 deletions

View File

@ -3,22 +3,26 @@ import dataclasses
import os import os
import re import re
import rich.markup
import pwncat import pwncat
from pwncat.db import Fact
from pwncat.platform.linux import Linux from pwncat.platform.linux import Linux
from pwncat.modules import Status from pwncat.modules import Status
from pwncat.modules.agnostic.enumerate import EnumerateModule, Schedule from pwncat.modules.agnostic.enumerate import EnumerateModule, Schedule
@dataclasses.dataclass class CronEntry(Fact):
class CronEntry: def __init__(self, source, path, uid, command, datetime):
super().__init__(source=source, types=["software.cron.entry"])
path: str self.path: str = path
""" The path to the crontab where this was found """ """ The path to the crontab where this was found """
uid: int self.uid: int = uid
""" The user ID this entry will run as """ """ The user ID this entry will run as """
command: str self.command: str = command
""" The command that will execute """ """ The command that will execute """
datetime: str self.datetime: str = datetime
""" The entire date/time specifier from the crontab entry """ """ The entire date/time specifier from the crontab entry """
def __str__(self): def __str__(self):

View File

@ -4,20 +4,31 @@ import os
import re import re
import shlex import shlex
import rich.markup
import pwncat import pwncat
from pwncat.modules.agnostic.enumerate import EnumerateModule, Schedule from pwncat.db import Fact
from pwncat.platform.linux import Linux from pwncat.platform.linux import Linux
from pwncat.modules.agnostic.enumerate import EnumerateModule, Schedule
from pwncat.subprocess import CalledProcessError
@dataclasses.dataclass """
class ScreenVersion: TODO: This should realistically yield an ability (which can be used for
privilege escalation)... but we can implement that later.
"""
path: str
perms: int class ScreenVersion(Fact):
vulnerable: bool = True def __init__(self, source, path, perms, vulnerable):
super().__init__(source=source, types=["software.screen.version"])
self.path: str = path
self.perms: int = perms
self.vulnerable: bool = vulnerable
def __str__(self): def __str__(self):
return f"[cyan]{self.path}[/cyan] (perms: [blue]{oct(self.perms)[2:]}[/blue])" return f"[cyan]{rich.markup.escape(self.path)}[/cyan] (perms: [blue]{oct(self.perms)[2:]}[/blue]) [bold red]is vulnerable[/bold red]"
class Module(EnumerateModule): class Module(EnumerateModule):
@ -31,14 +42,14 @@ class Module(EnumerateModule):
PLATFORM = [Linux] PLATFORM = [Linux]
SCHEDULE = Schedule.ONCE SCHEDULE = Schedule.ONCE
def enumerate(self): def enumerate(self, session):
""" """
Enumerate kernel/OS version information Enumerate locations of vulnerable screen versions
:return: :return:
""" """
# Grab current path plus other interesting paths # Grab current path plus other interesting paths
paths = set(pwncat.victim.getenv("PATH").split(":")) paths = set(session.platform.getenv("PATH").split(":"))
paths = paths | { paths = paths | {
"/bin", "/bin",
"/sbin", "/sbin",
@ -49,11 +60,17 @@ class Module(EnumerateModule):
} }
# Look for matching binaries # Look for matching binaries
with pwncat.victim.subprocess( proc = session.platform.Popen(
f"find {shlex.join(paths)} \\( -type f -or -type l \\) -executable \\( -name 'screen' -or -name 'screen-*' \\) -printf '%#m %p\\n' 2>/dev/null" f"find {shlex.join(paths)} \\( -type f -or -type l \\) -executable \\( -name 'screen' -or -name 'screen-*' \\) -printf '%#m %p\\n' 2>/dev/null",
) as pipe: shell=True,
for line in pipe: text=True,
line = line.decode("utf-8").strip() stdout=pwncat.subprocess.PIPE,
)
# First, collect all the paths to a `screen` binary we can find
screen_paths = []
for line in proc.stdout:
line = line.strip()
perms, *path = line.split(" ") perms, *path = line.split(" ")
path = " ".join(path) path = " ".join(path)
perms = int(perms, 8) perms = int(perms, 8)
@ -62,4 +79,37 @@ class Module(EnumerateModule):
if os.path.splitext(path)[1] in [".c", ".o", ".h"]: if os.path.splitext(path)[1] in [".c", ".o", ".h"]:
continue continue
yield "software.screen.version", ScreenVersion(path, perms) if perms & 0o4000:
# if this is executable
screen_paths.append(path)
# Now, check each screen version to determine if it is vulnerable
for screen_path in screen_paths:
version_output = session.platform.Popen(
f"{screen_path} --version",
shell=True,
text=True,
stdout=pwncat.subprocess.PIPE,
)
for line in version_output.stdout:
# This process checks if it is a vulnerable version of screen
match = re.search(r"(\d+\.\d+\.\d+)", line)
if not match:
continue
version_triplet = [int(x) for x in match.group().split(".")]
if version_triplet[0] > 4:
continue
if version_triplet[0] == 4 and version_triplet[1] > 5:
continue
if (
version_triplet[0] == 4
and version_triplet[1] == 5
and version_triplet[2] >= 1
):
continue
yield ScreenVersion(self.name, path, perms, vulnerable=True)

View File

@ -3,9 +3,12 @@ import dataclasses
import re import re
from typing import Generator, Optional, List from typing import Generator, Optional, List
import rich.markup
import pwncat import pwncat
from pwncat.platform.linux import Linux
from pwncat import util from pwncat import util
from pwncat.db import Fact
from pwncat.platform.linux import Linux
from pwncat.modules.agnostic.enumerate import EnumerateModule, Schedule from pwncat.modules.agnostic.enumerate import EnumerateModule, Schedule
per_user = True per_user = True
@ -19,29 +22,43 @@ sudo_pattern = re.compile(
directives = ["Defaults", "User_Alias", "Runas_Alias", "Host_Alias", "Cmnd_Alias"] directives = ["Defaults", "User_Alias", "Runas_Alias", "Host_Alias", "Cmnd_Alias"]
@dataclasses.dataclass class SudoSpec(Fact):
class SudoSpec: def __init__(
self,
source,
line: str,
matched: bool = False,
user: Optional[str] = None,
group: Optional[str] = None,
host: Optional[str] = None,
runas_user: Optional[str] = None,
runas_group: Optional[str] = None,
options: List[str] = None,
hash: str = None,
commands: List[str] = None,
):
super().__init__(source=source, types=["software.sudo.rule"])
line: str self.line: str
""" The full, unaltered line from the sudoers file """ """ The full, unaltered line from the sudoers file """
matched: bool = False self.matched: bool = False
""" The regular expression match data. If this is None, all following fields """ The regular expression match data. If this is None, all following fields
are invalid and should not be used. """ are invalid and should not be used. """
user: Optional[str] = None self.user: Optional[str] = None
""" The user which this rule applies to. This is None if a group was specified """ """ The user which this rule applies to. This is None if a group was specified """
group: Optional[str] = None self.group: Optional[str] = None
""" The group this rule applies to. This is None if a user was specified. """ """ The group this rule applies to. This is None if a user was specified. """
host: Optional[str] = None self.host: Optional[str] = None
""" The host this rule applies to """ """ The host this rule applies to """
runas_user: Optional[str] = None self.runas_user: Optional[str] = None
""" The user we are allowed to run as """ """ The user we are allowed to run as """
runas_group: Optional[str] = None self.runas_group: Optional[str] = None
""" The GID we are allowed to run as (may be None)""" """ The GID we are allowed to run as (may be None)"""
options: List[str] = None self.options: List[str] = None
""" A list of options specified (e.g. NOPASSWD, SETENV, etc) """ """ A list of options specified (e.g. NOPASSWD, SETENV, etc) """
hash: str = None self.hash: str = None
""" A hash type and value which sudo will obey """ """ A hash type and value which sudo will obey """
commands: List[str] = None self.commands: List[str] = None
""" The command specification """ """ The command specification """
def __str__(self): def __str__(self):
@ -51,28 +68,32 @@ class SudoSpec:
return self.line return self.line
if self.user is not None: if self.user is not None:
display += f"User [blue]{self.user}[/blue]: " display += f"User [blue]{rich.markup.escape(self.user)}[/blue]: "
else: else:
display += f"Group [cyan]{self.group}[/cyan]: " display += f"Group [cyan]{rich.markup.escape(self.group)}[/cyan]: "
display += f"[yellow]{'[/yellow], [yellow]'.join(self.commands)}[/yellow] as " display += f"[yellow]{'[/yellow], [yellow]'.join((rich.markup.escape(x) for c in self.commands))}[/yellow] as "
if self.runas_user == "root": if self.runas_user == "root":
display += f"[red]root[/red]" display += f"[red]root[/red]"
elif self.runas_user is not None: elif self.runas_user is not None:
display += f"[blue]{self.runas_user}[/blue]" display += f"[blue]{rich.markup.escape(self.runas_user)}[/blue]"
if self.runas_group == "root": if self.runas_group == "root":
display += f":[red]root[/red]" display += f":[red]root[/red]"
elif self.runas_group is not None: elif self.runas_group is not None:
display += f"[cyan]{self.runas_group}[/cyan]" display += f"[cyan]{rich.markup.escape(self.runas_group)}[/cyan]"
if self.host is not None: if self.host is not None:
display += f" on [magenta]{self.host}[/magenta]" display += f" on [magenta]{rich.markup.escape(self.host)}[/magenta]"
if self.options: if self.options:
display += ( display += (
" (" + ",".join(f"[green]{x}[/green]" for x in self.options) + ")" " ("
+ ",".join(
f"[green]{rich.markup.escape(x)}[/green]" for x in self.options
)
+ ")"
) )
return display return display
@ -151,17 +172,17 @@ class Module(EnumerateModule):
PLATFORM = [Linux] PLATFORM = [Linux]
SCHEDULE = Schedule.PER_USER SCHEDULE = Schedule.PER_USER
def enumerate(self): def enumerate(self, session):
try: try:
with pwncat.victim.open("/etc/sudoers", "r") as filp: with session.platform.open("/etc/sudoers", "r") as filp:
for line in filp: for line in filp:
line = line.strip() line = line.strip()
# Ignore comments and empty lines # Ignore comments and empty lines
if line.startswith("#") or line == "": if line.startswith("#") or line == "":
continue continue
yield "sudo", LineParser(line) yield LineParser(line)
# No need to parse `sudo -l`, since can read /etc/sudoers # No need to parse `sudo -l`, since can read /etc/sudoers
return return
@ -170,10 +191,13 @@ class Module(EnumerateModule):
# Check for our privileges # Check for our privileges
try: try:
result = pwncat.victim.sudo("-nl", send_password=False).decode("utf-8")
if result.strip() == "sudo: a password is required": proc = session.platform.sudo(["sudo", "-nl"], as_is=True)
result = pwncat.victim.sudo("-l").decode("utf-8") result = proc.stdout.read()
proc.wait() # ensure this closes properly
except PermissionError: except PermissionError:
# if this asks for a password and we don't have one, bail
return return
for line in result.split("\n"): for line in result.split("\n"):
@ -191,6 +215,6 @@ class Module(EnumerateModule):
continue continue
# Build the beginning part of a normal spec # Build the beginning part of a normal spec
line = f"{pwncat.victim.current_user.name} local=" + line.strip() line = f"{session.current_user()} local=" + line.strip()
yield "software.sudo.rule", LineParser(line) yield LineParser(line)

View File

@ -2,24 +2,30 @@
import dataclasses import dataclasses
import re import re
from pwncat.modules.agnostic.enumerate import EnumerateModule, Schedule import rich.markup
import pwncat import pwncat
from pwncat.db import Fact
from pwncat.platform.linux import Linux from pwncat.platform.linux import Linux
from pwncat.subprocess import CalledProcessError
from pwncat.modules.agnostic.enumerate import EnumerateModule, Schedule
@dataclasses.dataclass class SudoVersion(Fact):
class SudoVersion:
""" """
Version of the installed sudo binary may be useful for exploitation Version of the installed sudo binary may be useful for exploitation
""" """
version: str def __init__(self, source, version, output, vulnerable):
output: str super().__init__(source=source, types=["software.sudo.version"])
vulnerable: bool
self.version: str = version
self.output: str = output
self.vulnerable: bool = vulnerable
def __str__(self): def __str__(self):
result = f"[yellow]sudo[/yellow] version [cyan]{self.version}[/cyan]" result = f"[yellow]sudo[/yellow] version [cyan]{rich.markup.escape(self.version)}[/cyan]"
if self.vulnerable: if self.vulnerable:
result += f" (may be [red]vulnerable[/red])" result += f" (may be [red]vulnerable[/red])"
return result return result
@ -44,18 +50,23 @@ class Module(EnumerateModule):
PLATFORM = [Linux] PLATFORM = [Linux]
SCHEDULE = Schedule.ONCE SCHEDULE = Schedule.ONCE
def enumerate(self): def enumerate(self, session):
""" """
Enumerate kernel/OS version information Enumerate the currently running version of sudo
:return: :return:
""" """
try: try:
# Check the sudo version number # Check the sudo version number
result = pwncat.victim.env(["sudo", "--version"]).decode("utf-8").strip() result = session.platform.run(
except FileNotFoundError: ["sudo", "--version"], capture_output=True, check=True
)
except CalledProcessError:
# Something went wrong with the sudo version
return return
version = result.stdout.decode("utf-8")
# Taken from here: # Taken from here:
# https://book.hacktricks.xyz/linux-unix/privilege-escalation#sudo-version # https://book.hacktricks.xyz/linux-unix/privilege-escalation#sudo-version
known_vulnerable = [ known_vulnerable = [
@ -76,7 +87,7 @@ class Module(EnumerateModule):
# Can we match this output to a specific sudo version? # Can we match this output to a specific sudo version?
match = re.search( match = re.search(
r"sudo version ([0-9]+\.[0-9]+\.[^\s]*)", result, re.IGNORECASE r"sudo version ([0-9]+\.[0-9]+\.[^\s]*)", version, re.IGNORECASE
) )
if match is not None and match.group(1) is not None: if match is not None and match.group(1) is not None:
vulnerable = False vulnerable = False
@ -87,9 +98,9 @@ class Module(EnumerateModule):
vulnerable = True vulnerable = True
break break
yield "sudo.version", SudoVersion(match.group(1), result, vulnerable) yield SudoVersion(self.name, match.group(1), version, vulnerable)
return return
# We couldn't parse the version out, but at least give the full version # We couldn't parse the version out, but at least give the full version
# output in the long form/report of enumeration. # output in the long form/report of enumeration.
yield "software.sudo.version", SudoVersion("unknown", result, False) yield SudoVersion(self.name, "unknown", version, False)

View File

@ -35,7 +35,6 @@ class Module(EnumerateModule):
yield group yield group
except Exception as exc: except Exception as exc:
raise ModuleFailed(f"something fucked {exc}")
# Bad group line # Bad group line
continue continue

View File

@ -1217,7 +1217,7 @@ class Linux(Platform):
popen_kwargs["env"] = None popen_kwargs["env"] = None
if password is None: if password is None:
password = self.current_user.password password = self.session.current_user().password
# At this point, the command is a string # At this point, the command is a string
if not as_is: if not as_is: