mirror of
https://github.com/calebstewart/pwncat.git
synced 2024-11-24 01:25:37 +01:00
Go started on software
This commit is contained in:
parent
eb068ac493
commit
fb7cff5d80
@ -3,23 +3,27 @@ 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):
|
||||||
return (
|
return (
|
||||||
|
@ -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,30 +22,44 @@ 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):
|
||||||
display = ""
|
display = ""
|
||||||
@ -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)
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user