Merge pull request #227 from calebstewart/issue-91-paramiko-ng

Add support for SSHAgent and Key Types outside of RSA
This commit is contained in:
Caleb Stewart 2021-12-26 03:21:14 -05:00 committed by GitHub
commit 6a913f0fb3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 112 additions and 112 deletions

View File

@ -11,6 +11,8 @@ and simply didn't have the time to go back and retroactively create one.
### Changed
- Fixed parsing of `--ssl` argument in main entrypoint ([#225](https://github.com/calebstewart/pwncat/issues/225))
- Replaced `paramiko` with `paramiko-ng`
- Utilized Paramiko SSHClient which will also utilize the SSHAgent if available by default and supports key types aside from RSA ([#91](https://github.com/calebstewart/pwncat/issues/91))
## [0.5.1] - 2021-12-07
Minor bug fixes. Mainly typos from changing the package name.

65
poetry.lock generated
View File

@ -120,7 +120,7 @@ test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"]
[[package]]
name = "cryptography"
version = "3.4.7"
version = "36.0.1"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
category = "main"
optional = false
@ -131,11 +131,11 @@ cffi = ">=1.12"
[package.extras]
docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"]
docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"]
pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
sdist = ["setuptools-rust (>=0.11.4)"]
sdist = ["setuptools_rust (>=0.11.4)"]
ssh = ["bcrypt (>=3.1.5)"]
test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
test = ["pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
[[package]]
name = "docutils"
@ -261,23 +261,20 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
pyparsing = ">=2.0.2"
[[package]]
name = "paramiko"
version = "2.7.2"
name = "paramiko-ng"
version = "2.8.8"
description = "SSH2 protocol library"
category = "main"
optional = false
python-versions = "*"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.dependencies]
bcrypt = ">=3.1.3"
cryptography = ">=2.5"
pynacl = ">=1.0.1"
bcrypt = ">=3"
cryptography = ">=1.6"
[package.extras]
all = ["pyasn1 (>=0.1.7)", "pynacl (>=1.0.1)", "bcrypt (>=3.1.3)", "invoke (>=1.3)", "gssapi (>=1.4.1)", "pywin32 (>=2.1.8)"]
ed25519 = ["pynacl (>=1.0.1)", "bcrypt (>=3.1.3)"]
gssapi = ["pyasn1 (>=0.1.7)", "gssapi (>=1.4.1)", "pywin32 (>=2.1.8)"]
invoke = ["invoke (>=1.3)"]
ed25519 = ["pynacl"]
gssapi = ["gssapi", "pyasn1"]
[[package]]
name = "persistent"
@ -787,7 +784,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
[metadata]
lock-version = "1.1"
python-versions = "^3.9"
content-hash = "9357cb6dd124d9b201849436e710ae4baec36f65104fa505e5e155c60d602e79"
content-hash = "23945db2620e31067d3b8810462ebaa6627fde4220ed01d49ba009e89962a725"
[metadata.files]
alabaster = [
@ -931,18 +928,26 @@ commonmark = [
{file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"},
]
cryptography = [
{file = "cryptography-3.4.7-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1"},
{file = "cryptography-3.4.7-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250"},
{file = "cryptography-3.4.7-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2"},
{file = "cryptography-3.4.7-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6"},
{file = "cryptography-3.4.7-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959"},
{file = "cryptography-3.4.7-cp36-abi3-win32.whl", hash = "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d"},
{file = "cryptography-3.4.7-cp36-abi3-win_amd64.whl", hash = "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca"},
{file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873"},
{file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d"},
{file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177"},
{file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"},
{file = "cryptography-3.4.7.tar.gz", hash = "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713"},
{file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:73bc2d3f2444bcfeac67dd130ff2ea598ea5f20b40e36d19821b4df8c9c5037b"},
{file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:2d87cdcb378d3cfed944dac30596da1968f88fb96d7fc34fdae30a99054b2e31"},
{file = "cryptography-36.0.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74d6c7e80609c0f4c2434b97b80c7f8fdfaa072ca4baab7e239a15d6d70ed73a"},
{file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:6c0c021f35b421ebf5976abf2daacc47e235f8b6082d3396a2fe3ccd537ab173"},
{file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d59a9d55027a8b88fd9fd2826c4392bd487d74bf628bb9d39beecc62a644c12"},
{file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a817b961b46894c5ca8a66b599c745b9a3d9f822725221f0e0fe49dc043a3a3"},
{file = "cryptography-36.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:94ae132f0e40fe48f310bba63f477f14a43116f05ddb69d6fa31e93f05848ae2"},
{file = "cryptography-36.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7be0eec337359c155df191d6ae00a5e8bbb63933883f4f5dffc439dac5348c3f"},
{file = "cryptography-36.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e0344c14c9cb89e76eb6a060e67980c9e35b3f36691e15e1b7a9e58a0a6c6dc3"},
{file = "cryptography-36.0.1-cp36-abi3-win32.whl", hash = "sha256:4caa4b893d8fad33cf1964d3e51842cd78ba87401ab1d2e44556826df849a8ca"},
{file = "cryptography-36.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:391432971a66cfaf94b21c24ab465a4cc3e8bf4a939c1ca5c3e3a6e0abebdbcf"},
{file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bb5829d027ff82aa872d76158919045a7c1e91fbf241aec32cb07956e9ebd3c9"},
{file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc15b1c22e55c4d5566e3ca4db8689470a0ca2babef8e3a9ee057a8b82ce4b1"},
{file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:596f3cd67e1b950bc372c33f1a28a0692080625592ea6392987dba7f09f17a94"},
{file = "cryptography-36.0.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:30ee1eb3ebe1644d1c3f183d115a8c04e4e603ed6ce8e394ed39eea4a98469ac"},
{file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec63da4e7e4a5f924b90af42eddf20b698a70e58d86a72d943857c4c6045b3ee"},
{file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca238ceb7ba0bdf6ce88c1b74a87bffcee5afbfa1e41e173b1ceb095b39add46"},
{file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:ca28641954f767f9822c24e927ad894d45d5a1e501767599647259cbf030b903"},
{file = "cryptography-36.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:39bdf8e70eee6b1c7b289ec6e5d84d49a6bfa11f8b8646b5b3dfe41219153316"},
{file = "cryptography-36.0.1.tar.gz", hash = "sha256:53e5c1dc3d7a953de055d77bef2ff607ceef7a2aac0353b5d630ab67f7423638"},
]
docutils = [
{file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"},
@ -1052,9 +1057,9 @@ packaging = [
{file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"},
{file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"},
]
paramiko = [
{file = "paramiko-2.7.2-py2.py3-none-any.whl", hash = "sha256:4f3e316fef2ac628b05097a637af35685183111d4bc1b5979bd397c2ab7b5898"},
{file = "paramiko-2.7.2.tar.gz", hash = "sha256:7f36f4ba2c0d81d219f4595e35f70d56cc94f9ac40a6acdf51d6ca210ce65035"},
paramiko-ng = [
{file = "paramiko-ng-2.8.8.tar.gz", hash = "sha256:d11191bda27dea3d4cd080c41912bd286ba3879c49851b6c7fc286c2fd0d4497"},
{file = "paramiko_ng-2.8.8-py2.py3-none-any.whl", hash = "sha256:21c8ad4d86006862ef32f6a1f68e563d3dd07fef4336b89a61d2f4c465806e8e"},
]
persistent = [
{file = "persistent-4.7.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:ed939f58937f43a67a0f60abb0057e7f31e90d35b6a215159b04248368696930"},

View File

@ -8,7 +8,7 @@ An optional port argument is also accepted.
"""
import os
import socket
from typing import Optional
from typing import Union, TextIO, Optional
import paramiko
from prompt_toolkit import prompt
@ -46,66 +46,32 @@ class Ssh(Channel):
password = prompt("Password: ", is_password=True)
try:
# Connect to the remote host's ssh server
sock = socket.create_connection((host, port))
except Exception as exc:
raise ChannelError(self, str(exc))
client = paramiko.client.SSHClient()
client.set_missing_host_key_policy(paramiko.client.AutoAddPolicy)
# Create a paramiko SSH transport layer around the socket
t = paramiko.Transport(sock)
try:
t.start_client()
except paramiko.SSHException:
sock.close()
raise ChannelError(self, "ssh negotiation failed")
if identity is not None:
try:
# Load the private key for the user
if isinstance(identity, str):
key = paramiko.RSAKey.from_private_key_file(
os.path.expanduser(identity)
client.connect(
hostname=host,
port=port,
username=user,
password=password,
pkey=load_private_key(identity),
allow_agent=True,
look_for_keys=False,
)
else:
key = paramiko.RSAKey.from_private_key(identity)
except paramiko.ssh_exception.SSHException:
password = prompt("RSA Private Key Passphrase: ", is_password=True)
try:
if isinstance(identity, str):
key = paramiko.RSAKey.from_private_key_file(identity, password)
else:
# Seek back to the beginning of the file (the above load read the whole file)
identity.seek(0)
key = paramiko.RSAKey.from_private_key(identity, password)
except paramiko.ssh_exception.SSHException:
raise ChannelError(self, "invalid private key or passphrase")
# Attempt authentication
try:
t.auth_publickey(user, key)
except paramiko.ssh_exception.AuthenticationException as exc:
raise ChannelError(self, str(exc))
else:
try:
t.auth_password(user, password)
except paramiko.ssh_exception.AuthenticationException as exc:
raise ChannelError(self, str(exc))
columns, rows = os.get_terminal_size(0)
shell = client.invoke_shell(width=columns, height=rows)
shell.setblocking(0)
if not t.is_authenticated():
t.close()
sock.close()
raise ChannelError(self, "authentication failed")
# Open an interactive session
chan = t.open_session()
chan.get_pty()
chan.invoke_shell()
chan.setblocking(0)
self.client = chan
self.client = shell
self.address = (host, port)
self._connected = True
except paramiko.ssh_exception.AuthenticationException as exc:
raise ChannelError(self, f"ssh authentication failed: {str(exc)}") from exc
except (paramiko.ssh_exception.SSHException, socket.error) as exc:
raise ChannelError(self, f"ssh connection failed: {str(exc)}") from exc
@property
def connected(self):
return self._connected
@ -153,3 +119,36 @@ class Ssh(Channel):
pass
return data
def load_private_key(identity: Union[str, TextIO], passphrase: str = None):
"""Load a private key and return the appropriate PKey object"""
if identity is None:
return None
try:
if isinstance(identity, str):
return paramiko.pkey.load_private_key_file(
os.path.expanduser(identity), password=passphrase
)
identity.seek(0)
return paramiko.pkey.load_private_key(identity.read(), password=passphrase)
except paramiko.PasswordRequiredException:
# Bad passphrase
if passphrase is not None:
raise
try:
# No passphrase, prompt for one
passphrase = prompt("Private Key Passphrase: ", is_password=True)
except KeyboardInterrupt:
passphrase = None
# No passphrase given, re-raise
if passphrase is None:
raise
# Try again with the given passphrase
return load_private_key(identity, passphrase=passphrase)

View File

@ -5,9 +5,8 @@ should not generally need to use these types except as reference
when interacting with data returned by an enumeration module.
"""
import time
import pathlib
import tempfile
import subprocess
from io import StringIO
from typing import Callable, Optional
import rich.markup
@ -356,19 +355,13 @@ class PrivateKey(Implant):
else:
raise ModuleFailed(f"unknown username for uid={self.uid}")
with tempfile.NamedTemporaryFile("w") as filp:
filp.write(self.content)
filp.flush()
pathlib.Path(filp.name).chmod(0o600)
try:
# Connect via SSH
session = manager.create_session(
"linux",
host=target.public_address[0],
user=user.name,
identity=filp.name,
identity=StringIO(self.content + "\n"),
)
except (ChannelError, PlatformError) as exc:
manager.log(

View File

@ -40,8 +40,9 @@ rich = "^10.4.0"
python-rapidjson = "^1.0"
ZODB3 = "^3.11.0"
zodburi = "^2.5.0"
paramiko = "^2.7.2"
Jinja2 = "^3.0.1"
paramiko-ng = "^2.8.8"
PyNaCl = "^1.4.0"
[tool.poetry.dev-dependencies]
isort = "^5.8.0"