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

Go started on software

This commit is contained in:
John Hammond 2021-05-07 23:41:46 -04:00
parent eb068ac493
commit fb7cff5d80
3 changed files with 102 additions and 63 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

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