mirror of
https://github.com/calebstewart/pwncat.git
synced 2024-11-30 20:34:15 +01:00
Added separate domain user and group enumerations
This commit is contained in:
parent
9c522b6997
commit
686caba900
@ -60,7 +60,6 @@ class WindowsUser(User):
|
|||||||
password_last_set: Optional[datetime],
|
password_last_set: Optional[datetime],
|
||||||
last_logon: Optional[datetime],
|
last_logon: Optional[datetime],
|
||||||
principal_source: str,
|
principal_source: str,
|
||||||
domain: Optional[str] = None,
|
|
||||||
password: Optional[str] = None,
|
password: Optional[str] = None,
|
||||||
hash: Optional[str] = None,
|
hash: Optional[str] = None,
|
||||||
):
|
):
|
||||||
@ -79,15 +78,14 @@ class WindowsUser(User):
|
|||||||
self.password_last_set: Optional[datetime] = password_last_set
|
self.password_last_set: Optional[datetime] = password_last_set
|
||||||
self.last_logon: Optional[datetime] = last_logon
|
self.last_logon: Optional[datetime] = last_logon
|
||||||
self.principal_source: str = principal_source
|
self.principal_source: str = principal_source
|
||||||
self.domain: Optional[str] = domain
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
if self.password is None and self.hash is None:
|
if self.password is None and self.hash is None:
|
||||||
return f"""User(uid={self.id}, name={repr(self.name)}, domain={repr(self.domain)})"""
|
return f"""User(uid={self.id}, name={repr(self.name)})"""
|
||||||
elif self.password is not None:
|
elif self.password is not None:
|
||||||
return f"""User(uid={repr(self.id)}, name={repr(self.name)}, domain={repr(self.domain)}, password={repr(self.password)})"""
|
return f"""User(uid={repr(self.id)}, name={repr(self.name)}, password={repr(self.password)})"""
|
||||||
else:
|
else:
|
||||||
return f"""User(uid={repr(self.id)}, name={repr(self.name)}, domain={repr(self.domain)}, hash={repr(self.hash)})"""
|
return f"""User(uid={repr(self.id)}, name={repr(self.name)}, hash={repr(self.hash)})"""
|
||||||
|
|
||||||
|
|
||||||
class WindowsGroup(Group):
|
class WindowsGroup(Group):
|
||||||
|
123
pwncat/modules/windows/enumerate/domain/user.py
Normal file
123
pwncat/modules/windows/enumerate/domain/user.py
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
from datetime import datetime
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
from pwncat.db import Fact
|
||||||
|
from pwncat.modules import Status, ModuleFailed
|
||||||
|
from pwncat.facts.windows import WindowsUser
|
||||||
|
from pwncat.platform.windows import Windows, PowershellError
|
||||||
|
from pwncat.modules.enumerate import Schedule, EnumerateModule
|
||||||
|
|
||||||
|
|
||||||
|
class DomainUser(WindowsUser):
|
||||||
|
""" Builds on Windows Groups to add domain specific information """
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
source: str,
|
||||||
|
name: str,
|
||||||
|
uid: str,
|
||||||
|
account_expires: Optional[datetime],
|
||||||
|
description: str,
|
||||||
|
enabled: bool,
|
||||||
|
full_name: str,
|
||||||
|
password_changeable_date: Optional[datetime],
|
||||||
|
password_expires: Optional[datetime],
|
||||||
|
user_may_change_password: bool,
|
||||||
|
password_required: bool,
|
||||||
|
password_last_set: Optional[datetime],
|
||||||
|
last_logon: Optional[datetime],
|
||||||
|
principal_source: str,
|
||||||
|
domain: str,
|
||||||
|
data: Dict,
|
||||||
|
password: Optional[str] = None,
|
||||||
|
hash: Optional[str] = None,
|
||||||
|
):
|
||||||
|
super().__init__(
|
||||||
|
source=source,
|
||||||
|
name=name,
|
||||||
|
uid=uid,
|
||||||
|
account_expires=account_expires,
|
||||||
|
description=description,
|
||||||
|
enabled=enabled,
|
||||||
|
full_name=full_name,
|
||||||
|
password_changeable_date=password_changeable_date,
|
||||||
|
password_expires=password_expires,
|
||||||
|
user_may_change_password=user_may_change_password,
|
||||||
|
password_required=password_required,
|
||||||
|
password_last_set=password_last_set,
|
||||||
|
last_logon=last_logon,
|
||||||
|
principal_source=principal_source,
|
||||||
|
password=password,
|
||||||
|
hash=hash,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.types.append("domain.user")
|
||||||
|
|
||||||
|
self.domain = domain
|
||||||
|
|
||||||
|
if "description" in data:
|
||||||
|
data["user_description"] = data.get("description")
|
||||||
|
del data["description"]
|
||||||
|
|
||||||
|
self.__dict__.update(data)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
if self.password is None and self.hash is None:
|
||||||
|
return f"""DomainUser(uid={self.id}, name={repr(self.name)}, domain={repr(self.domain)})"""
|
||||||
|
elif self.password is not None:
|
||||||
|
return f"""DomainUser(uid={repr(self.id)}, name={repr(self.name)}, domain={repr(self.domain)}, password={repr(self.password)})"""
|
||||||
|
else:
|
||||||
|
return f"""DomainUser(uid={repr(self.id)}, name={repr(self.name)}, domain={repr(self.domain)}, hash={repr(self.hash)})"""
|
||||||
|
|
||||||
|
|
||||||
|
class Module(EnumerateModule):
|
||||||
|
""" Retrieve information on all domain computers """
|
||||||
|
|
||||||
|
PLATFORM = [Windows]
|
||||||
|
PROVIDES = ["domain.user", "user"]
|
||||||
|
SCHEDULE = Schedule.ONCE
|
||||||
|
|
||||||
|
def enumerate(self, session: "pwncat.manager.Session"):
|
||||||
|
""" Perform enumeration """
|
||||||
|
|
||||||
|
# Ensure we have PowerView loaded
|
||||||
|
yield Status("loading powersploit recon")
|
||||||
|
session.run("powersploit", group="recon")
|
||||||
|
|
||||||
|
try:
|
||||||
|
domain = session.run("enumerate.domain")[0]
|
||||||
|
except IndexError:
|
||||||
|
# Not a domain joined machine
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
yield Status("requesting domain groups")
|
||||||
|
users = session.platform.powershell("Get-DomainUser")[0]
|
||||||
|
except (IndexError, PowershellError) as exc:
|
||||||
|
# Doesn't appear to be a domain joined user
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(users, dict):
|
||||||
|
users = [users]
|
||||||
|
|
||||||
|
for user in users:
|
||||||
|
yield DomainUser(
|
||||||
|
source=self.name,
|
||||||
|
name=user["samaccountname"],
|
||||||
|
uid=user["objectsid"],
|
||||||
|
account_expires=user.get("accountexpires"),
|
||||||
|
description=user.get("description") or "",
|
||||||
|
enabled=True,
|
||||||
|
full_name=user.get("name") or "",
|
||||||
|
password_changeable_date=None,
|
||||||
|
password_expires=None,
|
||||||
|
user_may_change_password=True,
|
||||||
|
password_required=True,
|
||||||
|
password_last_set=None,
|
||||||
|
last_logon=None,
|
||||||
|
principal_source="",
|
||||||
|
domain=domain["Name"],
|
||||||
|
data=user,
|
||||||
|
)
|
@ -40,63 +40,4 @@ class Module(EnumerateModule):
|
|||||||
password_last_set=None,
|
password_last_set=None,
|
||||||
last_logon=None,
|
last_logon=None,
|
||||||
principal_source=user["PrincipalSource"],
|
principal_source=user["PrincipalSource"],
|
||||||
domain=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = session.platform.powershell(
|
|
||||||
"(Get-WmiObject Win32_ComputerSystem).PartOfDomain"
|
|
||||||
)[0]
|
|
||||||
except (KeyError, PowershellError):
|
|
||||||
# Unable to grab domain status
|
|
||||||
return
|
|
||||||
|
|
||||||
# Not in a domain
|
|
||||||
if not result:
|
|
||||||
return
|
|
||||||
|
|
||||||
# We are in a domain, load powerview
|
|
||||||
session.run("powersploit", group="recon")
|
|
||||||
|
|
||||||
try:
|
|
||||||
results = session.platform.powershell("Get-DomainUser")[0]
|
|
||||||
except (KeyError, PowershellError):
|
|
||||||
# We coudln't retrieve domain users :(
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(results, dict):
|
|
||||||
results = [results]
|
|
||||||
|
|
||||||
for user in results:
|
|
||||||
|
|
||||||
dn = user.get("distinguishedname")
|
|
||||||
if dn is None:
|
|
||||||
domain = "unknown"
|
|
||||||
else:
|
|
||||||
dn = dn.split(",")
|
|
||||||
domain = []
|
|
||||||
for element in dn[::-1]:
|
|
||||||
if element.startswith("DC="):
|
|
||||||
domain.insert(0, element.split("=")[1])
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
domain = ".".join(domain)
|
|
||||||
|
|
||||||
yield WindowsUser(
|
|
||||||
source=self.name,
|
|
||||||
name=user["samaccountname"],
|
|
||||||
uid=user["objectsid"],
|
|
||||||
account_expires=None,
|
|
||||||
description=user.get("description") or "",
|
|
||||||
enabled=True,
|
|
||||||
full_name=user.get("name") or "",
|
|
||||||
password_changeable_date=None,
|
|
||||||
password_expires=None,
|
|
||||||
user_may_change_password=True,
|
|
||||||
password_required=True,
|
|
||||||
password_last_set=None,
|
|
||||||
last_logon=None,
|
|
||||||
principal_source="",
|
|
||||||
domain=domain,
|
|
||||||
)
|
)
|
||||||
|
@ -1,83 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import dataclasses
|
|
||||||
from enum import IntFlag
|
|
||||||
|
|
||||||
from rich.table import Table
|
|
||||||
from pwncat.facts import Fact
|
|
||||||
from pwncat.platform.windows import Windows
|
|
||||||
from pwncat.modules.enumerate import Schedule, EnumerateModule
|
|
||||||
|
|
||||||
|
|
||||||
class LuidAttributes(IntFlag):
|
|
||||||
|
|
||||||
DISABLED = 0x00
|
|
||||||
SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x01
|
|
||||||
SE_PRIVILEGE_ENABLED = 0x02
|
|
||||||
SE_PRIVILEGE_REMOVE = 0x04
|
|
||||||
SE_PRIVILEGE_USED_FOR_ACCESS = 0x80000000
|
|
||||||
|
|
||||||
|
|
||||||
class TokenPrivilegeData(Fact):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
source: str,
|
|
||||||
privilege: str,
|
|
||||||
attributes: LuidAttributes,
|
|
||||||
token_handle: int,
|
|
||||||
pid: int,
|
|
||||||
):
|
|
||||||
super().__init__(source=source, types=["user.privs"])
|
|
||||||
|
|
||||||
self.privilege: str = privilege
|
|
||||||
self.attributes: LuidAttributes = attributes
|
|
||||||
# self.token_handle: int = token_handle
|
|
||||||
# self.pid: int = pid
|
|
||||||
|
|
||||||
def title(self, session: "pwncat.manager.Session"):
|
|
||||||
|
|
||||||
if self.attributes == LuidAttributes.DISABLED:
|
|
||||||
color = "red"
|
|
||||||
else:
|
|
||||||
color = "green"
|
|
||||||
|
|
||||||
attrs = str(self.attributes)
|
|
||||||
attrs = attrs.replace("LuidAttributes.", "")
|
|
||||||
attrs = attrs.replace("|", "[/cyan]|[cyan]")
|
|
||||||
attrs = "[cyan]" + attrs + "[/cyan]"
|
|
||||||
|
|
||||||
return f"[{color}]{self.privilege}[/{color}] -> {attrs}"
|
|
||||||
|
|
||||||
|
|
||||||
class Module(EnumerateModule):
|
|
||||||
"""Enumerate user privileges using PowerView's Get-ProcessTokenPrivilege"""
|
|
||||||
|
|
||||||
PROVIDES = ["user.privs"]
|
|
||||||
PLATFORM = [Windows]
|
|
||||||
SCHEDULE = Schedule.ALWAYS
|
|
||||||
|
|
||||||
def enumerate(self, session: "pwncat.manager.Session"):
|
|
||||||
|
|
||||||
# Ensure that powerview is loaded
|
|
||||||
session.run(
|
|
||||||
"manage.powershell.import",
|
|
||||||
path="PowerShellMafia/PowerSploit/Privesc/PowerUp.ps1",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Grab our current token privileges
|
|
||||||
results = session.platform.powershell("Get-ProcessTokenPrivilege")
|
|
||||||
if len(results) == 0:
|
|
||||||
session.log("[red]error[/red]: Get-ProcessTokenPrivilege failed")
|
|
||||||
return
|
|
||||||
|
|
||||||
# They end up in an array in an array
|
|
||||||
privs = results[0]
|
|
||||||
|
|
||||||
# Create our enumeration data types
|
|
||||||
for priv in privs:
|
|
||||||
yield TokenPrivilegeData(
|
|
||||||
source=self.name,
|
|
||||||
privilege=priv["Privilege"],
|
|
||||||
attributes=LuidAttributes(priv["Attributes"]),
|
|
||||||
token_handle=priv["TokenHandle"],
|
|
||||||
pid=priv["ProcessId"],
|
|
||||||
)
|
|
Loading…
Reference in New Issue
Block a user