diff --git a/CHANGELOG.md b/CHANGELOG.md index a51a771..d038434 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 The Changelog starts with v0.4.1, because we did not keep one before that, and simply didn't have the time to go back and retroactively create one. +## [Unreleased] +### Fixed +- Fixed `shlex.join` use with non-str type objects (e.g. `RemotePath`) +- Fixed `set` command use with incorrect keys (e.g. `set invalid value`) + +### Added +- Added missed `PlatformError` for `upload` command (e.g. "no gtfobins writers available") + ## [0.5.4] - 2022-01-27 Bug fix for the `load` command. diff --git a/docs/source/conf.py b/docs/source/conf.py index 80faa23..390c04d 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -12,7 +12,8 @@ # import os import sys -sys.path.insert(0, os.path.abspath('../..')) + +sys.path.insert(0, os.path.abspath("../..")) # -- Project information ----------------------------------------------------- diff --git a/pwncat/commands/run.py b/pwncat/commands/run.py index 0dd9561..b205496 100644 --- a/pwncat/commands/run.py +++ b/pwncat/commands/run.py @@ -68,12 +68,6 @@ class Command(CommandDefinition): if args.module is not None: manager.config.back() - except pwncat.modules.ModuleFailed as exc: - if args.traceback: - console.print_exception() - else: - console.log(f"[red]error[/red]: module failed: {exc}") - return except pwncat.modules.ModuleNotFound: console.log(f"[red]error[/red]: {module_name}: not found") return @@ -86,6 +80,12 @@ class Command(CommandDefinition): except pwncat.modules.InvalidArgument as exc: console.log(f"[red]error[/red]: invalid argument: {exc}") return + except pwncat.modules.ModuleFailed as exc: + if args.traceback: + console.print_exception() + else: + console.log(f"[red]error[/red]: module failed: {exc}") + return if isinstance(result, list): result = [r for r in result if not r.hidden] diff --git a/pwncat/commands/set.py b/pwncat/commands/set.py index 0a5296d..9f28c00 100644 --- a/pwncat/commands/set.py +++ b/pwncat/commands/set.py @@ -80,9 +80,14 @@ class Command(CommandDefinition): try: if manager.sessions and args.variable == "db": raise ValueError("cannot change database with running session") - manager.config.set( - args.variable, args.value, getattr(args, "global") - ) + if args.variable in manager.config: + manager.config.set( + args.variable, args.value, getattr(args, "global") + ) + else: + console.log( + f"[red]error[/red]: invalid choice {repr(args.variable)}" + ) if args.variable == "db": # Ensure the database is re-opened, if it was already manager.open_database() @@ -95,10 +100,15 @@ class Command(CommandDefinition): except ValueError as exc: console.log(f"[red]error[/red]: {exc}") elif args.variable is not None: - value = manager.config[args.variable] - console.print( - f" [cyan]{args.variable}[/cyan] = [yellow]{repr(value)}[/yellow]" - ) + if args.variable in manager.config: + value = manager.config[args.variable] + console.print( + f" [cyan]{args.variable}[/cyan] = [yellow]{repr(value)}[/yellow]" + ) + else: + console.log( + f"[red]error[/red]: invalid choice {repr(args.variable)}" + ) else: for name in manager.config: value = manager.config[name] diff --git a/pwncat/commands/upload.py b/pwncat/commands/upload.py index badb828..ffa86c7 100644 --- a/pwncat/commands/upload.py +++ b/pwncat/commands/upload.py @@ -14,6 +14,7 @@ from rich.progress import ( import pwncat from pwncat.util import console, copyfileobj, human_readable_size, human_readable_delta from pwncat.commands import Complete, Parameter, CommandDefinition +from pwncat.platform import PlatformError class Command(CommandDefinition): @@ -77,5 +78,10 @@ class Command(CommandDefinition): f"uploaded [cyan]{human_readable_size(length)}[/cyan] " f"in [green]{human_readable_delta(elapsed)}[/green]" ) - except (FileNotFoundError, PermissionError, IsADirectoryError) as exc: + except ( + FileNotFoundError, + PermissionError, + IsADirectoryError, + PlatformError, + ) as exc: self.parser.error(str(exc)) diff --git a/pwncat/modules/linux/implant/authorized_key.py b/pwncat/modules/linux/implant/authorized_key.py index 7a295d3..88b9df4 100644 --- a/pwncat/modules/linux/implant/authorized_key.py +++ b/pwncat/modules/linux/implant/authorized_key.py @@ -41,7 +41,7 @@ class AuthorizedKeyImplant(PrivateKey): user = session.find_user(uid=self.uid) if current_user.id != self.uid and current_user.id != 0: - raise ModuleFailed(f"must be root or {user.name}") + raise ModuleFailed(f"must be [blue]root[/blue] or [blue]{user.name}[/blue]") # Ensure the directory exists homedir = session.platform.Path(user.home) @@ -93,10 +93,12 @@ class Module(ImplantModule): yield Status("verifying user permissions") current_user = session.current_user() if user != "__pwncat_current__" and current_user.id != 0: - raise ModuleFailed("only root can install implants for other users") + raise ModuleFailed( + "only [blue]root[/blue] can install implants for other users" + ) if not os.path.isfile(key): - raise ModuleFailed(f"private key {key} does not exist") + raise ModuleFailed(f"private key [blue]{key}[/blue] does not exist") try: yield Status("reading public key") @@ -119,7 +121,7 @@ class Module(ImplantModule): for implant in session.run("enumerate", types=["implant.*"]): if implant.source == self.name and implant.uid == user_info.uid: raise ModuleFailed( - f"{self.name} already installed for {user_info.name}" + f"[blue]{self.name}[/blue] already installed for [blue]{user_info.name}[/blue]" ) # Ensure the directory exists diff --git a/pwncat/platform/linux.py b/pwncat/platform/linux.py index d259d9e..52b0f28 100644 --- a/pwncat/platform/linux.py +++ b/pwncat/platform/linux.py @@ -1111,7 +1111,7 @@ class Linux(Platform): ) if isinstance(args, list): - command = shlex.join(args) + command = shlex.join(str(arg) for arg in args) elif isinstance(args, str): command = args else: diff --git a/pwncat/util.py b/pwncat/util.py index a3b8a56..9beb52d 100644 --- a/pwncat/util.py +++ b/pwncat/util.py @@ -146,7 +146,7 @@ def human_readable_delta(seconds): def join(argv: List[str]): - """Join the string much line shlex.join, except assume that each token + """Join the string much like shlex.join, except assume that each token is expecting double quotes. This allows variable references within the tokens.""" diff --git a/tests/conftest.py b/tests/conftest.py index bb1f4b2..a65517f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,9 +8,10 @@ import dataclasses from io import StringIO import pytest -from pwncat.channel import ChannelError from Crypto.PublicKey import RSA +from pwncat.channel import ChannelError + PLATFORM_MAP = {"ubuntu": "linux", "centos": "linux", "windows": "windows"} @@ -37,13 +38,13 @@ def connection_details_for(name): @pytest.fixture(params=["ubuntu", "centos"]) def linux_details(request): - """ Get available connection details for linux hosts """ + """Get available connection details for linux hosts""" return connection_details_for(request.param) @pytest.fixture(params=["windows"]) def windows_details(request): - """ Get available connection details for windows hosts """ + """Get available connection details for windows hosts""" return connection_details_for(request.param) @@ -84,18 +85,18 @@ set -g db "memory://" @pytest.fixture(params=["windows", "ubuntu", "centos"]) def session(request): - """ Start a session with any platform """ + """Start a session with any platform""" yield from session_for(request) @pytest.fixture(params=["windows"]) def windows(request): - """ Start a windows session """ + """Start a windows session""" yield from session_for(request) @pytest.fixture(params=["ubuntu", "centos"]) def linux(request): - """ Start a linux session """ + """Start a linux session""" yield from session_for(request) diff --git a/tests/test_session.py b/tests/test_session.py index 79949d3..2f8339a 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import pytest + from pwncat.modules import IncorrectPlatformError @@ -13,13 +14,13 @@ def test_session_iter_users(session): def test_session_find_user_name(session): - """ Test that locating a user by name works """ + """Test that locating a user by name works""" assert session.find_user(name="john") is not None def test_session_find_user_uid(linux): - """ Test locating a user by their UID (for linux only) """ + """Test locating a user by their UID (for linux only)""" user = linux.find_user(uid=0) @@ -28,7 +29,7 @@ def test_session_find_user_uid(linux): def test_session_find_user_sid(windows): - """ Test locating a user by their SID (for windows only) """ + """Test locating a user by their SID (for windows only)""" # This is the SID of the Administrator in the windows servercore image... # This will only work from the testing container, but I've decided that's fine. @@ -39,7 +40,7 @@ def test_session_find_user_sid(windows): def test_session_find_module(session): - """ Test that locating modules works """ + """Test that locating modules works""" assert len(list(session.find_module("enumerate.*"))) > 0 assert len(list(session.find_module("enumerate.user"))) == 1 @@ -47,7 +48,7 @@ def test_session_find_module(session): def test_session_run_module(session): - """ Test running a module within a session """ + """Test running a module within a session""" # We should be able to enumerate a hostname facts = session.run("enumerate", types=["system.hostname"]) @@ -55,14 +56,14 @@ def test_session_run_module(session): def test_session_wrong_platform_linux(linux): - """ Test that windows modules don't run in linux """ + """Test that windows modules don't run in linux""" with pytest.raises(IncorrectPlatformError): linux.run("windows.enumerate.user") def test_session_wrong_platform_windows(windows): - """ Test that linux modules don't run on windows """ + """Test that linux modules don't run on windows""" with pytest.raises(IncorrectPlatformError): windows.run("linux.enumerate.user")