1
0
mirror of https://github.com/calebstewart/pwncat.git synced 2024-11-23 17:15:38 +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 re
import rich.markup
import pwncat
from pwncat.db import Fact
from pwncat.platform.linux import Linux
from pwncat.modules import Status
from pwncat.modules.agnostic.enumerate import EnumerateModule, Schedule
@dataclasses.dataclass
class CronEntry:
class CronEntry(Fact):
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 """
uid: int
self.uid: int = uid
""" The user ID this entry will run as """
command: str
self.command: str = command
""" The command that will execute """
datetime: str
self.datetime: str = datetime
""" The entire date/time specifier from the crontab entry """
def __str__(self):

View File

@ -3,9 +3,12 @@ import dataclasses
import re
from typing import Generator, Optional, List
import rich.markup
import pwncat
from pwncat.platform.linux import Linux
from pwncat import util
from pwncat.db import Fact
from pwncat.platform.linux import Linux
from pwncat.modules.agnostic.enumerate import EnumerateModule, Schedule
per_user = True
@ -19,29 +22,43 @@ sudo_pattern = re.compile(
directives = ["Defaults", "User_Alias", "Runas_Alias", "Host_Alias", "Cmnd_Alias"]
@dataclasses.dataclass
class SudoSpec:
class SudoSpec(Fact):
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 """
matched: bool = False
self.matched: bool = False
""" The regular expression match data. If this is None, all following fields
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 """
group: Optional[str] = None
self.group: Optional[str] = None
""" 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 """
runas_user: Optional[str] = None
self.runas_user: Optional[str] = None
""" 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)"""
options: List[str] = None
self.options: List[str] = None
""" 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 """
commands: List[str] = None
self.commands: List[str] = None
""" The command specification """
def __str__(self):
@ -51,28 +68,32 @@ class SudoSpec:
return self.line
if self.user is not None:
display += f"User [blue]{self.user}[/blue]: "
display += f"User [blue]{rich.markup.escape(self.user)}[/blue]: "
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":
display += f"[red]root[/red]"
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":
display += f":[red]root[/red]"
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:
display += f" on [magenta]{self.host}[/magenta]"
display += f" on [magenta]{rich.markup.escape(self.host)}[/magenta]"
if self.options:
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
@ -151,17 +172,17 @@ class Module(EnumerateModule):
PLATFORM = [Linux]
SCHEDULE = Schedule.PER_USER
def enumerate(self):
def enumerate(self, session):
try:
with pwncat.victim.open("/etc/sudoers", "r") as filp:
with session.platform.open("/etc/sudoers", "r") as filp:
for line in filp:
line = line.strip()
# Ignore comments and empty lines
if line.startswith("#") or line == "":
continue
yield "sudo", LineParser(line)
yield LineParser(line)
# No need to parse `sudo -l`, since can read /etc/sudoers
return
@ -170,10 +191,13 @@ class Module(EnumerateModule):
# Check for our privileges
try:
result = pwncat.victim.sudo("-nl", send_password=False).decode("utf-8")
if result.strip() == "sudo: a password is required":
result = pwncat.victim.sudo("-l").decode("utf-8")
proc = session.platform.sudo(["sudo", "-nl"], as_is=True)
result = proc.stdout.read()
proc.wait() # ensure this closes properly
except PermissionError:
# if this asks for a password and we don't have one, bail
return
for line in result.split("\n"):
@ -191,6 +215,6 @@ class Module(EnumerateModule):
continue
# 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 re
from pwncat.modules.agnostic.enumerate import EnumerateModule, Schedule
import rich.markup
import pwncat
from pwncat.db import Fact
from pwncat.platform.linux import Linux
from pwncat.subprocess import CalledProcessError
from pwncat.modules.agnostic.enumerate import EnumerateModule, Schedule
@dataclasses.dataclass
class SudoVersion:
class SudoVersion(Fact):
"""
Version of the installed sudo binary may be useful for exploitation
"""
version: str
output: str
vulnerable: bool
def __init__(self, source, version, output, vulnerable):
super().__init__(source=source, types=["software.sudo.version"])
self.version: str = version
self.output: str = output
self.vulnerable: bool = vulnerable
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:
result += f" (may be [red]vulnerable[/red])"
return result
@ -44,18 +50,23 @@ class Module(EnumerateModule):
PLATFORM = [Linux]
SCHEDULE = Schedule.ONCE
def enumerate(self):
def enumerate(self, session):
"""
Enumerate kernel/OS version information
Enumerate the currently running version of sudo
:return:
"""
try:
# Check the sudo version number
result = pwncat.victim.env(["sudo", "--version"]).decode("utf-8").strip()
except FileNotFoundError:
result = session.platform.run(
["sudo", "--version"], capture_output=True, check=True
)
except CalledProcessError:
# Something went wrong with the sudo version
return
version = result.stdout.decode("utf-8")
# Taken from here:
# https://book.hacktricks.xyz/linux-unix/privilege-escalation#sudo-version
known_vulnerable = [
@ -76,7 +87,7 @@ class Module(EnumerateModule):
# Can we match this output to a specific sudo version?
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:
vulnerable = False
@ -87,9 +98,9 @@ class Module(EnumerateModule):
vulnerable = True
break
yield "sudo.version", SudoVersion(match.group(1), result, vulnerable)
yield SudoVersion(self.name, match.group(1), version, vulnerable)
return
# We couldn't parse the version out, but at least give the full version
# output in the long form/report of enumeration.
yield "software.sudo.version", SudoVersion("unknown", result, False)
yield SudoVersion(self.name, "unknown", version, False)