diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..ba8a88c --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,36 @@ +## Description of Changes + +Fixes #XXX. + +**Please note any `noqa:` comments needed to appease flake8.** + +## Major Changes Implemented: +- +- +- + +## Pre-Merge Tasks +- [ ] Formatted all modified files w/ `python-black` +- [ ] Sorted imports for modified files w/ `isort` +- [ ] Ran `flake8` on repo, and fixed any new problems w/ modified files +- [ ] Ran `pytest` test cases +- [ ] Added brief summary of updates to CHANGELOG (under `[Unreleased]`) + +**For issues with pre-merge tasks, see CONTRIBUTING.md** + + diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7a99f38..64309b7 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -29,9 +29,19 @@ jobs: # They are stored in ~/.local/share/pwncat by default tar czvf pwncat-plugins.tar.gz --transform='s|.*pwncat/||' ~/.local/share/pwncat/* + - name: Extract Release Body + run: | + # Grab tag name without the `v` + version=$(git describe --tags --abbrev=0 | sed 's/v//') + + # build a changelog with just logs from this release + echo "## Changelog" >> this_version_changelog.md + cat CHANGELOG.md | sed -n '/^## \['"$version"'\]/,/^## /p' | head -n -1 | tail -n +2 >> this_version_changelog.md + echo "[Full Changelog](https://github.com/calebstewart/pwncat/blob/v$version/CHANGELOG.md)" >> this_version_changelog.md - name: Publish Plugins uses: softprops/action-gh-release@v1 with: files: "pwncat-plugins.tar.gz" + body_path: this_version_changelog.md env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..305ccf7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,36 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +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] + +## [0.4.2] - 2021-06-15 +Quick patch release due to corrected bug in `ChannelFile` which caused command +output to be empty in some situations. + +### Fixed +- Fixed `linux.enumerate.system.network` to work with old and new style `ip`. +- Fixed `ChannelFile.recvinto` which will no longer raise `BlockingIOError` ([#126](https://github.com/calebstewart/pwncat/issues/126), [#131](https://github.com/calebstewart/pwncat/issues/131)) +- Fixed sessions command with invalid session ID ([#130](https://github.com/calebstewart/pwncat/issues/130)) +- Fixed zsh shell prompt color syntax ([#130](https://github.com/calebstewart/pwncat/issues/130)) +### Added +- Added Pull Request template +- Added CONTRIBUTING.md +- Added `--version` option to entrypoint to retrieve pwncat version +- Added `latest` tag to documented install command to prevent dev installs + +## [0.4.1] - 2021-06-14 +### Added +- Differentiate prompt syntax for standard bash, zsh and sh ([#126](https://github.com/calebstewart/pwncat/issues/126)) +- Added `-c=never` to `ip` command in `linux.enumerate.system.network` + ([#126](https://github.com/calebstewart/pwncat/issues/126)) +- Updated Dockerfile to properly build post-v0.4.0 releases ([#125](https://github.com/calebstewart/pwncat/issues/125)) +- Added check for `nologin` shell to stop pwncat from accidentally + closing the session ([#116](https://github.com/calebstewart/pwncat/issues/116)) +- Resolved all flake8 errors ([#123](https://github.com/calebstewart/pwncat/issues/123)) +- Improved EOF handling for Linux file-writes ([#117](https://github.com/calebstewart/pwncat/issues/117)) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..39fa46a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,124 @@ +# Contributing + +If you have an idea for a new feature or just want to help out with a bug +fix, please refer to this guide and follow the rules before submitting a +pull request. + +## Submitting Issues + +If you aren't a programmer or don't have the time to contribute code to the +project, we would still appreciate bug reports and feature requests. Please +use the appropriate issue type in the GitHub issue system to report either +bugs or feature requests. + +When reporting bugs, ensure you include the current version pwncat you have +installed, what type of target/victim you are using, what payload you used +on the target to gain a shell, any relevant tracebacks, and of course +screenshots if they add context to your problem. In general, the more +information we have, the more chance there is we can fix the problem. + +For feature requests, please be very specific on what you would like pwncat +to do. We can't read your mind, and English isn't perfect. If you are +interested in or willing to help implement your new feature, please explicitly +let us know. This will help in prioritizing the issue. + +## Submitting Pull Requests + +When submitting a pull request, ensure you have read through and comply with +these contributing rules. The pull request template should guide you through +the things that need done before merging code. + +For help with running pre-merge tools, see the styling and formatting section +below. For running pytest test cases, see the testing section. + +Before submitting your changes in a pull request, please add a brief one-line +summary to the `CHANGELOG.md` file under the `[Unreleased]` heading. This makes +releases more straightforward and bug fixes and features are added along the way. +For information on the format of the changelog, see +[Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + +If you are submitting a bug fix, annotate this with `Fixes #XXX` replacing the +`XXX` with the issue number. This ensures that the issue will be closed once +the bug fix is merged. If your bug fix does not **completely** fix the issue, +do not use the `Fixes` keyword. Instead, mention the issue by number in your +pull request to ensure the link between the issue and pull request is clear. + +## Versioning + +pwncat follows Semantic Versioning. You can learn about the basics of semver +[here](https://semver.org). pwncat does not currently have any release +schedule, but in general the following rules apply: + +- `PATCH` fixes are released whenever there is either significant aggregate of + bug fixes or when a significantly agregious bug is fixed. The decision for + what "significant" means will be decided by a project owner. +- `MINOR` releases are for added functionality. The pwncat API is relatively + stable, but still has not attained `v1.0.0` status, and therefore minor + releases could make breaking API changes. However, a concerted effort + should be made to make all changes backwards compatible. + +As mentioned above, pwncat has not reached `v1.0.0` yet. As such, I don't have +rules yet for `MAJOR` version bumps. I will update this file as the situation +develops. + +## Making Changes + +In general, when contributing to a project on GitHub, you should work from a +branch. This helps organize your changes within the project. There are two +main branches which pwncat uses to organize contributions: `master` and the +next release branch (named like `release-vX.Y.Z`). + +- Any bug fixes which do not add new features should be made targeting `master`. +- Any new features should be made targeting the latest `release-vX.Y.Z`. + +When forking the repository to make contributions, you can work directly out +of your fork's `master` or `release` branches or fork them. When creating a +pull request, you must target the appropriate branch based on the intent of +your work. + +Pull requests targeting the wrong branch will be retargeted, which could +cause issues while merging. + +## Styling and Format + +The majority of pwncat is written in Python. We use `python-black` to format +code in a consistent and readable manner. We recommend you install a Black +plugin for your editor or IDE to ensure all code is formatted prior to +opening a pull request. + +Beyond Black, you should also run `isort` and `flake8` within your branch +prior to opening a pull request. `isort` will sort your imports to ensure +they are easy to read. `flake8` will notify you of some common Python +errors. pwncat has `flake8` and `isort` configurations, so the process is +as simple as running the associated tool. + +Prior to creating a pull request, please run the following from the repository +root to ensure formatting is in order: + +```sh +# Automatically fixes imports +isort ./pwncat +# Automatically fixes formatting +black ./pwncat +# Warns of errors or other syntax problems +flake8 +``` + +## Testing Your Changes + +Testing pwncat is difficult. There are some unit tests implemented in `tests/`. +These tests can be executed with `pytest`, but you must provide suitable targets +for the testing framework. The `run-tests.sh` script uses `podman` to start two +containers to act as targets, and then runs all tests. One container is a Ubuntu +machine with a bind shell and the other is a CentOS container with a bind shell. + +If you are creating Windows features, you can run the Windows tests as well by +manually providing a Windows bind shell target: + +```sh +WINDOWS_HOST=10.10.10.10 WINDOWS_BIND_PORT=4444 ./run-tests.sh +``` + +The included unit tests are not great. They do not have a lot of coverage, but +they at least ensure that the basic automated functionality of pwncat is not +broken across some common target types. diff --git a/docs/source/installation.rst b/docs/source/installation.rst index eabf1c6..b9bc894 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -13,7 +13,8 @@ Once you have a working ``pip`` installation, you can install pwncat with the pr # A virtual environment is recommended python -m venv /opt/pwncat # Install pwncat within the virtual environment - /opt/pwncat/bin/pip install git+https://github.com/calebstewart/pwncat + # Replace `latest` with a versioned tag if needed (e.g. `v0.4.0`) + /opt/pwncat/bin/pip install 'git+https://github.com/calebstewart/pwncat@latest#egg=pwncat' # This allows you to use pwncat outside of the virtual environment ln -s /opt/pwncat/bin/pwncat /usr/local/bin diff --git a/pwncat/__main__.py b/pwncat/__main__.py index e2e949a..1b5ecbc 100644 --- a/pwncat/__main__.py +++ b/pwncat/__main__.py @@ -2,6 +2,7 @@ import sys import logging import argparse +import importlib.metadata from rich import box from rich.table import Table @@ -23,6 +24,9 @@ def main(): parser = argparse.ArgumentParser( description="""Start interactive pwncat session and optionally connect to existing victim via a known platform and channel type. This entrypoint can also be used to list known implants on previous targets.""" ) + parser.add_argument( + "--version", "-v", action="store_true", help="Show version number and exit" + ) parser.add_argument( "--download-plugins", action="store_true", @@ -78,6 +82,11 @@ def main(): ) args = parser.parse_args() + # Print the version number and exit. + if args.version: + print(importlib.metadata.version("pwncat")) + return + # Create the session manager with pwncat.manager.Manager(args.config) as manager: diff --git a/pwncat/commands/download.py b/pwncat/commands/download.py index d927424..2f75f01 100644 --- a/pwncat/commands/download.py +++ b/pwncat/commands/download.py @@ -14,11 +14,7 @@ from rich.progress import ( import pwncat from pwncat import util from pwncat.util import console -from pwncat.commands import ( - Complete, - Parameter, - CommandDefinition, -) +from pwncat.commands import Complete, Parameter, CommandDefinition class Command(CommandDefinition): diff --git a/pwncat/commands/help.py b/pwncat/commands/help.py index a0fd437..781ba45 100644 --- a/pwncat/commands/help.py +++ b/pwncat/commands/help.py @@ -6,7 +6,7 @@ from rich.table import Table, Column import pwncat from pwncat.util import console -from pwncat.commands import CommandDefinition, Complete, Parameter +from pwncat.commands import Complete, Parameter, CommandDefinition class Command(CommandDefinition): diff --git a/pwncat/commands/upload.py b/pwncat/commands/upload.py index 78e8a81..95cb0b8 100644 --- a/pwncat/commands/upload.py +++ b/pwncat/commands/upload.py @@ -12,12 +12,7 @@ from rich.progress import ( ) import pwncat -from pwncat.util import ( - console, - copyfileobj, - human_readable_size, - human_readable_delta, -) +from pwncat.util import console, copyfileobj, human_readable_size, human_readable_delta from pwncat.commands import Complete, Parameter, CommandDefinition diff --git a/pwncat/modules/linux/enumerate/file/suid.py b/pwncat/modules/linux/enumerate/file/suid.py index f4ed0ef..1d0c018 100644 --- a/pwncat/modules/linux/enumerate/file/suid.py +++ b/pwncat/modules/linux/enumerate/file/suid.py @@ -5,9 +5,7 @@ import rich.markup import pwncat from pwncat.db import Fact -from pwncat.facts.ability import ( - build_gtfo_ability, -) +from pwncat.facts.ability import build_gtfo_ability from pwncat.platform.linux import Linux from pwncat.modules.enumerate import Schedule, EnumerateModule diff --git a/pwncat/modules/linux/enumerate/software/cron.py b/pwncat/modules/linux/enumerate/software/cron.py index 9b6a4e6..090299c 100644 --- a/pwncat/modules/linux/enumerate/software/cron.py +++ b/pwncat/modules/linux/enumerate/software/cron.py @@ -2,7 +2,6 @@ import os import re - from pwncat.db import Fact from pwncat.modules import Status from pwncat.subprocess import CalledProcessError diff --git a/pwncat/modules/linux/enumerate/system/network.py b/pwncat/modules/linux/enumerate/system/network.py index 20a1405..f31ee04 100644 --- a/pwncat/modules/linux/enumerate/system/network.py +++ b/pwncat/modules/linux/enumerate/system/network.py @@ -3,6 +3,7 @@ import rich.markup from pwncat.db import Fact +from pwncat.subprocess import CalledProcessError from pwncat.platform.linux import Linux from pwncat.modules.enumerate import Schedule, EnumerateModule @@ -32,32 +33,36 @@ class Module(EnumerateModule): try: output = session.platform.run( - ["ip", "-c=never", "addr"], capture_output=True, text=True + ["ip", "-c=never", "addr"], capture_output=True, text=True, check=True ) - if output.stdout: - output = ( - line - for line in output.stdout.replace("\r\n", "\n").split("\n") - if line + except CalledProcessError: + try: + output = session.platform.run( + ["ip", "addr"], capture_output=True, text=True, check=True ) - - interface = None - - for line in output: - if not line.startswith(" "): - interface = line.split(":")[1].strip() - continue - - if interface is None: - # This shouldn't happen. The first line should be an interface - # definition, but just in case - continue - - line = line.strip() - if line.startswith("inet"): - address = line.split(" ")[1] - yield InterfaceData(self.name, interface, address) - - return + except CalledProcessError: + return except FileNotFoundError: - pass + return + + if output.stdout: + output = ( + line for line in output.stdout.replace("\r\n", "\n").split("\n") if line + ) + + interface = None + + for line in output: + if not line.startswith(" "): + interface = line.split(":")[1].strip() + continue + + if interface is None: + # This shouldn't happen. The first line should be an interface + # definition, but just in case + continue + + line = line.strip() + if line.startswith("inet"): + address = line.split(" ")[1] + yield InterfaceData(self.name, interface, address) diff --git a/pwncat/modules/linux/enumerate/system/process.py b/pwncat/modules/linux/enumerate/system/process.py index 5fab15e..eb7b9c5 100644 --- a/pwncat/modules/linux/enumerate/system/process.py +++ b/pwncat/modules/linux/enumerate/system/process.py @@ -2,7 +2,6 @@ import shlex from typing import List - from pwncat.db import Fact from pwncat.platform.linux import Linux from pwncat.modules.enumerate import Schedule, EnumerateModule diff --git a/pwncat/modules/linux/enumerate/system/services.py b/pwncat/modules/linux/enumerate/system/services.py index 922c766..f316d5d 100644 --- a/pwncat/modules/linux/enumerate/system/services.py +++ b/pwncat/modules/linux/enumerate/system/services.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 import subprocess - from pwncat.db import Fact from pwncat.util import Init from pwncat.platform.linux import Linux diff --git a/pwncat/platform/linux.py b/pwncat/platform/linux.py index 8dbc724..19de36c 100644 --- a/pwncat/platform/linux.py +++ b/pwncat/platform/linux.py @@ -1041,8 +1041,6 @@ class Linux(Platform): command += f" 2>{stderr}" elif stderr == pwncat.subprocess.DEVNULL: command += " 2>/dev/null" - elif stderr == pwncat.subprocess.PIPE: - command += " 2>&1" if isinstance(stdin, str): command += f" 0<{stdin}" diff --git a/setup.py b/setup.py index 46a8577..3322ec1 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ dependency_links = [] # Setup setup( name="pwncat", - version="0.4.0a1", + version="0.4.2", python_requires=">=3.8", description="A fancy reverse and bind shell handler", author="Caleb Stewart", diff --git a/test.zip b/test.zip deleted file mode 100644 index 17e972c..0000000 Binary files a/test.zip and /dev/null differ